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());