From ceb29b1ce2436f896ec44d1bb65e03ab12efd1af Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Thu, 30 Jul 2026 08:07:43 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Demo_Gallery/base/Frequency_Axis.h | 35 ++++ Demo_Gallery/base/Frequency_Axis_Test.h | 36 ++++ Demo_Gallery/base/Multi_Select_Test.h | 43 +++++ tests/ASan_Runtime_Smoke.cpp | 7 + tests/Memory_Callbacks.cpp | 100 ++++++++++++ tests/Memory_Data_Path_Stress.cpp | 173 ++++++++++++++++++++ tests/Memory_Default.cpp | 25 +++ tests/Memory_Failure.cpp | 75 +++++++++ tests/Memory_Late_Config.cpp | 24 +++ tests/Memory_Plot_Late_Config.cpp | 28 ++++ tests/Memory_Pool_Benchmark.cpp | 208 ++++++++++++++++++++++++ tests/Update_Completion.cpp | 46 ++++++ 12 files changed, 800 insertions(+) create mode 100644 Demo_Gallery/base/Frequency_Axis.h create mode 100644 Demo_Gallery/base/Frequency_Axis_Test.h create mode 100644 Demo_Gallery/base/Multi_Select_Test.h create mode 100644 tests/ASan_Runtime_Smoke.cpp create mode 100644 tests/Memory_Callbacks.cpp create mode 100644 tests/Memory_Data_Path_Stress.cpp create mode 100644 tests/Memory_Default.cpp create mode 100644 tests/Memory_Failure.cpp create mode 100644 tests/Memory_Late_Config.cpp create mode 100644 tests/Memory_Plot_Late_Config.cpp create mode 100644 tests/Memory_Pool_Benchmark.cpp create mode 100644 tests/Update_Completion.cpp diff --git a/Demo_Gallery/base/Frequency_Axis.h b/Demo_Gallery/base/Frequency_Axis.h new file mode 100644 index 0000000..75e544f --- /dev/null +++ b/Demo_Gallery/base/Frequency_Axis.h @@ -0,0 +1,35 @@ +#pragma once +#include "../../Renderive/base/Tool.h" +static QWidget* create_frequent_axis_test_plot() { + class Axis_Test_Plot : public YSG::Plot { + public: + std::shared_ptr axis{}; + QMenu* menu{}; + void init() override { + Plot::init(); + object_name = "Axis_Test_Plot"; + auto axis_node = create_renderable_node(root_renderable(), "Axis_Renderable"); + axis = YSG::Frequency_Axis::Builder(axis_node, Qt::Horizontal) + .set_tick_length(-10) + .set_sub_tick_length(-5) + .build(); + } + protected: + void resizeEvent(QResizeEvent* event) override { + int space = 50; + axis->set_x(space); + axis->set_y(height() / 2); + axis->set_pixel_size(width() - space * 2); + } + void contextMenuEvent(QContextMenuEvent* event) override { + if (!menu) { + auto ws = bw(cw(axis.get()), "轴"); + menu = create_menu(this, ws); + } + menu->exec(event->globalPos()); + } + }; + auto w = new Axis_Test_Plot; + w->init(); + return w; +} diff --git a/Demo_Gallery/base/Frequency_Axis_Test.h b/Demo_Gallery/base/Frequency_Axis_Test.h new file mode 100644 index 0000000..0439314 --- /dev/null +++ b/Demo_Gallery/base/Frequency_Axis_Test.h @@ -0,0 +1,36 @@ +#pragma once +#include "../../Renderive/base/Tool.h" +static QWidget* create_frequent_axis_test_plot() { + class Axis_Test_Plot : public renderive::Plot { + public: + std::shared_ptr axis{}; + QMenu* menu{}; + void init() override { + Plot::init(); + object_name = "Axis_Test_Plot"; + auto axis_node = create_renderable_node(get_root_renderable(), "Axis_Renderable"); + axis = renderive::Frequency_Axis::Builder(axis_node, Qt::Horizontal) + .set_tick_length(-10) + .set_sub_tick_length(-5) + .set_coord_range({-12000, 12000}) + .build(); + } + protected: + void resizeEvent(QResizeEvent* event) override { + int space = 50; + axis->set_x(space); + axis->set_y(height() / 2); + axis->set_pixel_size(width() - space * 2); + } + void contextMenuEvent(QContextMenuEvent* event) override { + if (!menu) { + auto ws = bw(cw(axis.get()), "轴"); + menu = create_menu(this, ws); + } + menu->exec(event->globalPos()); + } + }; + auto w = new Axis_Test_Plot; + w->init(); + return w; +} diff --git a/Demo_Gallery/base/Multi_Select_Test.h b/Demo_Gallery/base/Multi_Select_Test.h new file mode 100644 index 0000000..b4ec0c4 --- /dev/null +++ b/Demo_Gallery/base/Multi_Select_Test.h @@ -0,0 +1,43 @@ +#pragma once +#include "../../Renderive/base/Tool.h" +static QWidget* create_Multi_Select_Plot() { + class Multi_Select_Plot : public renderive::Plot { + public: + std::shared_ptr x_axis{}; + std::shared_ptr y_axis{}; + std::shared_ptr multi_select_rect{}; + QMenu* menu{}; + void init() override { + Plot::init(); + object_name = "PlanispherePlot"; + auto axis = create_renderable_node(get_root_renderable(), "Axis_Renderable"); + auto overlay = create_renderable_node(get_root_renderable(), "Overlay_Renderable"); + x_axis = renderive::Axis::Builder(axis, Qt::Horizontal) + .set_tick_length(-10) + .set_sub_tick_length(-5) + .build(); + y_axis = renderive::Axis::Builder(axis, Qt::Vertical).build(); + multi_select_rect = renderive::Multi_Select_Rect::Builder(overlay, x_axis, y_axis).build(); + } + protected: + void resizeEvent(QResizeEvent* event) override { + x_axis->set_x(0); + x_axis->set_y(height() - 1); + x_axis->set_pixel_size(width()); + y_axis->set_x(0); + y_axis->set_y(0); + // y_axis->setCoordReserve(true); + y_axis->set_pixel_size(height()); + } + void contextMenuEvent(QContextMenuEvent* event) override { + if (!menu) { + auto ws = bw(cw(multi_select_rect.get()), "多选框"); + menu = create_menu(this, ws); + } + menu->exec(event->globalPos()); + } + }; + auto w = new Multi_Select_Plot; + w->init(); + return w; +} diff --git a/tests/ASan_Runtime_Smoke.cpp b/tests/ASan_Runtime_Smoke.cpp new file mode 100644 index 0000000..0d05f09 --- /dev/null +++ b/tests/ASan_Runtime_Smoke.cpp @@ -0,0 +1,7 @@ +#include +int main() { + auto values = std::make_unique(64); + for (int i = 0; i < 64; ++i) + values[i] = i; + return values[63] == 63 ? 0 : 1; +} diff --git a/tests/Memory_Callbacks.cpp b/tests/Memory_Callbacks.cpp new file mode 100644 index 0000000..b345c73 --- /dev/null +++ b/tests/Memory_Callbacks.cpp @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "../Renderive/base/Memory.h" +namespace { +struct Callback_State { + std::atomic allocation_count{}; + std::atomic deallocation_count{}; + std::atomic_bool alignment_error{}; + std::atomic_bool deallocated_off_main_thread{}; + std::thread::id main_thread; +}; +Callback_State callback_state; +void* callback_allocate(void* context, std::size_t size, std::size_t alignment) { + auto* state = static_cast(context); + void* result{}; + try { + result = std::pmr::new_delete_resource()->allocate(size, alignment); + } + catch (...) { + return nullptr; + } + state->allocation_count.fetch_add(1, std::memory_order_relaxed); + if (reinterpret_cast(result) % alignment) + state->alignment_error.store(true, std::memory_order_relaxed); + return result; +} +void callback_deallocate(void* context, void* address, std::size_t size, std::size_t alignment) { + auto* state = static_cast(context); + state->deallocation_count.fetch_add(1, std::memory_order_relaxed); + if (std::this_thread::get_id() != state->main_thread) + state->deallocated_off_main_thread.store(true, std::memory_order_relaxed); + std::pmr::new_delete_resource()->deallocate(address, size, alignment); +} +struct Payload { + std::uint64_t value{}; +}; +struct Large_Payload { + std::array data{}; +}; +} +int main() { + callback_state.main_thread = std::this_thread::get_id(); + renderive::Memory_Callbacks invalid_callbacks; + invalid_callbacks.context = &callback_state; + invalid_callbacks.allocate = callback_allocate; + try { + renderive::set_memory_callbacks(invalid_callbacks); + return 1; + } + catch (const std::invalid_argument&) {} + invalid_callbacks.allocate = nullptr; + invalid_callbacks.deallocate = callback_deallocate; + try { + renderive::set_memory_callbacks(invalid_callbacks); + return 2; + } + catch (const std::invalid_argument&) {} + renderive::Memory_Callbacks callbacks; + callbacks.context = &callback_state; + callbacks.allocate = callback_allocate; + callbacks.deallocate = callback_deallocate; + renderive::set_memory_callbacks(callbacks); + try { + renderive::set_memory_callbacks(callbacks); + return 3; + } + catch (const std::logic_error&) {} + { + std::pmr::vector values(renderive::memory_resource()); + values.resize(8192, 11); + auto owner = renderive::make_shared(); + auto large_owner = renderive::make_shared(); + owner->value = values.front(); + std::thread release_thread([owner = std::move(owner), large_owner = std::move(large_owner)]() mutable { + owner.reset(); + large_owner.reset(); + }); + release_thread.join(); + } + renderive::release_unused_memory(); + renderive::Memory_Stats stats = renderive::memory_stats(); + if (callback_state.alignment_error.load(std::memory_order_relaxed)) + return 4; + if (!callback_state.allocation_count.load(std::memory_order_relaxed)) + return 5; + if (stats.upstream_allocation_count != callback_state.allocation_count.load(std::memory_order_relaxed)) + return 6; + if (!callback_state.deallocation_count.load(std::memory_order_relaxed) || !callback_state.deallocated_off_main_thread.load(std::memory_order_relaxed)) + return 7; + renderive::shutdown_memory(); + if (renderive::memory_stats().upstream_current_bytes) + return 8; + return 0; +} diff --git a/tests/Memory_Data_Path_Stress.cpp b/tests/Memory_Data_Path_Stress.cpp new file mode 100644 index 0000000..7d9e920 --- /dev/null +++ b/tests/Memory_Data_Path_Stress.cpp @@ -0,0 +1,173 @@ +#include +#include +#include +#include +#include +#include +#include +#include "../Renderive/Axis/Axis.h" +#include "../Renderive/Axis/Frequency_Axis.h" +#include "../Renderive/Axis/Time_Axis.h" +#include "../Renderive/Axis/Time_Axis_p.h" +#include "../Renderive/architecture/Plot.h" +#include "../Renderive/architecture/Plot_p.h" +#include "../Renderive/base/Memory.h" +#include "../Renderive/plottable/Audio_Frequency.h" +#include "../Renderive/plottable/Audio_Frequency_p.h" +#include "../Renderive/plottable/Planisphere.h" +#include "../Renderive/plottable/Planisphere_p.h" +#include "../Renderive/plottable/Waterfall.h" +#include "../Renderive/plottable/Waterfall_p.h" +namespace { +void process_events(int milliseconds) { + QElapsedTimer timer; + timer.start(); + while (timer.elapsed() < milliseconds) { + QApplication::processEvents(QEventLoop::AllEvents, 10); + QThread::msleep(1); + } +} +std::vector make_frequency_data(int size, int frame) { + std::vector data(size); + for (int i = 0; i < size; ++i) + data[i] = -90.0 + 30.0 * std::sin(static_cast(i + frame) * 0.017); + return data; +} +bool wait_ready(const std::shared_ptr& waterfall, const std::shared_ptr& time_axis, const std::shared_ptr& audio, const std::shared_ptr& planisphere) { + QElapsedTimer timer; + timer.start(); + while (timer.elapsed() < 3000) { + QApplication::processEvents(QEventLoop::AllEvents, 10); + if (waterfall->ok() && time_axis->ok() && audio->ok() && planisphere->ok()) + return true; + QThread::msleep(1); + } + return false; +} +bool wait_idle(renderive::Plot& plot) { + QElapsedTimer timer; + timer.start(); + while (timer.elapsed() < 3000) { + QApplication::processEvents(QEventLoop::AllEvents, 10); + if (!plot.is_rendering() && !plot.d->active_render_tasks.load(std::memory_order_acquire)) + return true; + QThread::msleep(1); + } + return false; +} +int stress_data_paths() { + renderive::Plot plot; + plot.init(); + plot.resize(720, 420); + auto data_node = plot.create_renderable_node(plot.get_root_renderable(), "Data_Renderable"); + auto axis_node = plot.create_renderable_node(plot.get_root_renderable(), "Axis_Renderable"); + auto frequency_axis = renderive::Frequency_Axis::Builder(axis_node, Qt::Horizontal).set_pixel_size(700).set_coord_range({0.0, 100.0}).build(); + auto time_axis = renderive::Time_Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(400).set_time_point_size(32).build(); + auto value_axis = renderive::Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(400).set_coord_range({-120.0, 0.0}).build(); + auto i_axis = renderive::Axis::Builder(axis_node, Qt::Horizontal).set_pixel_size(700).set_coord_range({-2.0, 2.0}).build(); + auto q_axis = renderive::Axis::Builder(axis_node, Qt::Vertical).set_pixel_size(400).set_coord_range({-2.0, 2.0}).build(); + auto waterfall = renderive::Waterfall::Builder(data_node, frequency_axis, time_axis).set_frequency_range({0.0, 100.0}).set_power_range({-120.0, 0.0}).set_frequency_point_size(64).build(); + auto audio = renderive::Audio_Frequency::Builder(data_node, time_axis, value_axis).set_time_point_size(32).set_key_range({-120.0, 0.0}).build(); + auto planisphere = renderive::Planisphere::Builder(data_node, i_axis, q_axis).set_i_range({-2.0, 2.0}).set_q_range({-2.0, 2.0}).set_continue_millisecond(500).build(); + plot.show(); + plot.start_render(240); + if (!wait_ready(waterfall, time_axis, audio, planisphere)) + return 1; + const int frequency_sizes[] = {64, 512, 8192, 8193, 128}; + const int time_sizes[] = {32, 96, 48, 64, 24}; + const renderive::Waterfall_Update_Mode update_modes[] = { + renderive::Waterfall_Update_Mode::Batch, + renderive::Waterfall_Update_Mode::Single_Line, + renderive::Waterfall_Update_Mode::Timed_Latest, + renderive::Waterfall_Update_Mode::Single_Line, + renderive::Waterfall_Update_Mode::Batch + }; + for (int stage = 0; stage < 5; ++stage) { + waterfall->set_frequency_point_size(frequency_sizes[stage]); + waterfall->set_update_mode(update_modes[stage]); + waterfall->set_update_interval_ms(2); + time_axis->set_time_point_size(time_sizes[stage]); + audio->set_time_point_size(time_sizes[stage]); + process_events(100); + std::unordered_map expected_frequency; + QTime expected_time; + double expected_power{}; + QPointF expected_position; + for (int frame = 0; frame < time_sizes[stage] * 2; ++frame) { + std::vector frequency_data = make_frequency_data(frequency_sizes[stage], frame); + expected_time = QTime::fromMSecsSinceStartOfDay((stage * 10000 + frame * 4) % 86400000); + int tick = time_axis->give_data(expected_time); + waterfall->give_data(tick, frequency_data); + expected_frequency[tick] = frequency_data.front(); + expected_power = frequency_data[frame % frequency_data.size()]; + audio->give_data(tick, expected_power); + double phase = static_cast(frame) * 0.13; + expected_position = {1.5 * std::cos(phase), 1.5 * std::sin(phase)}; + planisphere->give_data(expected_position); + if ((frame & 3) == 3) + process_events(8); + } + process_events(60); + plot.pause_render(); + if (!wait_idle(plot)) + return 2; + auto* waterfall_data = waterfall->d(); + if (waterfall_data->ring_buffer.col_count != frequency_sizes[stage] || waterfall_data->ring_buffer.row_count != time_sizes[stage]) + return 3; + if (waterfall_data->image.width() != frequency_sizes[stage] || waterfall_data->image.height() != time_sizes[stage]) + return 4; + int row_count = waterfall_data->ring_buffer.snapshot(); + int previous_tick{}; + for (int row = 0; row < row_count; ++row) { + int tick = waterfall_data->ring_buffer.tick(row); + auto expected = expected_frequency.find(tick); + if (expected == expected_frequency.end()) + return 5; + if (std::abs(waterfall_data->ring_buffer.frequency_data(row, 0, 0)[0] - expected->second) > 0.000001) + return 6; + if (row && tick >= previous_tick) + return 7; + previous_tick = tick; + } + auto* time_data = time_axis->d(); + if (time_data->time_ticker.time_point_size != time_sizes[stage] || time_data->time_snapshot.size() != static_cast(time_sizes[stage])) + return 8; + if (!time_data->time_data_count || time_data->time_snapshot[time_data->time_data_count - 1] != expected_time) + return 9; + int lower = qMin(time_data->time_ticker.lower, time_data->time_ticker.upper()); + int upper = qMax(time_data->time_ticker.lower, time_data->time_ticker.upper()); + for (const renderive::Time_Tick& tick : time_data->data) { + if (tick.tick < lower || tick.tick > upper) + return 10; + } + auto* audio_data = audio->d(); + if (audio_data->point_count != time_sizes[stage] || audio_data->power_snapshot.size() != static_cast(time_sizes[stage])) + return 11; + if (!audio_data->data_count || std::abs(audio_data->power_snapshot[audio_data->data_count - 1] - expected_power) > 0.000001) + return 12; + auto* planisphere_data = planisphere->d(); + if (planisphere_data->data_list.empty()) + return 13; + QPointF actual_position = planisphere_data->data_list.back().pos; + if (std::abs(actual_position.x() - expected_position.x()) > 0.000001 || std::abs(actual_position.y() - expected_position.y()) > 0.000001) + return 14; + plot.start_render(240); + } + for (int frame = 0; frame < 180; ++frame) { + planisphere->set_continue_millisecond(500 + (frame & 1)); + process_events(8); + } + plot.pause_render(); + if (!wait_idle(plot)) + return 15; + if (!planisphere->d()->data_list.empty()) + return 16; + renderive::Memory_Stats stats = renderive::memory_stats(); + return stats.upstream_allocation_count && stats.upstream_peak_bytes ? 0 : 17; +} +} +int main(int argc, char** argv) { + QApplication app(argc, argv); + renderive::start_render_scheduler(); + return stress_data_paths(); +} diff --git a/tests/Memory_Default.cpp b/tests/Memory_Default.cpp new file mode 100644 index 0000000..104ebf0 --- /dev/null +++ b/tests/Memory_Default.cpp @@ -0,0 +1,25 @@ +#include +#include +#include +#include "../Renderive/base/Memory.h" +namespace { +struct Payload { + std::uint64_t value{}; +}; +} +int main() { + std::pmr::vector values(renderive::memory_resource()); + values.resize(4096, 7); + auto owner = renderive::make_shared(); + owner->value = values.front(); + if (owner->value != 7) + return 1; + std::thread release_thread([owner = std::move(owner)]() mutable { + owner.reset(); + }); + release_thread.join(); + renderive::Memory_Stats stats = renderive::memory_stats(); + if (!stats.upstream_allocation_count || !stats.upstream_current_bytes || !stats.upstream_peak_bytes) + return 2; + return 0; +} diff --git a/tests/Memory_Failure.cpp b/tests/Memory_Failure.cpp new file mode 100644 index 0000000..0b5015a --- /dev/null +++ b/tests/Memory_Failure.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include "../Renderive/base/Memory.h" +#include "../Renderive/base/Frame_Memory.h" +#include "Core/Base/RingBuffer.hpp" +namespace { +struct Callback_State { + std::atomic_bool fail{true}; +}; +Callback_State callback_state; +void* callback_allocate(void* context, std::size_t size, std::size_t alignment) { + auto* state = static_cast(context); + if (state->fail.load(std::memory_order_relaxed)) + return nullptr; + return std::pmr::new_delete_resource()->allocate(size, alignment); +} +void callback_deallocate(void*, void* address, std::size_t size, std::size_t alignment) { + std::pmr::new_delete_resource()->deallocate(address, size, alignment); +} +} +int main() { + renderive::Memory_Callbacks callbacks; + callbacks.context = &callback_state; + callbacks.allocate = callback_allocate; + callbacks.deallocate = callback_deallocate; + callback_state.fail.store(false, std::memory_order_relaxed); + renderive::set_memory_callbacks(callbacks); + std::pmr::memory_resource* resource = renderive::memory_resource(); + callback_state.fail.store(true, std::memory_order_relaxed); + bool direct_failure_caught = false; + try { + void* unexpected = resource->allocate(128 * 1024); + resource->deallocate(unexpected, 128 * 1024); + } + catch (const std::bad_alloc&) { + direct_failure_caught = true; + } + callback_state.fail.store(false, std::memory_order_relaxed); + std::pmr::vector values(resource); + callback_state.fail.store(true, std::memory_order_relaxed); + bool container_failure_caught = false; + try { + values.resize(1024 * 1024); + } + catch (const std::bad_alloc&) { + container_failure_caught = true; + } + callback_state.fail.store(false, std::memory_order_relaxed); + { + renderive::Frame_Arena arena(64 * 1024); + std::pmr::vector scratch(arena.resource()); + scratch.resize(16 * 1024); + } + callback_state.fail.store(true, std::memory_order_relaxed); + bool frame_failure_caught = false; + try { + renderive::Frame_Arena arena(1024 * 1024); + std::pmr::vector scratch(arena.resource()); + scratch.resize(1024 * 1024); + } + catch (const std::bad_alloc&) { + frame_failure_caught = true; + } + bool ring_buffer_failure_caught = false; + try { + Psc::RingBuffer_ST buffer(renderive::memory_resource()); + buffer.init(1024 * 1024); + } + catch (const std::bad_alloc&) { + ring_buffer_failure_caught = true; + } + return direct_failure_caught && container_failure_caught && frame_failure_caught && ring_buffer_failure_caught ? 0 : 1; +} diff --git a/tests/Memory_Late_Config.cpp b/tests/Memory_Late_Config.cpp new file mode 100644 index 0000000..4a71add --- /dev/null +++ b/tests/Memory_Late_Config.cpp @@ -0,0 +1,24 @@ +#include +#include "../Renderive/architecture/Plot.h" +#include "../Renderive/base/Memory.h" +namespace { +void* callback_allocate(void*, std::size_t size, std::size_t alignment) { + return std::pmr::new_delete_resource()->allocate(size, alignment); +} +void callback_deallocate(void*, void* address, std::size_t size, std::size_t alignment) { + std::pmr::new_delete_resource()->deallocate(address, size, alignment); +} +} +int main() { + renderive::start_render_scheduler(); + renderive::Memory_Callbacks callbacks; + callbacks.allocate = callback_allocate; + callbacks.deallocate = callback_deallocate; + try { + renderive::set_memory_callbacks(callbacks); + } + catch (const std::logic_error&) { + return 0; + } + return 1; +} diff --git a/tests/Memory_Plot_Late_Config.cpp b/tests/Memory_Plot_Late_Config.cpp new file mode 100644 index 0000000..a0ca51e --- /dev/null +++ b/tests/Memory_Plot_Late_Config.cpp @@ -0,0 +1,28 @@ +#include +#include +#include "../Renderive/architecture/Plot.h" +#include "../Renderive/base/Memory.h" +namespace { +void* callback_allocate(void*, std::size_t size, std::size_t alignment) { + return std::pmr::new_delete_resource()->allocate(size, alignment); +} +void callback_deallocate(void*, void* address, std::size_t size, std::size_t alignment) { + std::pmr::new_delete_resource()->deallocate(address, size, alignment); +} +} +int main(int argc, char** argv) { + QApplication app(argc, argv); + renderive::start_render_scheduler(); + renderive::Plot plot; + plot.init(); + renderive::Memory_Callbacks callbacks; + callbacks.allocate = callback_allocate; + callbacks.deallocate = callback_deallocate; + try { + renderive::set_memory_callbacks(callbacks); + } + catch (const std::logic_error&) { + return 0; + } + return 1; +} diff --git a/tests/Memory_Pool_Benchmark.cpp b/tests/Memory_Pool_Benchmark.cpp new file mode 100644 index 0000000..18c9556 --- /dev/null +++ b/tests/Memory_Pool_Benchmark.cpp @@ -0,0 +1,208 @@ +#include +#include +#ifdef _WIN32 +#include +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../Renderive/architecture/Renderable.h" +namespace { +class Counting_Resource final : public std::pmr::memory_resource { +public: + std::uint64_t allocation_count{}; + std::uint64_t deallocation_count{}; + std::uint64_t current_bytes{}; + std::uint64_t peak_bytes{}; +private: + void* do_allocate(std::size_t size, std::size_t alignment) override { + void* result = std::pmr::new_delete_resource()->allocate(size, alignment); + ++allocation_count; + current_bytes += size; + peak_bytes = std::max(peak_bytes, current_bytes); + return result; + } + void do_deallocate(void* address, std::size_t size, std::size_t alignment) override { + std::pmr::new_delete_resource()->deallocate(address, size, alignment); + ++deallocation_count; + current_bytes -= size; + } + bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { + return this == &other; + } +}; +struct Benchmark_Waterfall_Row { + int tick{}; + std::vector frequency; +}; +struct Benchmark_Time_Tick { + QTime time; + int tick{}; +}; +struct Benchmark_Result { + double renderable_ms{}; + double waterfall_ms{}; + double time_audio_ms{}; +}; +volatile std::uint64_t benchmark_sink{}; +template +std::shared_ptr make_two_allocation_shared(std::pmr::memory_resource* resource) { + std::pmr::polymorphic_allocator object_allocator(resource); + T* object = object_allocator.allocate(1); + try { + std::allocator_traits::construct(object_allocator, object); + } + catch (...) { + object_allocator.deallocate(object, 1); + throw; + } + auto deleter = [object_allocator](T* value) mutable { + std::allocator_traits::destroy(object_allocator, value); + object_allocator.deallocate(value, 1); + }; + return std::shared_ptr(object, std::move(deleter), std::pmr::polymorphic_allocator(resource)); +} +template +double measure_milliseconds(Handler handler) { + auto begin = std::chrono::steady_clock::now(); + handler(); + auto end = std::chrono::steady_clock::now(); + return std::chrono::duration(end - begin).count(); +} +double benchmark_renderables(std::pmr::memory_resource* resource, bool two_allocations) { + return measure_milliseconds([resource, two_allocations]() { + std::pmr::vector> owners(resource); + owners.reserve(20000); + for (int i = 0; i < 20000; ++i) { + if (two_allocations) + owners.push_back(make_two_allocation_shared(resource)); + else + owners.push_back(std::allocate_shared(std::pmr::polymorphic_allocator(resource))); + } + benchmark_sink += owners.size(); + owners.clear(); + }); +} +double benchmark_waterfall(std::pmr::memory_resource* resource) { + static constexpr std::array frequency_sizes{64, 512, 8192, 8193, 128}; + static constexpr std::array time_sizes{32, 96, 48, 64, 24}; + return measure_milliseconds([resource]() { + for (int round = 0; round < 32; ++round) { + std::pmr::vector write_buffer(resource); + std::pmr::vector record_buffer(resource); + std::pmr::vector snapshot_buffer(resource); + std::pmr::vector row_buffer(resource); + std::pmr::vector pending_rows(resource); + for (std::size_t stage = 0; stage < frequency_sizes.size(); ++stage) { + std::size_t row_bytes = static_cast(frequency_sizes[stage]) * sizeof(double); + std::size_t record_bytes = row_bytes + sizeof(int); + write_buffer.resize(record_bytes); + record_buffer.resize(record_bytes); + snapshot_buffer.resize(record_bytes * static_cast(time_sizes[stage])); + row_buffer.resize(frequency_sizes[stage]); + pending_rows.clear(); + for (int row = 0; row < 8; ++row) { + Benchmark_Waterfall_Row value; + value.tick = row; + value.frequency.resize(frequency_sizes[stage]); + pending_rows.push_back(std::move(value)); + } + write_buffer.front() = static_cast(round); + row_buffer.front() = static_cast(round); + } + benchmark_sink += write_buffer.size() + snapshot_buffer.size() + pending_rows.size(); + } + }); +} +double benchmark_time_audio(std::pmr::memory_resource* resource) { + static constexpr std::array point_sizes{32, 256, 4096, 64}; + return measure_milliseconds([resource]() { + for (int round = 0; round < 64; ++round) { + std::pmr::vector time_snapshot(resource); + std::pmr::vector power_snapshot(resource); + std::pmr::list time_ticks(resource); + for (int size : point_sizes) { + time_snapshot.resize(size); + power_snapshot.resize(size); + for (int tick = 0; tick < size; ++tick) + time_ticks.push_front({QTime::fromMSecsSinceStartOfDay(tick), tick}); + while (time_ticks.size() > static_cast(size)) + time_ticks.pop_back(); + if (!power_snapshot.empty()) + power_snapshot.back() = static_cast(round); + } + benchmark_sink += time_snapshot.size() + power_snapshot.size() + time_ticks.size(); + } + }); +} +std::uint64_t peak_working_set_bytes() { +#ifdef _WIN32 + PROCESS_MEMORY_COUNTERS_EX counters{}; + counters.cb = sizeof(counters); + GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast(&counters), sizeof(counters)); + return counters.PeakWorkingSetSize; +#else + rusage usage{}; + getrusage(RUSAGE_SELF, &usage); + return static_cast(usage.ru_maxrss) * 1024; +#endif +} +Benchmark_Result run_benchmark(std::pmr::memory_resource* resource, bool two_allocations) { + Benchmark_Result result; + result.renderable_ms = benchmark_renderables(resource, two_allocations); + result.waterfall_ms = benchmark_waterfall(resource); + result.time_audio_ms = benchmark_time_audio(resource); + return result; +} +} +int main(int argc, char** argv) { + if (argc != 2) + return 1; + std::string name = argv[1]; + Counting_Resource upstream; + std::unique_ptr pool; + std::pmr::memory_resource* resource = &upstream; + bool two_allocations = false; + if (name == "old") { + two_allocations = true; + } + else if (name == "default") { + pool = std::make_unique(&upstream); + resource = pool.get(); + } + else { + std::pmr::pool_options options; + if (name == "64x64k") { + options.max_blocks_per_chunk = 64; + options.largest_required_pool_block = 64 * 1024; + } + else if (name == "32x32k") { + options.max_blocks_per_chunk = 32; + options.largest_required_pool_block = 32 * 1024; + } + else if (name == "64x128k") { + options.max_blocks_per_chunk = 64; + options.largest_required_pool_block = 128 * 1024; + } + else { + return 2; + } + pool = std::make_unique(options, &upstream); + resource = pool.get(); + } + Benchmark_Result result = run_benchmark(resource, two_allocations); + std::cout << name << ',' << result.renderable_ms << ',' << result.waterfall_ms << ',' << result.time_audio_ms << ',' << upstream.allocation_count << ',' << upstream.current_bytes << ',' << upstream.peak_bytes << ',' << peak_working_set_bytes() << '\n'; + return 0; +} diff --git a/tests/Update_Completion.cpp b/tests/Update_Completion.cpp new file mode 100644 index 0000000..c19b0a6 --- /dev/null +++ b/tests/Update_Completion.cpp @@ -0,0 +1,46 @@ +#include "../Renderive/architecture/Update_Completion.h" +#include "asio.hpp" +#include +#include +int main() { + asio::io_context io; + auto state = renderive::make_update_state(); + renderive::Update_Ticket ticket(state); + std::atomic_int rendered_count{}; + std::atomic_int input_count{}; + ticket.async_wait(renderive::Update_Stage::Rendered, asio::bind_executor(io.get_executor(), [&](std::error_code error, renderive::Update_Outcome outcome) { + if (error || outcome != renderive::Update_Outcome::Completed) + std::terminate(); + rendered_count.fetch_add(1, std::memory_order_relaxed); + })); + state->complete_until(renderive::Update_Stage::Input_Released); + io.poll(); + if (rendered_count.load(std::memory_order_relaxed) != 0) + return 1; + state->complete_until(renderive::Update_Stage::Rendered); + io.restart(); + io.run(); + if (rendered_count.load(std::memory_order_relaxed) != 1) + return 2; + ticket.async_wait(renderive::Update_Stage::Input_Released, asio::bind_executor(io.get_executor(), [&](std::error_code error, renderive::Update_Outcome outcome) { + if (error || outcome != renderive::Update_Outcome::Completed) + std::terminate(); + input_count.fetch_add(1, std::memory_order_relaxed); + })); + io.restart(); + io.run(); + if (input_count.load(std::memory_order_relaxed) != 1) + return 3; + auto cancelled_state = renderive::make_update_state(); + renderive::Update_Ticket cancelled_ticket(cancelled_state); + std::atomic_int cancel_count{}; + cancelled_ticket.async_wait(renderive::Update_Stage::Presented, asio::bind_executor(io.get_executor(), [&](std::error_code error, renderive::Update_Outcome outcome) { + if (!error || outcome != renderive::Update_Outcome::Cancelled) + std::terminate(); + cancel_count.fetch_add(1, std::memory_order_relaxed); + })); + cancelled_state->complete_all(std::make_error_code(std::errc::operation_canceled), renderive::Update_Outcome::Cancelled); + io.restart(); + io.run(); + return cancel_count.load(std::memory_order_relaxed) == 1 ? 0 : 4; +}