Files
plotter/tests/CMakeLists.txt
T
Martino Ferrari 0ac5d47fa6 perf(data): absolute-space pyramid so acquire() is O(produced), not O(window)
Frame cost was O(capacity x levels) once the ring saturated: acquire() saw the
window base move (it always moves once the ring wraps), cleared the pyramid and
re-appended all 1,048,576 retained samples, and append() walked every live level
with no early stop -- 13,647,936 bucket merges and 73.8 ms per frame at a
1M-sample window, scaling with the window, which is what capped the 1 MS/s demo
at 32-58 ms/frame while it was still filling.

Index the pyramid in absolute bucket space instead. Level l bucket k covers the
samples [k * (kBucket << l), (k + 1) * (kBucket << l)), so a bucket's span never
depends on where the window starts and the window can slide over the aggregate
instead of forcing it to be rebuilt. Each level keeps its complete buckets in a
deque plus the absolute base of its first entry; append() folds a sample into the
single in-progress level-0 bucket and, only when that bucket completes, cascades
upward, stopping at the first level that gains no new entry; a new drop_before()
slides the window by discarding leading buckets and remembers where the window
begins. acquire() is now: drop to the new base, then append exactly what was
produced -- 256 merges and 0.034 ms per frame for 8192 new samples, independent
of the window, and memory bounded by the window (32,767 entries after 50M samples
through a 1M ring, the same as after 10M). A producer that laps the consumer by
more than capacity() between two acquires restarts the window rather than
splicing a gap.

Interface: `level(l) -> std::span<const MinMax>` cannot survive a ring and is
replaced by level_base/level_size/bucket in absolute bucket coordinates, plus
window_base/window_end/drop_before. The *_buckets queries stay in window
coordinates (index 0 = oldest retained sample), so Series keeps its window
semantics for callers; its exactness split now aligns in absolute space
(window_base() + the ceil/floor) because the bucket grid is absolute. The sums
move from per-level prefix sums to per-bucket ones -- the only representation a
sliding front can keep exact -- and StreamSeries::y_bounds() becomes
reduce(0, size()), exact in O(log n + kBucket) per frame instead of a scan of the
window. The pyramid owns the published window now, so there is no second copy of
base/end to keep in step, and gather() emits only buckets that are both complete
and wholly retained.

Tests: test-only merge counters (mirroring PLOT_PYRAMID_COUNT_COVERS, absent from
every non-test build) pin an acquire proportional to production and independent
of capacity, an append cost amortised over 16 live levels, and bounded memory;
brute-force checks pin absolute bucket aggregates across slides, sliding-window
reduce/sum/sumsq over unaligned ranges, and the level-1 gather mapping. The demo
now draws 946 samples/px at 1.03 ms/frame (967 fps) where the saturated window
cost 54.96 ms/frame (18 fps).
2026-09-28 00:05:15 +02:00

50 lines
1.7 KiB
CMake

# EGL powers the surfaceless headless context the GL tests assert pixels
# against. It is a *test-only* dependency: plot_core never links or includes it,
# so a machine without EGL can still build the library (-DPLOT_BUILD_TESTS=OFF).
# Prefer CMake's own module, fall back to pkg-config, and fail loudly if neither
# can supply EGL rather than silently dropping the GL tests.
find_package(EGL QUIET)
if(NOT EGL_FOUND)
find_package(PkgConfig QUIET)
if(PkgConfig_FOUND)
pkg_check_modules(EGL_PC QUIET IMPORTED_TARGET egl)
endif()
endif()
if(TARGET EGL::EGL)
set(plot_egl_library EGL::EGL)
elseif(TARGET PkgConfig::EGL_PC)
set(plot_egl_library PkgConfig::EGL_PC)
else()
message(FATAL_ERROR
"plot: PLOT_BUILD_TESTS=ON requires EGL (EGL/egl.h plus libEGL) for the headless "
"OpenGL context used by the GL tests. Install it (e.g. libegl-dev, libegl1-mesa-dev "
"or mesa-libEGL-devel) or configure with -DPLOT_BUILD_TESTS=OFF.")
endif()
find_package(Threads REQUIRED) # std::thread in the push/read race test
add_executable(plot_tests
main.cpp
test_transform.cpp
test_gl_context.cpp
test_primitives.cpp
test_ring_buffer.cpp
test_minmax_pyramid.cpp
test_series.cpp
test_ticks.cpp
test_measure.cpp
test_plot.cpp
test_render_smoke.cpp
test_controller.cpp
egl_headless.cpp
)
target_link_libraries(plot_tests PRIVATE plot::core ${plot_egl_library} Threads::Threads)
# Same switches the library is built with: the cost tests read the counters
# behind them (see include/plot/data/detail/{cover,merge}_count.hpp).
target_compile_definitions(plot_tests PRIVATE PLOT_PYRAMID_COUNT_COVERS
PLOT_PYRAMID_COUNT_MERGES)
plot_apply_warnings(plot_tests)
add_test(NAME plot_tests COMMAND plot_tests)