内核优化
This commit is contained in:
@@ -1,4 +1,11 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include "renderive/frame_control/Frame_Control.hpp"
|
||||
struct Flow_Refresh_Test_Frame {
|
||||
int value{};
|
||||
@@ -26,3 +33,150 @@ TEST(flow_refresh_strategy_test, returns_empty_lease_when_queue_is_empty) {
|
||||
EXPECT_FALSE(frame);
|
||||
EXPECT_EQ(strategy.state().empty_acquire_count, 1);
|
||||
}
|
||||
TEST(flow_refresh_strategy_test, accepts_multiple_concurrent_producers_without_losing_frames) {
|
||||
Flow_Refresh_Test_Strategy strategy;
|
||||
constexpr int producer_count = 4;
|
||||
constexpr int frames_per_producer = 100;
|
||||
std::vector<std::thread> producers;
|
||||
for (int producer = 0; producer < producer_count; ++producer) {
|
||||
producers.emplace_back([&strategy, producer] {
|
||||
for (int index = 0; index < frames_per_producer; ++index) {
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = producer * 1000 + index;
|
||||
}
|
||||
});
|
||||
}
|
||||
for (auto& producer : producers) {
|
||||
producer.join();
|
||||
}
|
||||
EXPECT_EQ(strategy.pending_frame_count(), producer_count * frames_per_producer);
|
||||
std::vector<int> values;
|
||||
while (auto frame = strategy.acquire_renderer()) {
|
||||
values.push_back(frame->value);
|
||||
}
|
||||
std::sort(values.begin(), values.end());
|
||||
EXPECT_EQ(values.size(), producer_count * frames_per_producer);
|
||||
for (int producer = 0; producer < producer_count; ++producer) {
|
||||
for (int index = 0; index < frames_per_producer; ++index) {
|
||||
EXPECT_TRUE(std::binary_search(values.begin(), values.end(), producer * 1000 + index));
|
||||
}
|
||||
}
|
||||
}
|
||||
TEST(flow_refresh_strategy_test, concurrent_consumer_does_not_underflow_pending_count) {
|
||||
Flow_Refresh_Test_Strategy strategy;
|
||||
constexpr int producer_count = 4;
|
||||
constexpr int frames_per_producer = 200;
|
||||
constexpr int frame_count = producer_count * frames_per_producer;
|
||||
std::atomic<int> producers_done{};
|
||||
std::atomic<int> consumed{};
|
||||
std::vector<int> values;
|
||||
values.reserve(frame_count);
|
||||
std::thread consumer([&] {
|
||||
while (consumed.load(std::memory_order_acquire) != frame_count) {
|
||||
auto frame = strategy.acquire_renderer();
|
||||
if (!frame) {
|
||||
if (producers_done.load(std::memory_order_acquire) == producer_count && strategy.pending_frame_count() == 0) {
|
||||
break;
|
||||
}
|
||||
std::this_thread::yield();
|
||||
continue;
|
||||
}
|
||||
values.push_back(frame->value);
|
||||
consumed.fetch_add(1, std::memory_order_release);
|
||||
}
|
||||
});
|
||||
std::vector<std::thread> producers;
|
||||
for (int producer = 0; producer < producer_count; ++producer) {
|
||||
producers.emplace_back([&strategy, &producers_done, producer] {
|
||||
for (int index = 0; index < frames_per_producer; ++index) {
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = producer * 1000 + index;
|
||||
}
|
||||
producers_done.fetch_add(1, std::memory_order_release);
|
||||
});
|
||||
}
|
||||
for (auto& producer : producers) {
|
||||
producer.join();
|
||||
}
|
||||
consumer.join();
|
||||
EXPECT_EQ(consumed.load(std::memory_order_acquire), frame_count);
|
||||
EXPECT_EQ(strategy.pending_frame_count(), 0);
|
||||
std::sort(values.begin(), values.end());
|
||||
EXPECT_EQ(values.size(), frame_count);
|
||||
for (int producer = 0; producer < producer_count; ++producer) {
|
||||
for (int index = 0; index < frames_per_producer; ++index) {
|
||||
EXPECT_TRUE(std::binary_search(values.begin(), values.end(), producer * 1000 + index));
|
||||
}
|
||||
}
|
||||
}
|
||||
struct Flow_Refresh_Reentrant_Observer_Data {
|
||||
std::function<void(int)> callback;
|
||||
};
|
||||
struct Flow_Refresh_Reentrant_Observer {
|
||||
static constexpr bool enabled = true;
|
||||
std::shared_ptr<Flow_Refresh_Reentrant_Observer_Data> data{std::make_shared<Flow_Refresh_Reentrant_Observer_Data>()};
|
||||
template <class Observation>
|
||||
void observe(const Observation& observation) noexcept {
|
||||
if (data->callback) {
|
||||
data->callback(static_cast<int>(observation.event));
|
||||
}
|
||||
}
|
||||
};
|
||||
using Flow_Refresh_Reentrant_Observer_State = Observer_State<Flow_Refresh_Reentrant_Observer>;
|
||||
using Flow_Refresh_Reentrant_Strategy = Flow_Refresh_Strategy<Flow_Refresh_Test_Frame, std::mutex, Flow_Refresh_Reentrant_Observer_State>;
|
||||
TEST(flow_refresh_strategy_test, observer_can_reenter_observer_state_without_deadlock) {
|
||||
Flow_Refresh_Reentrant_Observer recorder;
|
||||
auto data = recorder.data;
|
||||
Flow_Refresh_Reentrant_Strategy strategy{Flow_Refresh_Reentrant_Observer_State(recorder)};
|
||||
std::atomic<bool> reentered{};
|
||||
data->callback = [&](int event) {
|
||||
if (event != static_cast<int>(Flow_Refresh_Reentrant_Strategy::Observation_Event::enqueued) || reentered.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
strategy.on_real_time_data_update({Real_Time_Data_Observation_Event::updated, {nullptr, Real_Time_Data_Retention::latest, 1, 1, 1, 1}});
|
||||
};
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 1;
|
||||
}
|
||||
EXPECT_TRUE(reentered.load());
|
||||
EXPECT_EQ(strategy.state().real_time_data_update_sequence, 1);
|
||||
}
|
||||
struct Flow_Refresh_Render_Reentrant_Observer_Data {
|
||||
std::function<void()> callback;
|
||||
};
|
||||
struct Flow_Refresh_Render_Reentrant_Observer {
|
||||
static constexpr bool enabled = true;
|
||||
std::shared_ptr<Flow_Refresh_Render_Reentrant_Observer_Data> data{std::make_shared<Flow_Refresh_Render_Reentrant_Observer_Data>()};
|
||||
template <class Observation>
|
||||
void observe(const Observation& observation) noexcept {
|
||||
if (static_cast<int>(observation.event) == 1 && data->callback) {
|
||||
data->callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
using Flow_Refresh_Render_Reentrant_Observer_State = Observer_State<Flow_Refresh_Render_Reentrant_Observer>;
|
||||
using Flow_Refresh_Render_Reentrant_Strategy = Flow_Refresh_Strategy<Flow_Refresh_Test_Frame, std::mutex, Flow_Refresh_Render_Reentrant_Observer_State>;
|
||||
TEST(flow_refresh_strategy_test, render_observer_reentry_does_not_deadlock_render_mutex) {
|
||||
Flow_Refresh_Render_Reentrant_Observer recorder;
|
||||
auto data = recorder.data;
|
||||
Flow_Refresh_Render_Reentrant_Strategy strategy{Flow_Refresh_Render_Reentrant_Observer_State(recorder)};
|
||||
for (int value = 1; value <= 2; ++value) {
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = value;
|
||||
}
|
||||
std::atomic<bool> nested_result{true};
|
||||
data->callback = [&] {
|
||||
auto nested = strategy.acquire_renderer();
|
||||
nested_result.store(static_cast<bool>(nested), std::memory_order_release);
|
||||
};
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_EQ(frame->value, 1);
|
||||
}
|
||||
EXPECT_FALSE(nested_result.load(std::memory_order_acquire));
|
||||
auto second = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(second);
|
||||
EXPECT_EQ(second->value, 2);
|
||||
}
|
||||
|
||||
+26
@@ -1,4 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include "Low_Latency_Strategy_Test_Types.hpp"
|
||||
TEST(low_latency_strategy_test, publishes_and_renders_latest_frame) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
@@ -98,3 +100,27 @@ TEST(low_latency_strategy_test, detects_render_limited_state) {
|
||||
}
|
||||
EXPECT_EQ(strategy.state().limit_state, Low_Latency_Test_Strategy::Limit_State::render_limited);
|
||||
}
|
||||
TEST(low_latency_strategy_test, rejects_non_finite_frequency_configuration) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
EXPECT_THROW((Low_Latency_Test_Strategy(Low_Latency_Test_Observer_State(observer, time_source), {std::numeric_limits<double>::quiet_NaN()})), std::invalid_argument);
|
||||
EXPECT_THROW((Low_Latency_Test_Strategy(Low_Latency_Test_Observer_State(observer, time_source), {std::numeric_limits<double>::infinity()})), std::invalid_argument);
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
EXPECT_THROW(strategy.set_frequency_hz(std::numeric_limits<double>::quiet_NaN()), std::invalid_argument);
|
||||
EXPECT_THROW(strategy.set_frequency_hz(std::numeric_limits<double>::infinity()), std::invalid_argument);
|
||||
}
|
||||
TEST(low_latency_strategy_test, clamps_extremely_small_frequency_interval_without_overflow) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
auto strategy = make_low_latency_test_strategy(time_source, observer);
|
||||
strategy.set_frequency_hz(1e-300);
|
||||
{
|
||||
auto frame = strategy.acquire_painter();
|
||||
frame->value = 1;
|
||||
}
|
||||
{
|
||||
auto frame = strategy.acquire_renderer();
|
||||
ASSERT_TRUE(frame);
|
||||
}
|
||||
EXPECT_EQ(strategy.state().target_interval_ns, std::numeric_limits<std::uint64_t>::max());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user