From 7a5df8238a5fd1d1b0a7b3bae3e5b35d781a91ce Mon Sep 17 00:00:00 2001 From: rstein <r.steinhagen@gsi.de> Date: Tue, 14 Jan 2025 12:54:27 +0100 Subject: [PATCH] HistoryBuffer<T> flipped push_back<->push_front naming should (hopefully) simplify and provide a more intuitive the naming. ### Storage - Dynamic (`N == std::dynamic_extent`): uses `std::vector<T>` (size = 2 * capacity). - Fixed (`N != std::dynamic_extent`): uses `std::array<T, N * 2>`. ### Key Operations - `push_front(const T&)`: Add new item, drop oldest if full, index `[0]` is newest. - `push_back(const T&)`: Add new item, drop oldest if full, index `[0]` is oldest. - `pop_front()`, `pop_back()`: Remove from logical front/back of the ring. - `front()`, `back()`: Returns the first/last item in logical order. - `operator[](i)`, `at(i)`: Unchecked/checked access. - `resize(...)` (dynamic-only): Adjust capacity, preserving existing data. - `get_span(...)`: Obtain a contiguous view across wrap boundaries. ### Code Examples ```cpp gr::history_buffer<int, 5> hb_newest; // use push_front -> index[0] is newest hb_newest.push_front(1); // [1] hb_newest.push_front(2); // [2, 1] hb_newest.push_front(3); // [3, 2, 1] // => index[0] == 3, newest item gr::history_buffer<int, 5> hb_oldest; // use push_back -> index[0] is oldest hb_oldest.push_back(10); // [10] hb_oldest.push_back(20); // [10, 20] hb_oldest.push_back(30); // [10, 20, 30] // => index[0] == 10, oldest item hb_newest.pop_front(); // remove newest => now hb_newest: [2, 1] hb_oldest.pop_front(); // remove oldest => now hb_oldest: [20, 30] auto val = hb_newest.front(); // val == 2 auto last = hb_newest.back(); // last == 1 ``` Also added/extended te `HistoryBuffer<T>` performance benchmark Signed-off-by: rstein <r.steinhagen@gsi.de> Signed-off-by: Ralph J. Steinhagen <r.steinhagen@gsi.de> --- .../gnuradio-4.0/algorithm/SchmittTrigger.hpp | 6 +- .../algorithm/filter/FilterTool.hpp | 20 +++--- algorithm/test/qa_FilterTool.cpp | 2 +- .../include/gnuradio-4.0/basic/DataSink.hpp | 4 +- .../gnuradio-4.0/basic/StreamToDataSet.hpp | 6 +- .../include/gnuradio-4.0/basic/SyncBlock.hpp | 1 - .../electrical/PowerEstimators.hpp | 1 - .../filter/time_domain_filter.hpp | 14 ++-- .../gnuradio-4.0/testing/ImChartMonitor.hpp | 29 ++++---- core/benchmarks/bm_HistoryBuffer.cpp | 53 +++++++++++++-- core/include/gnuradio-4.0/HistoryBuffer.hpp | 66 ++++++++----------- core/include/gnuradio-4.0/Scheduler.hpp | 2 +- core/test/qa_buffer.cpp | 44 ++++++------- 13 files changed, 133 insertions(+), 115 deletions(-) diff --git a/algorithm/include/gnuradio-4.0/algorithm/SchmittTrigger.hpp b/algorithm/include/gnuradio-4.0/algorithm/SchmittTrigger.hpp index bcfadee3..5c1f53f9 100644 --- a/algorithm/include/gnuradio-4.0/algorithm/SchmittTrigger.hpp +++ b/algorithm/include/gnuradio-4.0/algorithm/SchmittTrigger.hpp @@ -103,7 +103,7 @@ struct SchmittTrigger { } if constexpr (Method == BASIC_LINEAR_INTERPOLATION) { - _historyBuffer.push_back(input); + _historyBuffer.push_front(input); if (_historyBuffer.size() < 2) { return EdgeDetection::NONE; } @@ -113,7 +113,7 @@ struct SchmittTrigger { auto computeEdgePosition = [&](const EdgeType& y1, const EdgeType& y2) -> std::pair<std::int32_t, EdgeType> { if (y1 == y2) { - return {static_cast<std::int32_t>(0), static_cast<EdgeType>(0)}; + return {static_cast<std::int32_t>(0U), static_cast<EdgeType>(0)}; } const EdgeType offset = (EdgeType(_offset) - y1) / (y2 - y1); std::int32_t intPart = static_cast<std::int32_t>(std::floor(gr::value(offset))); @@ -145,7 +145,7 @@ struct SchmittTrigger { } if constexpr (Method == LINEAR_INTERPOLATION) { - _historyBuffer.push_back(input); + _historyBuffer.push_front(input); if (_historyBuffer.size() < 2) { return EdgeDetection::NONE; diff --git a/algorithm/include/gnuradio-4.0/algorithm/filter/FilterTool.hpp b/algorithm/include/gnuradio-4.0/algorithm/filter/FilterTool.hpp index b146a798..b288ef04 100644 --- a/algorithm/include/gnuradio-4.0/algorithm/filter/FilterTool.hpp +++ b/algorithm/include/gnuradio-4.0/algorithm/filter/FilterTool.hpp @@ -124,35 +124,35 @@ template<typename T, std::size_t bufferSize, Form form = std::is_floating_point_ if constexpr (form == Form::DF_I) { // y[n] = b[0]·x[n] + b[1]·x[n-1] + … + b[N]·x[n-N] // - a[1]·y[n-1] - a[2]·y[n-2] - … - a[M]·y[n-M] - inputHistory.push_back(input); + inputHistory.push_front(input); T output = std::transform_reduce(execPolicy, b.cbegin(), b.cend(), inputHistory.cbegin(), static_cast<T>(0), std::plus<>(), std::multiplies<>()) // feed-forward path - std::transform_reduce(execPolicy, std::next(a.cbegin()), a.cend(), outputHistory.cbegin(), static_cast<T>(0), std::plus<>(), std::multiplies<>()); // feedback path - outputHistory.push_back(output); + outputHistory.push_front(output); return output; } else if constexpr (form == Form::DF_II) { // w[n] = x[n] - a[1]·w[n-1] - a[2]·w[n-2] - … - a[M]·w[n-M] // y[n] = b[0]·w[n] + b[1]·w[n-1] + … + b[N]·w[n-N] if (a.size() > 1) { const T w = input - std::transform_reduce(execPolicy, std::next(a.cbegin()), a.cend(), inputHistory.cbegin(), T{0}, std::plus<>(), std::multiplies<>()); - inputHistory.push_back(w); + inputHistory.push_front(w); return std::transform_reduce(execPolicy, b.cbegin(), b.cend(), inputHistory.cbegin(), T{0}, std::plus<>(), std::multiplies<>()); } else { - inputHistory.push_back(input); + inputHistory.push_front(input); return std::transform_reduce(execPolicy, b.cbegin(), b.cend(), inputHistory.cbegin(), T{0}, std::plus<>(), std::multiplies<>()); } } else if constexpr (form == Form::DF_I_TRANSPOSED) { // w_1[n] = x[n] - a[1]·w_2[n-1] - a[2]·w_2[n-2] - … - a[M]·w_2[n-M] // y[n] = b[0]·w_2[n] + b[1]·w_2[n-1] + … + b[N]·w_2[n-N] T v0 = input - std::transform_reduce(execPolicy, std::next(a.cbegin()), a.cend(), outputHistory.cbegin(), static_cast<T>(0), std::plus<>(), std::multiplies<>()); - outputHistory.push_back(v0); + outputHistory.push_front(v0); return std::transform_reduce(execPolicy, b.cbegin(), b.cend(), outputHistory.cbegin(), T{0}, std::plus<>(), std::multiplies<>()); } else if constexpr (form == Form::DF_II_TRANSPOSED) { // y[n] = b_0·f[n] + \sum_(k=1)^N(b_k·f[n−k] − a_k·y[n−k]) T output = b[0] * input // + std::transform_reduce(execPolicy, std::next(b.cbegin()), b.cend(), inputHistory.cbegin(), static_cast<T>(0), std::plus<>(), std::multiplies<>()) // - std::transform_reduce(execPolicy, std::next(a.cbegin()), a.cend(), outputHistory.cbegin(), static_cast<T>(0), std::plus<>(), std::multiplies<>()); - inputHistory.push_back(input); - outputHistory.push_back(output); + inputHistory.push_front(input); + outputHistory.push_front(output); return output; } else { static_assert(gr::meta::always_false<T>, "should not reach here"); @@ -276,12 +276,12 @@ struct Filter<UncertainValue<T>, bufferSize, form, execPolicy> { const auto& autocorrelationFunction = section.autoCorrelation; // Feed-forward path (uncorrelated uncertainties) - inputHistory.push_back(inputUncertainty * inputUncertainty); + inputHistory.push_front(inputUncertainty * inputUncertainty); TBaseType feedForwardUncertainty = std::transform_reduce(execPolicy, b.cbegin(), b.cend(), inputHistory.cbegin(), static_cast<TBaseType>(0), // std::plus<>(), [](TBaseType bVal, TBaseType sigma2) { return bVal * bVal * sigma2; }); if (a.size() <= 1 || autocorrelationFunction.empty()) { - outputHistory.push_back(feedForwardUncertainty); + outputHistory.push_front(feedForwardUncertainty); return feedForwardUncertainty; } @@ -296,7 +296,7 @@ struct Filter<UncertainValue<T>, bufferSize, form, execPolicy> { } TBaseType totalUncertainty = feedForwardUncertainty + feedbackUncertainty; - outputHistory.push_back(totalUncertainty); + outputHistory.push_front(totalUncertainty); return totalUncertainty; } diff --git a/algorithm/test/qa_FilterTool.cpp b/algorithm/test/qa_FilterTool.cpp index 86c928d0..1f4f52ca 100644 --- a/algorithm/test/qa_FilterTool.cpp +++ b/algorithm/test/qa_FilterTool.cpp @@ -839,7 +839,7 @@ const boost::ut::suite<"IIR & FIR Benchmarks"> filterBenchmarks = [] { gr::HistoryBuffer<T, 8> buffer; ::benchmark::benchmark<10>(fmt::format("HistoryBuffer<{}>", gr::meta::type_name<T>()), nSamples) = [&actualGain, &buffer, &yValues] { for (auto& yValue : yValues) { - buffer.push_back(yValue); + buffer.push_front(yValue); actualGain = std::max(actualGain, buffer[0]); } }; diff --git a/blocks/basic/include/gnuradio-4.0/basic/DataSink.hpp b/blocks/basic/include/gnuradio-4.0/basic/DataSink.hpp index 5497abef..f2c06703 100644 --- a/blocks/basic/include/gnuradio-4.0/basic/DataSink.hpp +++ b/blocks/basic/include/gnuradio-4.0/basic/DataSink.hpp @@ -591,7 +591,7 @@ This block type is mean for non-data set input streams. For input streams of typ listener->process(historyView, inData, tagData); } if (_history) { - _history->push_back_bulk(inData.begin(), inData.end()); + _history->push_front(inData); } } return work::Status::OK; @@ -609,7 +609,7 @@ This block type is mean for non-data set input streams. For input streams of typ auto new_history = gr::HistoryBuffer<T>(new_size); if (_history) { - new_history.push_back_bulk(_history->begin(), _history->end()); + new_history.push_front(_history->begin(), _history->end()); } _history = std::move(new_history); } diff --git a/blocks/basic/include/gnuradio-4.0/basic/StreamToDataSet.hpp b/blocks/basic/include/gnuradio-4.0/basic/StreamToDataSet.hpp index 0b93c4eb..c165b2f9 100644 --- a/blocks/basic/include/gnuradio-4.0/basic/StreamToDataSet.hpp +++ b/blocks/basic/include/gnuradio-4.0/basic/StreamToDataSet.hpp @@ -124,9 +124,7 @@ If multiple 'start' or 'stop' Tags arrive in a single merged tag, only one DataS throw gr::exception("n_pre must be <= output port CircularBuffer size"); } } - auto newBuffer = HistoryBuffer<T>(MIN_BUFFER_SIZE + std::get<gr::Size_t>(newSettings.at("n_pre"))); - newBuffer.push_back_bulk(_history); - _history = std::move(newBuffer); + _history.resize(MIN_BUFFER_SIZE + std::get<gr::Size_t>(newSettings.at("n_pre"))); } if constexpr (!streamOut) { @@ -321,7 +319,7 @@ If multiple 'start' or 'stop' Tags arrive in a single merged tag, only one DataS void copyInputSamplesToHistory(InputSpanLike auto& inSamples, std::size_t maxSamplesToCopy) { if (n_pre > 0) { const auto samplesToCopy = std::min(maxSamplesToCopy, inSamples.size()); - _history.push_back_bulk(inSamples.begin(), std::next(inSamples.begin(), static_cast<std::ptrdiff_t>(samplesToCopy))); + _history.push_front(inSamples.begin(), std::next(inSamples.begin(), static_cast<std::ptrdiff_t>(samplesToCopy))); } } diff --git a/blocks/basic/include/gnuradio-4.0/basic/SyncBlock.hpp b/blocks/basic/include/gnuradio-4.0/basic/SyncBlock.hpp index 2d94cb30..0fd75c87 100644 --- a/blocks/basic/include/gnuradio-4.0/basic/SyncBlock.hpp +++ b/blocks/basic/include/gnuradio-4.0/basic/SyncBlock.hpp @@ -2,7 +2,6 @@ #define GNURADIO_SYNC_BLOCK_HPP #include <gnuradio-4.0/Block.hpp> -#include <gnuradio-4.0/HistoryBuffer.hpp> namespace gr::basic { diff --git a/blocks/electrical/include/gnuradio-4.0/electrical/PowerEstimators.hpp b/blocks/electrical/include/gnuradio-4.0/electrical/PowerEstimators.hpp index 4ecca564..ae37f74f 100644 --- a/blocks/electrical/include/gnuradio-4.0/electrical/PowerEstimators.hpp +++ b/blocks/electrical/include/gnuradio-4.0/electrical/PowerEstimators.hpp @@ -9,7 +9,6 @@ #include <gnuradio-4.0/Block.hpp> #include <gnuradio-4.0/BlockRegistry.hpp> -#include <gnuradio-4.0/HistoryBuffer.hpp> #include <gnuradio-4.0/algorithm/filter/FilterTool.hpp> #include <gnuradio-4.0/meta/UncertainValue.hpp> diff --git a/blocks/filter/include/gnuradio-4.0/filter/time_domain_filter.hpp b/blocks/filter/include/gnuradio-4.0/filter/time_domain_filter.hpp index 8eaa65a0..f5c3c413 100644 --- a/blocks/filter/include/gnuradio-4.0/filter/time_domain_filter.hpp +++ b/blocks/filter/include/gnuradio-4.0/filter/time_domain_filter.hpp @@ -42,7 +42,7 @@ H(z) = b[0] + b[1]*z^-1 + b[2]*z^-2 + ... + b[N]*z^-N } constexpr T processOne(T input) noexcept { - inputHistory.push_back(input); + inputHistory.push_front(input); return std::transform_reduce(std::execution::unseq, b.cbegin(), b.cend(), inputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{}); } }; @@ -85,32 +85,32 @@ a are the feedback coefficients if constexpr (form == IIRForm::DF_I) { // y[n] = b[0] * x[n] + b[1] * x[n-1] + ... + b[N] * x[n-N] // - a[1] * y[n-1] - a[2] * y[n-2] - ... - a[M] * y[n-M] - inputHistory.push_back(input); + inputHistory.push_front(input); const T feedforward = std::transform_reduce(std::execution::unseq, b.cbegin(), b.cend(), inputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{}); const T feedback = std::transform_reduce(std::execution::unseq, a.cbegin() + 1, a.cend(), outputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{}); const T output = feedforward - feedback; - outputHistory.push_back(output); + outputHistory.push_front(output); return output; } else if constexpr (form == IIRForm::DF_II) { // w[n] = x[n] - a[1] * w[n-1] - a[2] * w[n-2] - ... - a[M] * w[n-M] // y[n] = b[0] * w[n] + b[1] * w[n-1] + ... + b[N] * w[n-N] const T w = input - std::transform_reduce(std::execution::unseq, a.cbegin() + 1, a.cend(), inputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{}); - inputHistory.push_back(w); + inputHistory.push_front(w); return std::transform_reduce(std::execution::unseq, b.cbegin(), b.cend(), inputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{}); } else if constexpr (form == IIRForm::DF_I_TRANSPOSED) { // w_1[n] = x[n] - a[1] * w_2[n-1] - a[2] * w_2[n-2] - ... - a[M] * w_2[n-M] // y[n] = b[0] * w_2[n] + b[1] * w_2[n-1] + ... + b[N] * w_2[n-N] const T v0 = input - std::transform_reduce(std::execution::unseq, a.cbegin() + 1, a.cend(), outputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{}); - outputHistory.push_back(v0); + outputHistory.push_front(v0); return std::transform_reduce(std::execution::unseq, b.cbegin(), b.cend(), outputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{}); } else if constexpr (form == IIRForm::DF_II_TRANSPOSED) { // y[n] = b_0 * f[n] + Σ (b_k * f[n−k] − a_k * y[n−k]) for k = 1 to N const T output = b[0] * input + std::transform_reduce(std::execution::unseq, b.cbegin() + 1, b.cend(), inputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{}) - std::transform_reduce(std::execution::unseq, a.cbegin() + 1, a.cend(), outputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{}); - inputHistory.push_back(input); - outputHistory.push_back(output); + inputHistory.push_front(input); + outputHistory.push_front(output); return output; } } diff --git a/blocks/testing/include/gnuradio-4.0/testing/ImChartMonitor.hpp b/blocks/testing/include/gnuradio-4.0/testing/ImChartMonitor.hpp index d93a5e3c..8a0c908d 100644 --- a/blocks/testing/include/gnuradio-4.0/testing/ImChartMonitor.hpp +++ b/blocks/testing/include/gnuradio-4.0/testing/ImChartMonitor.hpp @@ -24,9 +24,9 @@ struct ImChartMonitor : public Block<ImChartMonitor<T>, BlockingIO<false>, Drawa GR_MAKE_REFLECTABLE(ImChartMonitor, in, sample_rate, signal_name); - HistoryBuffer<T> _historyBufferX{1000}; - HistoryBuffer<T> _historyBufferY{1000}; - HistoryBuffer<Tag> _historyBufferTags{1000}; + HistoryBuffer<T> _historyBufferX{1000UZ}; + HistoryBuffer<T> _historyBufferY{1000UZ}; + HistoryBuffer<T> _historyBufferTags{1000UZ}; void start() { fmt::println("started sink {} aka. '{}'", this->unique_name, this->name); @@ -44,11 +44,14 @@ struct ImChartMonitor : public Block<ImChartMonitor<T>, BlockingIO<false>, Drawa _historyBufferY.push_back(input); if (this->inputTagsPresent()) { // received tag - _historyBufferTags.push_back(this->mergedInputTag()); - _historyBufferTags[1].index = 0; + _historyBufferTags.push_back(_historyBufferY.back()); this->_mergedInputTag.map.clear(); // TODO: provide proper API for clearing tags } else { - _historyBufferTags.push_back(Tag(0UZ, property_map())); + if constexpr (std::is_floating_point_v<T>) { + _historyBufferTags.push_back(std::numeric_limits<T>::quiet_NaN()); + } else { + _historyBufferTags.push_back(std::numeric_limits<T>::lowest()); + } } } @@ -66,16 +69,6 @@ struct ImChartMonitor : public Block<ImChartMonitor<T>, BlockingIO<false>, Drawa gr::graphs::resetView(); } - // create reversed copies -- draw(...) expects std::ranges::input_range -> - std::vector<T> reversedX(_historyBufferX.rbegin(), _historyBufferX.rend()); - std::vector<T> reversedY(_historyBufferY.rbegin(), _historyBufferY.rend()); - std::vector<T> reversedTag(_historyBufferX.size()); - if constexpr (std::is_floating_point_v<T>) { - std::transform(_historyBufferTags.rbegin(), _historyBufferTags.rend(), _historyBufferY.rbegin(), reversedTag.begin(), [](const Tag& tag, const T& yValue) { return tag.map.empty() ? std::numeric_limits<T>::quiet_NaN() : yValue; }); - } else { - std::transform(_historyBufferTags.rbegin(), _historyBufferTags.rend(), _historyBufferY.rbegin(), reversedTag.begin(), [](const Tag& tag, const T& yValue) { return tag.map.empty() ? std::numeric_limits<T>::lowest() : yValue; }); - } - auto adjustRange = [](T min, T max) { min = std::min(min, T(0)); max = std::max(max, T(0)); @@ -84,8 +77,8 @@ struct ImChartMonitor : public Block<ImChartMonitor<T>, BlockingIO<false>, Drawa }; auto chart = gr::graphs::ImChart<130, 28>({{*xMin, *xMax}, adjustRange(*yMin, *yMax)}); - chart.draw(reversedX, reversedY, signal_name); - chart.draw<gr::graphs::Style::Marker>(reversedX, reversedTag, "Tags"); + chart.draw(_historyBufferX, _historyBufferY, signal_name); + chart.draw<gr::graphs::Style::Marker>(_historyBufferX, _historyBufferTags, "Tags"); chart.draw(); } else if constexpr (gr::DataSetLike<T>) { if (_historyBufferY.empty()) { diff --git a/core/benchmarks/bm_HistoryBuffer.cpp b/core/benchmarks/bm_HistoryBuffer.cpp index 1094e70d..c47d98b9 100644 --- a/core/benchmarks/bm_HistoryBuffer.cpp +++ b/core/benchmarks/bm_HistoryBuffer.cpp @@ -91,30 +91,51 @@ inline const boost::ut::suite _buffer_tests = [] { { HistoryBuffer<int> buffer(32); - "history_buffer<int>(32)"_benchmark.repeat<n_repetitions>(samples) = [&buffer] { + "history_buffer<int>(32) - push_front"_benchmark.repeat<n_repetitions>(samples) = [&buffer] { static int counter = 0; for (std::size_t i = 0; i < samples; i++) { - buffer.push_back(counter); + buffer.push_front(counter); if (const auto data = buffer[0] != counter) { throw std::runtime_error(fmt::format("write {} read {} mismatch", counter, data)); } counter++; } }; + "history_buffer<int>(32) - push_back"_benchmark.repeat<n_repetitions>(samples) = [&buffer] { + static int counter = 0; + for (std::size_t i = 0; i < samples; i++) { + buffer.push_back(counter); + if (const auto data = buffer[buffer.size() - 1UZ] != counter) { + throw std::runtime_error(fmt::format("write {} read {} mismatch", counter, data)); + } + counter++; + } + }; } { HistoryBuffer<int> buffer(32); - "history_buffer<int, 32>"_benchmark.repeat<n_repetitions>(samples) = [&buffer] { + "history_buffer<int, 32> - push_front"_benchmark.repeat<n_repetitions>(samples) = [&buffer] { static int counter = 0; for (std::size_t i = 0; i < samples; i++) { - buffer.push_back(counter); + buffer.push_front(counter); if (const auto data = buffer[0] != counter) { throw std::runtime_error(fmt::format("write {} read {} mismatch", counter, data)); } counter++; } }; + + "history_buffer<int, 32> - push_back"_benchmark.repeat<n_repetitions>(samples) = [&buffer] { + static int counter = 0; + for (std::size_t i = 0; i < samples; i++) { + buffer.push_back(counter); + if (const auto data = buffer[buffer.size() - 1UZ] != counter) { + throw std::runtime_error(fmt::format("write {} read {} mismatch", counter, data)); + } + counter++; + } + }; } { @@ -140,7 +161,16 @@ inline const boost::ut::suite _buffer_tests = [] { { HistoryBuffer<int, 32> buffer; - "history_buffer<int, 32> - no checks"_benchmark.repeat<n_repetitions>(samples) = [&buffer] { + "history_buffer<int, 32> - no checks - push_front"_benchmark.repeat<n_repetitions>(samples) = [&buffer] { + static int counter = 0; + for (std::size_t i = 0; i < samples; i++) { + buffer.push_front(counter); + [[maybe_unused]] const auto data = buffer[0]; + counter++; + } + }; + + "history_buffer<int, 32> - no checks - push_back"_benchmark.repeat<n_repetitions>(samples) = [&buffer] { static int counter = 0; for (std::size_t i = 0; i < samples; i++) { buffer.push_back(counter); @@ -152,7 +182,16 @@ inline const boost::ut::suite _buffer_tests = [] { { HistoryBuffer<int> buffer(32); - "history_buffer<int>(32) - no checks"_benchmark.repeat<n_repetitions>(samples) = [&buffer] { + "history_buffer<int>(32) - no checks - push_front"_benchmark.repeat<n_repetitions>(samples) = [&buffer] { + static int counter = 0; + for (std::size_t i = 0; i < samples; i++) { + buffer.push_front(counter); + [[maybe_unused]] const auto data = buffer[0]; + counter++; + } + }; + + "history_buffer<int>(32) - no checks - push_back"_benchmark.repeat<n_repetitions>(samples) = [&buffer] { static int counter = 0; for (std::size_t i = 0; i < samples; i++) { buffer.push_back(counter); @@ -163,4 +202,4 @@ inline const boost::ut::suite _buffer_tests = [] { } }; -int main() { /* not needed by the UT framework */ } \ No newline at end of file +int main() { /* not needed by the UT framework */ } diff --git a/core/include/gnuradio-4.0/HistoryBuffer.hpp b/core/include/gnuradio-4.0/HistoryBuffer.hpp index 49d5cce5..5dd80de8 100644 --- a/core/include/gnuradio-4.0/HistoryBuffer.hpp +++ b/core/include/gnuradio-4.0/HistoryBuffer.hpp @@ -23,8 +23,8 @@ namespace gr { * - Fixed (`N != std::dynamic_extent`): uses `std::array<T, N * 2>`. * * ### Key Operations - * - `push_back(const T&)`: Add new item, drop oldest if full, index `[0]` is newest. - * - `push_front(const T&)`: Add new item, drop oldest if full, index `[0]` is oldest. + * - `push_front(const T&)`: Add new item, drop oldest if full, index `[0]` is newest. + * - `push_back(const T&)`: Add new item, drop oldest if full, index `[0]` is oldest. * - `pop_front()`, `pop_back()`: Remove from logical front/back of the ring. * - `front()`, `back()`: Returns the first/last item in logical order. * - `operator[](i)`, `at(i)`: Unchecked/checked access. @@ -33,16 +33,16 @@ namespace gr { * * ### Examples * \code{.cpp} - * gr::history_buffer<int, 5> hb_newest; // Use push_back -> index[0] is newest - * hb_newest.push_back(1); // [1] - * hb_newest.push_back(2); // [2, 1] - * hb_newest.push_back(3); // [3, 2, 1] + * gr::history_buffer<int, 5> hb_newest; // use push_front -> index[0] is newest + * hb_newest.push_front(1); // [1] + * hb_newest.push_front(2); // [2, 1] + * hb_newest.push_front(3); // [3, 2, 1] * // => index[0] == 3, newest item * - * gr::history_buffer<int, 5> hb_oldest; // Use push_front -> index[0] is oldest - * hb_oldest.push_front(10); // [10] - * hb_oldest.push_front(20); // [10, 20] - * hb_oldest.push_front(30); // [10, 20, 30] + * gr::history_buffer<int, 5> hb_oldest; // use push_back -> index[0] is oldest + * hb_oldest.push_back(10); // [10] + * hb_oldest.push_back(20); // [10, 20] + * hb_oldest.push_back(30); // [10, 20, 30] * // => index[0] == 10, oldest item * * hb_newest.pop_front(); // remove newest => now hb_newest: [2, 1] @@ -61,7 +61,7 @@ class HistoryBuffer { buffer_type _buffer{}; std::size_t _capacity = N; - std::size_t _write_position{0}; + std::size_t _write_position{0UZ}; std::size_t _size{0UZ}; /** @@ -98,7 +98,7 @@ class HistoryBuffer { /** * @brief Adds an element to the end expiring the oldest element beyond the buffer's capacities. */ - constexpr void push_back(const T& value) noexcept { + constexpr void push_front(const T& value) noexcept { if (_size < _capacity) [[unlikely]] { ++_size; } @@ -114,36 +114,26 @@ class HistoryBuffer { * @brief Adds a range of elements the end expiring the oldest elements beyond the buffer's capacities. */ template<std::input_iterator Iter> - constexpr void push_back(Iter cbegin, Iter cend) noexcept { - const auto nSamplesToCopy = std::distance(cbegin, cend); - Iter optimizedBegin = static_cast<std::size_t>(nSamplesToCopy) > _capacity ? std::prev(cend, static_cast<std::ptrdiff_t>(_capacity)) : cbegin; - for (auto it = optimizedBegin; it != cend; ++it) { - push_back(*it); + constexpr void push_front(Iter begin, Iter end) noexcept { + const auto nSamplesToCopy = std::distance(begin, end); + Iter optimizedBegin = static_cast<std::size_t>(nSamplesToCopy) > _capacity ? std::prev(end, static_cast<std::ptrdiff_t>(_capacity)) : begin; + for (auto it = optimizedBegin; it != end; ++it) { + push_front(*it); } } - template<std::input_iterator Iter> - constexpr void push_back_bulk(Iter cbegin, Iter cend) noexcept { - return push_back(cbegin, cend); - } - /** * @brief Adds a range of elements the end expiring the oldest elements beyond the buffer's capacities. */ template<std::ranges::range Range> - constexpr void push_back(const Range& range) noexcept { - push_back_bulk(std::ranges::begin(range), std::ranges::end(range)); - } - - template<std::ranges::range Range> - constexpr void push_back_bulk(const Range& range) noexcept { - push_back_bulk(range.cbegin(), range.cend()); + constexpr void push_front(const Range& range) noexcept { + push_front(std::ranges::begin(range), std::ranges::end(range)); } /** * @brief Adds an element to the front expiring the oldest element beyond the buffer's capacities. */ - constexpr void push_front(const T& value) noexcept { + constexpr void push_back(const T& value) noexcept { if (_size == _capacity) { if (++_write_position == _capacity) { _write_position = 0U; @@ -166,7 +156,7 @@ class HistoryBuffer { * the "newest" in the ring, with operator[](0) always the oldest. */ template<std::input_iterator Iter> - constexpr void push_front(Iter first, Iter last) noexcept { + constexpr void push_back(Iter first, Iter last) noexcept { std::size_t n = static_cast<std::size_t>(std::distance(first, last)); if (n == 0) { return; @@ -212,8 +202,8 @@ class HistoryBuffer { * the "newest" in the ring, with operator[](0) always the oldest. */ template<std::ranges::range Range> - constexpr void push_front(const Range& r) noexcept { - push_front(std::ranges::begin(r), std::ranges::end(r)); + constexpr void push_back(const Range& r) noexcept { + push_back(std::ranges::begin(r), std::ranges::end(r)); } /** @@ -222,8 +212,8 @@ class HistoryBuffer { * @throws std::out_of_range if the buffer is empty. * * @note - * - If you only call `push_back(...)`, `operator[](0)` is the newest element; hence `pop_front()` removes the newest. - * - If you only call `push_front(...)`, `operator[](0)` is the oldest element; hence `pop_front()` removes the oldest. + * - If you only call `push_front(...)`, `operator[](0)` is the newest element; hence `pop_front()` removes the newest. + * - If you only call `push_back(...)`, `operator[](0)` is the oldest element; hence `pop_front()` removes the oldest. * - Mixing both push modes can lead to non-intuitive behavior for front/back usage. */ constexpr void pop_front() { @@ -250,8 +240,8 @@ class HistoryBuffer { * @throws std::out_of_range if the buffer is empty. * * @note - * - If you only call `push_back(...)`, `operator[](size() - 1)` is the oldest element; hence `pop_back()` removes the oldest. - * - If you only call `push_front(...)`, `operator[](size() - 1)` is the newest element; hence `pop_back()` removes the newest. + * - If you only call `push_front(...)`, `operator[](size() - 1)` is the oldest element; hence `pop_back()` removes the oldest. + * - If you only call `push_back(...)`, `operator[](size() - 1)` is the newest element; hence `pop_back()` removes the newest. * - Mixing both push modes can lead to non-intuitive behavior for front/back usage. */ constexpr void pop_back() { @@ -280,7 +270,7 @@ class HistoryBuffer { std::swap(_buffer, newBuf); _capacity = newCapacity; _size = copyCount; - _write_position = 0; // implementation choice + _write_position = 0UZ; // implementation choice } /** diff --git a/core/include/gnuradio-4.0/Scheduler.hpp b/core/include/gnuradio-4.0/Scheduler.hpp index 9fe7c1e5..1f80d35e 100644 --- a/core/include/gnuradio-4.0/Scheduler.hpp +++ b/core/include/gnuradio-4.0/Scheduler.hpp @@ -446,7 +446,7 @@ class Simple : public SchedulerBase<Simple<execution, TProfiler>, execution, TPr std::lock_guard lock(base_t::_jobListsMutex); std::size_t blockCount = 0UZ; - this->forAllUnmanagedBlocks([&blockCount](auto&& block) { blockCount++; }); + this->forAllUnmanagedBlocks([&blockCount](auto&& /*block*/) { blockCount++; }); std::vector<BlockModel*> allBlocks; allBlocks.reserve(blockCount); this->forAllUnmanagedBlocks([&allBlocks](auto&& block) { allBlocks.push_back(block.get()); }); diff --git a/core/test/qa_buffer.cpp b/core/test/qa_buffer.cpp index 4ae5c60d..29f332ee 100644 --- a/core/test/qa_buffer.cpp +++ b/core/test/qa_buffer.cpp @@ -707,7 +707,7 @@ const boost::ut::suite HistoryBufferTest = [] { expect(eq(hb.size(), 0UZ)); for (std::size_t i = 1; i <= capacity + 1; ++i) { - hb.push_back(static_cast<int>(i)); + hb.push_front(static_cast<int>(i)); } expect(eq(hb.capacity(), capacity)); expect(eq(hb.size(), capacity)); @@ -725,8 +725,8 @@ const boost::ut::suite HistoryBufferTest = [] { "HistoryBuffer - range tests"_test = [] { HistoryBuffer<int> hb(5); - hb.push_back_bulk(std::array{1, 2, 3}); - hb.push_back_bulk(std::vector{4, 5, 6}); + hb.push_front(std::array{1, 2, 3}); + hb.push_front(std::vector{4, 5, 6}); expect(eq(hb.capacity(), 5UZ)); expect(eq(hb.size(), 5UZ)); @@ -761,8 +761,8 @@ const boost::ut::suite HistoryBufferTest = [] { HistoryBuffer<int, 8UZ> buffer8; for (std::size_t i = 0UZ; i <= buffer8.capacity(); ++i) { - buffer5.push_back(static_cast<int>(i)); - buffer8.push_back(static_cast<int>(i)); + buffer5.push_front(static_cast<int>(i)); + buffer8.push_front(static_cast<int>(i)); } expect(eq(buffer5[0], 8)); @@ -778,8 +778,8 @@ const boost::ut::suite HistoryBufferTest = [] { const auto& const_hb_one = hb_one; // tests const access expect(eq(hb_one.capacity(), 1UZ)); expect(eq(hb_one.size(), 0UZ)); - hb_one.push_back(41); - hb_one.push_back(42); + hb_one.push_front(41); + hb_one.push_front(42); expect(eq(hb_one.capacity(), 1UZ)); expect(eq(hb_one.size(), 1UZ)); expect(eq(hb_one[0], 42)); @@ -790,17 +790,17 @@ const boost::ut::suite HistoryBufferTest = [] { // Push more elements than buffer size HistoryBuffer<int> hb_overflow(5); auto in = std::vector{1, 2, 3, 4, 5, 6}; - hb_overflow.push_back_bulk(in.begin(), in.end()); + hb_overflow.push_front(in.begin(), in.end()); expect(eq(hb_overflow[0], 6)); - hb_overflow.push_back_bulk(std::vector{7, 8, 9, 10, 11, 12, 13, 14}); + hb_overflow.push_front(std::vector{7, 8, 9, 10, 11, 12, 13, 14}); expect(eq(hb_overflow[0], 14)); - hb_overflow.push_back_bulk(std::array{15, 16, 17}); + hb_overflow.push_front(std::array{15, 16, 17}); expect(eq(hb_overflow[0], 17)); // Test with different types, e.g., double HistoryBuffer<double> hb_double(5); for (int i = 0; i < 10; ++i) { - hb_double.push_back(i * 0.1); + hb_double.push_front(i * 0.1); } expect(eq(hb_double.capacity(), 5UZ)); expect(eq(hb_double.size(), 5UZ)); @@ -823,13 +823,13 @@ const boost::ut::suite HistoryBufferTest = [] { }; "HistoryBuffer - forward/reversed usage"_test = [] { - HistoryBuffer<int> forward(5); // stores push_back(..) with forward[0] being newest sample - HistoryBuffer<int> backward(5); // stores push_front(..) with backward[0] being the oldest sample + HistoryBuffer<int> forward(5); // stores push_front(..) with forward[0] being newest sample + HistoryBuffer<int> backward(5); // stores push_back(..) with backward[0] being the oldest sample // push {1,2,3,4,5,6} individually to both for (int i = 1; i <= 6; ++i) { - forward.push_back(i); - backward.push_front(i); + forward.push_front(i); + backward.push_back(i); } // expected content of forward: [6,5,4,3,2] @@ -848,7 +848,7 @@ const boost::ut::suite HistoryBufferTest = [] { // Bulk test: backward.reset(); - backward.push_front(std::vector<int>{10, 11, 12, 13, 14, 15, 16}); // push more than capacity: + backward.push_back(std::vector<int>{10, 11, 12, 13, 14, 15, 16}); // push more than capacity: expect(eq(backward[0], 12)); expect(eq(backward[4], 16)); @@ -862,7 +862,7 @@ const boost::ut::suite HistoryBufferTest = [] { // Only for dynamic-extent HistoryBuffer<int> hb(5); for (int i = 1; i <= 5; ++i) { - hb.push_back(i); + hb.push_front(i); } // now: [5,4,3,2,1] expect(eq(hb.size(), 5UZ)); @@ -876,7 +876,7 @@ const boost::ut::suite HistoryBufferTest = [] { // push more data for (int i = 6; i <= 10; ++i) { - hb.push_back(i); // if we keep pushing + hb.push_front(i); // if we keep pushing } expect(eq(hb.size(), 8UZ)); // now full at 8 expect(eq(hb[0], 10)); // newest => 10 @@ -894,7 +894,7 @@ const boost::ut::suite HistoryBufferTest = [] { expect(eq(hb.empty(), true)); for (int i = 1; i <= 6; ++i) { - hb.push_back(i); + hb.push_front(i); } // final ring => [6,5,4,3,2] expect(eq(hb.front(), 6)) << "front == [0] => newest sample in push_back orientation"; @@ -902,7 +902,7 @@ const boost::ut::suite HistoryBufferTest = [] { hb.reset(); for (int i = 1; i <= 6; ++i) { - hb.push_front(i); + hb.push_back(i); } // final ring => [2,3,4,5,6] in logical terms expect(eq(hb.front(), 2)) << "front == [0] => oldest sample in push_front orientation"; @@ -915,7 +915,7 @@ const boost::ut::suite HistoryBufferTest = [] { HistoryBuffer<int> hb(5); for (int i = 1; i <= 5; ++i) { // push_back => newest @ [0] - hb.push_back(i); // final ring => [5,4,3,2,1] + hb.push_front(i); // final ring => [5,4,3,2,1] } expect(eq(hb.size(), 5UZ)); expect(eq(hb[0], 5)); @@ -943,7 +943,7 @@ const boost::ut::suite HistoryBufferTest = [] { // test push_front orientation HistoryBuffer<int> hb2(5); for (int i = 1; i <= 5; ++i) { - hb2.push_front(i); // final ring => [1,2,3,4,5] logically + hb2.push_back(i); // final ring => [1,2,3,4,5] logically } expect(eq(hb2[0], 1)); hb2.pop_front(); // remove '1'