catch_benchmark.hpp
1 2 // Copyright Catch2 Authors 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE.txt or copy at 5 // https://www.boost.org/LICENSE_1_0.txt) 6 7 // SPDX-License-Identifier: BSL-1.0 8 // Adapted from donated nonius code. 9 10 #ifndef CATCH_BENCHMARK_HPP_INCLUDED 11 #define CATCH_BENCHMARK_HPP_INCLUDED 12 13 #include <catch2/catch_user_config.hpp> 14 #include <catch2/internal/catch_compiler_capabilities.hpp> 15 #include <catch2/internal/catch_context.hpp> 16 #include <catch2/internal/catch_move_and_forward.hpp> 17 #include <catch2/internal/catch_test_failure_exception.hpp> 18 #include <catch2/internal/catch_unique_name.hpp> 19 #include <catch2/interfaces/catch_interfaces_capture.hpp> 20 #include <catch2/interfaces/catch_interfaces_config.hpp> 21 #include <catch2/interfaces/catch_interfaces_registry_hub.hpp> 22 #include <catch2/benchmark/detail/catch_benchmark_stats.hpp> 23 #include <catch2/benchmark/catch_clock.hpp> 24 #include <catch2/benchmark/catch_environment.hpp> 25 #include <catch2/benchmark/catch_execution_plan.hpp> 26 #include <catch2/benchmark/detail/catch_estimate_clock.hpp> 27 #include <catch2/benchmark/detail/catch_analyse.hpp> 28 #include <catch2/benchmark/detail/catch_benchmark_function.hpp> 29 #include <catch2/benchmark/detail/catch_run_for_at_least.hpp> 30 31 #include <algorithm> 32 #include <chrono> 33 #include <exception> 34 #include <string> 35 #include <cmath> 36 37 namespace Catch { 38 namespace Benchmark { 39 struct Benchmark { 40 Benchmark(std::string&& benchmarkName) 41 : name(CATCH_MOVE(benchmarkName)) {} 42 43 template <class FUN> 44 Benchmark(std::string&& benchmarkName , FUN &&func) 45 : fun(CATCH_MOVE(func)), name(CATCH_MOVE(benchmarkName)) {} 46 47 template <typename Clock> 48 ExecutionPlan prepare(const IConfig &cfg, Environment env) const { 49 auto min_time = env.clock_resolution.mean * Detail::minimum_ticks; 50 auto run_time = std::max(min_time, std::chrono::duration_cast<decltype(min_time)>(cfg.benchmarkWarmupTime())); 51 auto&& test = Detail::run_for_at_least<Clock>(std::chrono::duration_cast<IDuration>(run_time), 1, fun); 52 int new_iters = static_cast<int>(std::ceil(min_time * test.iterations / test.elapsed)); 53 return { new_iters, test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), fun, std::chrono::duration_cast<FDuration>(cfg.benchmarkWarmupTime()), Detail::warmup_iterations }; 54 } 55 56 template <typename Clock = default_clock> 57 void run() { 58 static_assert( Clock::is_steady, 59 "Benchmarking clock should be steady" ); 60 auto const* cfg = getCurrentContext().getConfig(); 61 62 auto env = Detail::measure_environment<Clock>(); 63 64 getResultCapture().benchmarkPreparing(name); 65 CATCH_TRY{ 66 auto plan = user_code([&] { 67 return prepare<Clock>(*cfg, env); 68 }); 69 70 BenchmarkInfo info { 71 CATCH_MOVE(name), 72 plan.estimated_duration.count(), 73 plan.iterations_per_sample, 74 cfg->benchmarkSamples(), 75 cfg->benchmarkResamples(), 76 env.clock_resolution.mean.count(), 77 env.clock_cost.mean.count() 78 }; 79 80 getResultCapture().benchmarkStarting(info); 81 82 auto samples = user_code([&] { 83 return plan.template run<Clock>(*cfg, env); 84 }); 85 86 auto analysis = Detail::analyse(*cfg, samples.data(), samples.data() + samples.size()); 87 BenchmarkStats<> stats{ CATCH_MOVE(info), CATCH_MOVE(analysis.samples), analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance }; 88 getResultCapture().benchmarkEnded(stats); 89 } CATCH_CATCH_ANON (TestFailureException const&) { 90 getResultCapture().benchmarkFailed("Benchmark failed due to failed assertion"_sr); 91 } CATCH_CATCH_ALL{ 92 getResultCapture().benchmarkFailed(translateActiveException()); 93 // We let the exception go further up so that the 94 // test case is marked as failed. 95 std::rethrow_exception(std::current_exception()); 96 } 97 } 98 99 // sets lambda to be used in fun *and* executes benchmark! 100 template <typename Fun, std::enable_if_t<!Detail::is_related<Fun, Benchmark>::value, int> = 0> 101 Benchmark & operator=(Fun func) { 102 auto const* cfg = getCurrentContext().getConfig(); 103 if (!cfg->skipBenchmarks()) { 104 fun = Detail::BenchmarkFunction(func); 105 run(); 106 } 107 return *this; 108 } 109 110 explicit operator bool() { 111 return true; 112 } 113 114 private: 115 Detail::BenchmarkFunction fun; 116 std::string name; 117 }; 118 } 119 } // namespace Catch 120 121 #define INTERNAL_CATCH_GET_1_ARG(arg1, arg2, ...) arg1 122 #define INTERNAL_CATCH_GET_2_ARG(arg1, arg2, ...) arg2 123 124 #define INTERNAL_CATCH_BENCHMARK(BenchmarkName, name, benchmarkIndex)\ 125 if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \ 126 BenchmarkName = [&](int benchmarkIndex) 127 128 #define INTERNAL_CATCH_BENCHMARK_ADVANCED(BenchmarkName, name)\ 129 if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \ 130 BenchmarkName = [&] 131 132 #if defined(CATCH_CONFIG_PREFIX_ALL) 133 134 #define CATCH_BENCHMARK(...) \ 135 INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) 136 #define CATCH_BENCHMARK_ADVANCED(name) \ 137 INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name) 138 139 #else 140 141 #define BENCHMARK(...) \ 142 INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) 143 #define BENCHMARK_ADVANCED(name) \ 144 INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name) 145 146 #endif 147 148 #endif // CATCH_BENCHMARK_HPP_INCLUDED