From 4b2ff7e14381cb960c4f2d630c737eca8038ef4d Mon Sep 17 00:00:00 2001 From: superman15625 Date: Sat, 25 Jul 2026 20:11:18 -0500 Subject: [PATCH] optimizing clock resolution estimate function and adding safeguard to prevent bad_alloc --- docs/release-notes.md | 1 + .../benchmark/detail/catch_estimate_clock.hpp | 42 ++++++++++++------- .../InternalBenchmark.tests.cpp | 8 +++- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index ffa06430b9..2c41478c7c 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -82,6 +82,7 @@ ### Fixes * Fixed `--warn InfiniteGenerators` triggering even if the generator was limited to specific element via path filtering. * Fixed `-Wunused-parameter` triggering in `-fnoexceptions` builds. +* Fixed benchmark clock-resolution estimation potentially exhausting memory on fast or memory-constrained platforms. (#3180) ### Improvements * `catch_discover_tests` can handle cases where the binary prints out non-Catch2 output due to global constructors (#3162) diff --git a/src/catch2/benchmark/detail/catch_estimate_clock.hpp b/src/catch2/benchmark/detail/catch_estimate_clock.hpp index 576fdb6835..c67483a32d 100644 --- a/src/catch2/benchmark/detail/catch_estimate_clock.hpp +++ b/src/catch2/benchmark/detail/catch_estimate_clock.hpp @@ -26,19 +26,14 @@ namespace Catch { namespace Detail { template std::vector resolution(int k) { - const size_t points = static_cast( k + 1 ); - // To avoid overhead from the branch inside vector::push_back, - // we allocate them all and then overwrite. - std::vector> times(points); - for ( auto& time : times ) { - time = Clock::now(); - } - std::vector deltas; deltas.reserve(static_cast(k)); - for ( size_t idx = 1; idx < points; ++idx ) { + auto previous = Clock::now(); + for ( int idx = 0; idx < k; ++idx ) { + auto current = Clock::now(); deltas.push_back( static_cast( - ( times[idx] - times[idx - 1] ).count() ) ); + ( current - previous ).count() ) ); + previous = current; } return deltas; @@ -49,6 +44,7 @@ namespace Catch { constexpr auto minimum_ticks = 1000; constexpr auto warmup_seed = 10000; constexpr auto clock_resolution_estimation_time = std::chrono::milliseconds(500); + constexpr auto clock_resolution_estimation_iteration_limit = 100000; constexpr auto clock_cost_estimation_time_limit = std::chrono::seconds(1); constexpr auto clock_cost_estimation_tick_limit = 100000; constexpr auto clock_cost_estimation_time = std::chrono::milliseconds(10); @@ -61,12 +57,26 @@ namespace Catch { } template EnvironmentEstimate estimate_clock_resolution(int iterations) { - auto r = run_for_at_least(clock_resolution_estimation_time, iterations, &resolution) - .result; - return { - FDuration(mean(r.data(), r.data() + r.size())), - classify_outliers(r.data(), r.data() + r.size()), - }; + auto iters = (std::min)( iterations, + clock_resolution_estimation_iteration_limit ); + while ( true ) { + auto timing = + Detail::measure( &resolution, iters ); + auto const& r = timing.result; + if ( timing.elapsed >= clock_resolution_estimation_time || + iters == clock_resolution_estimation_iteration_limit ) { + return { + FDuration(mean(r.data(), r.data() + r.size())), + classify_outliers(r.data(), r.data() + r.size()), + }; + } + if ( iters > + clock_resolution_estimation_iteration_limit / 2 ) { + iters = clock_resolution_estimation_iteration_limit; + } else { + iters *= 2; + } + } } template EnvironmentEstimate estimate_clock_cost(FDuration resolution) { diff --git a/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp b/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp index 69251d9739..3323371e17 100644 --- a/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp @@ -109,11 +109,17 @@ TEST_CASE("estimate_clock_resolution", "[benchmark]") { auto rate = 2'000; counting_clock::set_rate(rate); - int iters = 160'000; + int iters = + Catch::Benchmark::Detail::clock_resolution_estimation_iteration_limit * + 2; auto res = Catch::Benchmark::Detail::estimate_clock_resolution(iters); REQUIRE(res.mean.count() == rate); REQUIRE(res.outliers.total() == 0); + if ( res.outliers.samples_seen > + Catch::Benchmark::Detail::clock_resolution_estimation_iteration_limit ) { + FAIL( "Clock resolution estimation exceeded the sample limit" ); + } } TEST_CASE("benchmark function call", "[benchmark]") {