From fe977e4e8ced8a660c070d45e94de1941c2e29ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Sat, 22 Aug 2026 19:00:32 +0200 Subject: [PATCH] Don't instantiate the default implementation of benchmarks in every TU The underlying benchmarking machinery is templated, so that it can use different clock implementations (either for testing, or to support user with platform-specific clocks). However, 99.99% of all users interact with it using the `BENCHMARK` macro, which means that they use it with `std::chrono::steady_clock`. By adding outlined implementation of benchmarking helpers specialized onto `std::chrono::steady_clock`, we save some amount of time per every TU that uses benchmarks. --- src/CMakeLists.txt | 1 + src/catch2/benchmark/catch_benchmark.cpp | 54 ++++++++++++ src/catch2/benchmark/catch_benchmark.hpp | 100 ++++++++++++++++++++--- 3 files changed, 143 insertions(+), 12 deletions(-) create mode 100644 src/catch2/benchmark/catch_benchmark.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0ed64ebf..886605f1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,6 +32,7 @@ set(BENCHMARK_HEADERS ${SOURCES_DIR}/benchmark/detail/catch_timing.hpp ) set(BENCHMARK_SOURCES + ${SOURCES_DIR}/benchmark/catch_benchmark.cpp ${SOURCES_DIR}/benchmark/catch_chronometer.cpp ${SOURCES_DIR}/benchmark/detail/catch_analyse.cpp ${SOURCES_DIR}/benchmark/detail/catch_benchmark_function.cpp diff --git a/src/catch2/benchmark/catch_benchmark.cpp b/src/catch2/benchmark/catch_benchmark.cpp new file mode 100644 index 00000000..5a34ed39 --- /dev/null +++ b/src/catch2/benchmark/catch_benchmark.cpp @@ -0,0 +1,54 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include + +#include +#include +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + + Environment measure_environment_default() { + return Detail::measure_environment(); + } + + ExecutionPlan prepare_default( const IConfig& cfg, + Environment env, + BenchmarkFunction&& fun ) { + // This mirrors Benchmark::prepare(), but with the + // clock fixed so it is instantiated once here in the library. + auto min_time = env.clock_resolution.mean * Detail::minimum_ticks; + auto run_time = std::max( + min_time, + std::chrono::duration_cast( + cfg.benchmarkWarmupTime() ) ); + auto&& test = Detail::run_for_at_least( + std::chrono::duration_cast( run_time ), 1, fun ); + int new_iters = static_cast( + std::ceil( min_time * test.iterations / test.elapsed ) ); + return { new_iters, + test.elapsed / test.iterations * new_iters * + cfg.benchmarkSamples(), + CATCH_MOVE( fun ), + std::chrono::duration_cast( + cfg.benchmarkWarmupTime() ), + Detail::warmup_iterations }; + } + + std::vector run_plan_default( ExecutionPlan const& plan, + const IConfig& cfg, + Environment env ) { + return plan.run( cfg, env ); + } + + } // namespace Detail + } // namespace Benchmark +} // namespace Catch diff --git a/src/catch2/benchmark/catch_benchmark.hpp b/src/catch2/benchmark/catch_benchmark.hpp index ce68aee7..9b879982 100644 --- a/src/catch2/benchmark/catch_benchmark.hpp +++ b/src/catch2/benchmark/catch_benchmark.hpp @@ -33,9 +33,94 @@ #include #include #include +#include namespace Catch { namespace Benchmark { + namespace Detail { + template + ExecutionPlan prepare( const IConfig& cfg, + Environment env, + BenchmarkFunction&& fun ) { + auto min_time = + env.clock_resolution.mean * Detail::minimum_ticks; + auto run_time = + std::max( min_time, + std::chrono::duration_cast( + cfg.benchmarkWarmupTime() ) ); + auto&& test = Detail::run_for_at_least( + std::chrono::duration_cast( run_time ), 1, fun ); + int new_iters = static_cast( + std::ceil( min_time * test.iterations / test.elapsed ) ); + return { new_iters, + test.elapsed / test.iterations * new_iters * + cfg.benchmarkSamples(), + CATCH_MOVE( fun ), + std::chrono::duration_cast( + cfg.benchmarkWarmupTime() ), + Detail::warmup_iterations }; + } + + // These are wrappers for their respective function templated + // over `default_clock`. This allows outlining the usual use + // of the template into single TU and save on compilation costs. + + Environment measure_environment_default(); + ExecutionPlan prepare_default( const IConfig& cfg, + Environment env, + BenchmarkFunction&& fun ); + std::vector run_plan_default( ExecutionPlan const& plan, + const IConfig& cfg, + Environment env ); + + template + std::enable_if_t::value, + Environment> + measureEnvironmentDispatch() { + return measure_environment_default(); + } + template + std::enable_if_t::value, + Environment> + measureEnvironmentDispatch() { + return measure_environment(); + } + + template + std::enable_if_t::value, + std::vector> + runPlanDispatch( ExecutionPlan const& plan, + const IConfig& cfg, + Environment env ) { + return run_plan_default( plan, cfg, env ); + } + template + std::enable_if_t::value, + std::vector> + runPlanDispatch( ExecutionPlan const& plan, + const IConfig& cfg, + Environment env ) { + return plan.template run( cfg, env ); + } + + template + std::enable_if_t::value, + ExecutionPlan> + prepareDispatch( const IConfig& cfg, + Environment env, + BenchmarkFunction&& fun ) { + return prepare_default( cfg, env, CATCH_MOVE( fun ) ); + } + template + std::enable_if_t::value, + ExecutionPlan> + prepareDispatch( const IConfig& cfg, + Environment env, + BenchmarkFunction&& fun ) { + return prepare( cfg, env, CATCH_MOVE( fun ) ); + } + } // namespace Detail + struct Benchmark { Benchmark(std::string&& benchmarkName) : name(CATCH_MOVE(benchmarkName)) {} @@ -44,27 +129,18 @@ namespace Catch { Benchmark(std::string&& benchmarkName , FUN &&func) : fun(CATCH_MOVE(func)), name(CATCH_MOVE(benchmarkName)) {} - template - ExecutionPlan prepare(const IConfig &cfg, Environment env) { - auto min_time = env.clock_resolution.mean * Detail::minimum_ticks; - auto run_time = std::max(min_time, std::chrono::duration_cast(cfg.benchmarkWarmupTime())); - auto&& test = Detail::run_for_at_least(std::chrono::duration_cast(run_time), 1, fun); - int new_iters = static_cast(std::ceil(min_time * test.iterations / test.elapsed)); - return { new_iters, test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), CATCH_MOVE(fun), std::chrono::duration_cast(cfg.benchmarkWarmupTime()), Detail::warmup_iterations }; - } - template void run() { static_assert( Clock::is_steady, "Benchmarking clock should be steady" ); auto const* cfg = getCurrentContext().getConfig(); - auto env = Detail::measure_environment(); + auto env = Detail::measureEnvironmentDispatch(); getResultCapture().benchmarkPreparing(name); CATCH_TRY{ auto plan = user_code([&] { - return prepare(*cfg, env); + return Detail::prepareDispatch( *cfg, env, CATCH_MOVE(fun) ); }); BenchmarkInfo info { @@ -80,7 +156,7 @@ namespace Catch { getResultCapture().benchmarkStarting(info); auto samples = user_code([&] { - return plan.template run(*cfg, env); + return Detail::runPlanDispatch( plan, *cfg, env ); }); auto analysis = Detail::analyse(*cfg, samples.data(), samples.data() + samples.size());