diff options
| author | Leitwolf | 2026-10-25 10:31:36 +0300 |
|---|---|---|
| committer | GitHub | 2026-10-25 10:31:36 +0300 |
| commit | d2e3e074bb2e1b8ffd9fc990d0b87667ea602f58 (patch) | |
| tree | 0bce4462b2e190c39b3e1c5f697e3e4abd1a5e2a /src/Progmasoft/Catch3/Benchmark | |
| parent | 13f81a480a93807c14dddb5abdb97355ced4d4c5 (diff) | |
| parent | 126dc333564cb72b5115d9a9242d66694694b6d0 (diff) | |
| download | catch3-d2e3e074bb2e1b8ffd9fc990d0b87667ea602f58.tar.gz | |
Merge pull request #18 from Progmasoft/build/catch3-identity
Give Catch3 its own build names and headers
Diffstat (limited to 'src/Progmasoft/Catch3/Benchmark')
28 files changed, 1863 insertions, 0 deletions
diff --git a/src/Progmasoft/Catch3/Benchmark/All.hpp b/src/Progmasoft/Catch3/Benchmark/All.hpp new file mode 100644 index 00000000..bc0d9526 --- /dev/null +++ b/src/Progmasoft/Catch3/Benchmark/All.hpp @@ -0,0 +1,44 @@ + +// 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 +/** \file + * This is a convenience header for Catch2's benchmarking. It includes + * **all** of Catch2 headers related to benchmarking. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of (significantly) increased + * compilation times. + * + * When a new header is added to either the `benchmark` folder, or to + * the corresponding internal (detail) subfolder, it should be added here. + */ + +#pragma once + +#include <Progmasoft/Catch3/Benchmark/Benchmark.hpp> +#include <Progmasoft/Catch3/Benchmark/Chronometer.hpp> +#include <Progmasoft/Catch3/Benchmark/Clock.hpp> +#include <Progmasoft/Catch3/Benchmark/Constructor.hpp> +#include <Progmasoft/Catch3/Benchmark/Detail/Analyse.hpp> +#include <Progmasoft/Catch3/Benchmark/Detail/BenchmarkFunction.hpp> +#include <Progmasoft/Catch3/Benchmark/Detail/BenchmarkStats.hpp> +#include <Progmasoft/Catch3/Benchmark/Detail/BenchmarkStatsFwd.hpp> +#include <Progmasoft/Catch3/Benchmark/Detail/CompleteInvoke.hpp> +#include <Progmasoft/Catch3/Benchmark/Detail/EstimateClock.hpp> +#include <Progmasoft/Catch3/Benchmark/Detail/Measure.hpp> +#include <Progmasoft/Catch3/Benchmark/Detail/Repeat.hpp> +#include <Progmasoft/Catch3/Benchmark/Detail/RunForAtLeast.hpp> +#include <Progmasoft/Catch3/Benchmark/Detail/Stats.hpp> +#include <Progmasoft/Catch3/Benchmark/Detail/Timing.hpp> +#include <Progmasoft/Catch3/Benchmark/Environment.hpp> +#include <Progmasoft/Catch3/Benchmark/Estimate.hpp> +#include <Progmasoft/Catch3/Benchmark/ExecutionPlan.hpp> +#include <Progmasoft/Catch3/Benchmark/Optimizer.hpp> +#include <Progmasoft/Catch3/Benchmark/OutlierClassification.hpp> +#include <Progmasoft/Catch3/Benchmark/SampleAnalysis.hpp> + diff --git a/src/Progmasoft/Catch3/Benchmark/Benchmark.cpp b/src/Progmasoft/Catch3/Benchmark/Benchmark.cpp new file mode 100644 index 00000000..8967b12c --- /dev/null +++ b/src/Progmasoft/Catch3/Benchmark/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 <Progmasoft/Catch3/Benchmark/Benchmark.hpp> + +#include <algorithm> +#include <chrono> +#include <cmath> + +namespace Catch { + namespace Benchmark { + namespace Detail { + + Environment measure_environment_default() { + return Detail::measure_environment<default_clock>(); + } + + ExecutionPlan prepare_default( const IConfig& cfg, + Environment env, + BenchmarkFunction&& fun ) { + // This mirrors Benchmark::prepare<default_clock>(), 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<decltype( min_time )>( + cfg.benchmarkWarmupTime() ) ); + auto&& test = Detail::run_for_at_least<default_clock>( + std::chrono::duration_cast<IDuration>( run_time ), 1, fun ); + int new_iters = static_cast<int>( + 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<FDuration>( + cfg.benchmarkWarmupTime() ), + Detail::warmup_iterations }; + } + + std::vector<FDuration> run_plan_default( ExecutionPlan const& plan, + const IConfig& cfg, + Environment env ) { + return plan.run<default_clock>( cfg, env ); + } + + } // namespace Detail + } // namespace Benchmark +} // namespace Catch diff --git a/src/Progmasoft/Catch3/Benchmark/Benchmark.hpp b/src/Progmasoft/Catch3/Benchmark/Benchmark.hpp new file mode 100644 index 00000000..f19c6603 --- /dev/null +++ b/src/Progmasoft/Catch3/Benchmark/Benchmark.hpp @@ -0,0 +1,220 @@ + +// 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 +// Adapted from donated nonius code. + +#pragma once + +#include <Progmasoft/Catch3/UserConfig.hpp> +#include <Progmasoft/Catch3/Internal/CompilerCapabilities.hpp> +#include <Progmasoft/Catch3/Internal/Context.hpp> +#include <Progmasoft/Catch3/Internal/MoveAndForward.hpp> +#include <Progmasoft/Catch3/Internal/TestFailureException.hpp> +#include <Progmasoft/Catch3/Internal/UniqueName.hpp> +#include <Progmasoft/Catch3/Interfaces/IResultCapture.hpp> +#include <Progmasoft/Catch3/Interfaces/IConfig.hpp> +#include <Progmasoft/Catch3/Interfaces/IRegistryHub.hpp> +#include <Progmasoft/Catch3/Benchmark/Detail/BenchmarkStats.hpp> +#include <Progmasoft/Catch3/Benchmark/Clock.hpp> +#include <Progmasoft/Catch3/Benchmark/Environment.hpp> +#include <Progmasoft/Catch3/Benchmark/ExecutionPlan.hpp> +#include <Progmasoft/Catch3/Benchmark/Detail/EstimateClock.hpp> +#include <Progmasoft/Catch3/Benchmark/Detail/Analyse.hpp> +#include <Progmasoft/Catch3/Benchmark/Detail/BenchmarkFunction.hpp> +#include <Progmasoft/Catch3/Benchmark/Detail/RunForAtLeast.hpp> + +#include <algorithm> +#include <chrono> +#include <exception> +#include <string> +#include <cmath> +#include <type_traits> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename Clock> + 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<decltype( min_time )>( + cfg.benchmarkWarmupTime() ) ); + auto&& test = Detail::run_for_at_least<Clock>( + std::chrono::duration_cast<IDuration>( run_time ), 1, fun ); + int new_iters = static_cast<int>( + 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<FDuration>( + 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<FDuration> run_plan_default( ExecutionPlan const& plan, + const IConfig& cfg, + Environment env ); + + template <typename Clock> + std::enable_if_t<std::is_same<Clock, default_clock>::value, + Environment> + measureEnvironmentDispatch() { + return measure_environment_default(); + } + template <typename Clock> + std::enable_if_t<!std::is_same<Clock, default_clock>::value, + Environment> + measureEnvironmentDispatch() { + return measure_environment<Clock>(); + } + + template <typename Clock> + std::enable_if_t<std::is_same<Clock, default_clock>::value, + std::vector<FDuration>> + runPlanDispatch( ExecutionPlan const& plan, + const IConfig& cfg, + Environment env ) { + return run_plan_default( plan, cfg, env ); + } + template <typename Clock> + std::enable_if_t<!std::is_same<Clock, default_clock>::value, + std::vector<FDuration>> + runPlanDispatch( ExecutionPlan const& plan, + const IConfig& cfg, + Environment env ) { + return plan.template run<Clock>( cfg, env ); + } + + template <typename Clock> + std::enable_if_t<std::is_same<Clock, default_clock>::value, + ExecutionPlan> + prepareDispatch( const IConfig& cfg, + Environment env, + BenchmarkFunction&& fun ) { + return prepare_default( cfg, env, CATCH_MOVE( fun ) ); + } + template <typename Clock> + std::enable_if_t<!std::is_same<Clock, default_clock>::value, + ExecutionPlan> + prepareDispatch( const IConfig& cfg, + Environment env, + BenchmarkFunction&& fun ) { + return prepare<Clock>( cfg, env, CATCH_MOVE( fun ) ); + } + } // namespace Detail + + struct Benchmark { + Benchmark(std::string&& benchmarkName) + : name(CATCH_MOVE(benchmarkName)) {} + + template <class FUN> + Benchmark(std::string&& benchmarkName , FUN &&func) + : fun(CATCH_MOVE(func)), name(CATCH_MOVE(benchmarkName)) {} + + template <typename Clock = default_clock> + void run() { + static_assert( Clock::is_steady, + "Benchmarking clock should be steady" ); + auto const* cfg = getCurrentContext().getConfig(); + + auto env = Detail::measureEnvironmentDispatch<Clock>(); + + getResultCapture().benchmarkPreparing(name); + CATCH_TRY{ + auto plan = user_code([&] { + return Detail::prepareDispatch<Clock>( *cfg, env, CATCH_MOVE(fun) ); + }); + + BenchmarkInfo info { + CATCH_MOVE(name), + plan.estimated_duration.count(), + plan.iterations_per_sample, + cfg->benchmarkSamples(), + cfg->benchmarkResamples(), + env.clock_resolution.mean.count(), + env.clock_cost.mean.count() + }; + + getResultCapture().benchmarkStarting(info); + + auto samples = user_code([&] { + return Detail::runPlanDispatch<Clock>( plan, *cfg, env ); + }); + + auto analysis = Detail::analyse(*cfg, samples.data(), samples.data() + samples.size()); + BenchmarkStats<> stats{ CATCH_MOVE(info), CATCH_MOVE(analysis.samples), analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance }; + getResultCapture().benchmarkEnded(stats); + } CATCH_CATCH_ALL { + getResultCapture().benchmarkFailed(translateActiveException()); + // We let the exception go further up so that the + // test case is marked as failed. + std::rethrow_exception(std::current_exception()); + } + } + + // sets lambda to be used in fun *and* executes benchmark! + template <typename Fun, std::enable_if_t<!Detail::is_related_v<Fun, Benchmark>, int> = 0> + Benchmark & operator=(Fun func) { + auto const* cfg = getCurrentContext().getConfig(); + if (!cfg->skipBenchmarks()) { + fun = Detail::BenchmarkFunction(func); + run(); + } + return *this; + } + + explicit operator bool() { + return true; + } + + private: + Detail::BenchmarkFunction fun; + std::string name; + }; + } +} // namespace Catch + +#define INTERNAL_CATCH_GET_1_ARG(arg1, arg2, ...) arg1 +#define INTERNAL_CATCH_GET_2_ARG(arg1, arg2, ...) arg2 + +#define INTERNAL_CATCH_BENCHMARK(BenchmarkName, name, benchmarkIndex)\ + if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \ + BenchmarkName = [&](int benchmarkIndex) + +#define INTERNAL_CATCH_BENCHMARK_ADVANCED(BenchmarkName, name)\ + if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \ + BenchmarkName = [&] + +#if defined(CATCH_CONFIG_PREFIX_ALL) + +#define CATCH_BENCHMARK(...) \ + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) +#define CATCH_BENCHMARK_ADVANCED(name) \ + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name) + +#else + +#define BENCHMARK(...) \ + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) +#define BENCHMARK_ADVANCED(name) \ + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name) + +#endif + diff --git a/src/Progmasoft/Catch3/Benchmark/Chronometer.cpp b/src/Progmasoft/Catch3/Benchmark/Chronometer.cpp new file mode 100644 index 00000000..78cbb97e --- /dev/null +++ b/src/Progmasoft/Catch3/Benchmark/Chronometer.cpp @@ -0,0 +1,17 @@ + +// 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 <Progmasoft/Catch3/Benchmark/Chronometer.hpp> + +namespace Catch { + namespace Benchmark { + namespace Detail { + ChronometerConcept::~ChronometerConcept() = default; + } // namespace Detail + } // namespace Benchmark +} // namespace Catch diff --git a/src/Progmasoft/Catch3/Benchmark/Chronometer.hpp b/src/Progmasoft/Catch3/Benchmark/Chronometer.hpp new file mode 100644 index 00000000..e5550e57 --- /dev/null +++ b/src/Progmasoft/Catch3/Benchmark/Chronometer.hpp @@ -0,0 +1,75 @@ + +// 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 +// Adapted from donated nonius code. + +#pragma once + +#include <Progmasoft/Catch3/Benchmark/Clock.hpp> +#include <Progmasoft/Catch3/Benchmark/Optimizer.hpp> +#include <Progmasoft/Catch3/Internal/Meta.hpp> +#include <Progmasoft/Catch3/Internal/MoveAndForward.hpp> + +namespace Catch { + namespace Benchmark { + namespace Detail { + struct ChronometerConcept { + virtual void start() = 0; + virtual void finish() = 0; + virtual ~ChronometerConcept(); // = default; + + ChronometerConcept() = default; + ChronometerConcept(ChronometerConcept const&) = default; + ChronometerConcept& operator=(ChronometerConcept const&) = default; + }; + template <typename Clock> + struct ChronometerModel final : public ChronometerConcept { + void start() override { started = Clock::now(); } + void finish() override { finished = Clock::now(); } + + IDuration elapsed() const { + return std::chrono::duration_cast<std::chrono::nanoseconds>( + finished - started ); + } + + TimePoint<Clock> started; + TimePoint<Clock> finished; + }; + } // namespace Detail + + struct Chronometer { + public: + template <typename Fun> + void measure(Fun&& fun) { measure(CATCH_FORWARD(fun), is_callable<Fun(int)>()); } + + int runs() const { return repeats; } + + Chronometer(Detail::ChronometerConcept& meter, int repeats_) + : impl(&meter) + , repeats(repeats_) {} + + private: + template <typename Fun> + void measure(Fun&& fun, std::false_type) { + measure([&fun](int) { return fun(); }, std::true_type()); + } + + template <typename Fun> + void measure(Fun&& fun, std::true_type) { + Detail::optimizer_barrier(); + impl->start(); + for (int i = 0; i < repeats; ++i) invoke_deoptimized(fun, i); + impl->finish(); + Detail::optimizer_barrier(); + } + + Detail::ChronometerConcept* impl; + int repeats; + }; + } // namespace Benchmark +} // namespace Catch + diff --git a/src/Progmasoft/Catch3/Benchmark/Clock.hpp b/src/Progmasoft/Catch3/Benchmark/Clock.hpp new file mode 100644 index 00000000..b2cca2e9 --- /dev/null +++ b/src/Progmasoft/Catch3/Benchmark/Clock.hpp @@ -0,0 +1,25 @@ + +// 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 +// Adapted from donated nonius code. + +#pragma once + +#include <chrono> + +namespace Catch { + namespace Benchmark { + using IDuration = std::chrono::nanoseconds; + using FDuration = std::chrono::duration<double, std::nano>; + + template <typename Clock> + using TimePoint = typename Clock::time_point; + + using default_clock = std::chrono::steady_clock; + } // namespace Benchmark +} // namespace Catch + diff --git a/src/Progmasoft/Catch3/Benchmark/Constructor.hpp b/src/Progmasoft/Catch3/Benchmark/Constructor.hpp new file mode 100644 index 00000000..5d054620 --- /dev/null +++ b/src/Progmasoft/Catch3/Benchmark/Constructor.hpp @@ -0,0 +1,80 @@ + +// 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 +// Adapted from donated nonius code. + +#pragma once + +#include <Progmasoft/Catch3/Internal/MoveAndForward.hpp> + +#include <type_traits> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename T, bool Destruct> + struct ObjectStorage + { + ObjectStorage() = default; + + ObjectStorage(const ObjectStorage& other) + { + new(&data) T(other.stored_object()); + } + + ObjectStorage(ObjectStorage&& other) + { + new(data) T(CATCH_MOVE(other.stored_object())); + } + + ~ObjectStorage() { destruct_on_exit<T>(); } + + template <typename... Args> + void construct(Args&&... args) + { + new (data) T(CATCH_FORWARD(args)...); + } + + template <bool AllowManualDestruction = !Destruct> + std::enable_if_t<AllowManualDestruction> destruct() + { + stored_object().~T(); + } + + private: + // If this is a constructor benchmark, destruct the underlying object + template <typename U> + void destruct_on_exit(std::enable_if_t<Destruct, U>* = nullptr) { destruct<true>(); } + // Otherwise, don't + template <typename U> + void destruct_on_exit(std::enable_if_t<!Destruct, U>* = nullptr) { } + +#if defined( __GNUC__ ) && __GNUC__ <= 6 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + T& stored_object() { return *reinterpret_cast<T*>( data ); } + + T const& stored_object() const { + return *reinterpret_cast<T const*>( data ); + } +#if defined( __GNUC__ ) && __GNUC__ <= 6 +# pragma GCC diagnostic pop +#endif + + alignas( T ) unsigned char data[sizeof( T )]{}; + }; + } // namespace Detail + + template <typename T> + using storage_for = Detail::ObjectStorage<T, true>; + + template <typename T> + using destructable_object = Detail::ObjectStorage<T, false>; + } // namespace Benchmark +} // namespace Catch + diff --git a/src/Progmasoft/Catch3/Benchmark/Detail/Analyse.cpp b/src/Progmasoft/Catch3/Benchmark/Detail/Analyse.cpp new file mode 100644 index 00000000..eb1e2847 --- /dev/null +++ b/src/Progmasoft/Catch3/Benchmark/Detail/Analyse.cpp @@ -0,0 +1,85 @@ + +// 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 +// Adapted from donated nonius code. + +#include <Progmasoft/Catch3/Benchmark/Detail/Analyse.hpp> +#include <Progmasoft/Catch3/Benchmark/Clock.hpp> +#include <Progmasoft/Catch3/Benchmark/SampleAnalysis.hpp> +#include <Progmasoft/Catch3/Benchmark/Detail/Stats.hpp> +#include <Progmasoft/Catch3/Interfaces/IConfig.hpp> +#include <Progmasoft/Catch3/Internal/MoveAndForward.hpp> + +#include <vector> + +namespace Catch { + namespace Benchmark { + namespace Detail { + SampleAnalysis analyse(const IConfig &cfg, FDuration* first, FDuration* last) { + if (!cfg.benchmarkNoAnalysis()) { + std::vector<double> samples; + samples.reserve(static_cast<size_t>(last - first)); + for (auto current = first; current != last; ++current) { + samples.push_back( current->count() ); + } + + auto analysis = Catch::Benchmark::Detail::analyse_samples( + cfg.benchmarkConfidenceInterval(), + cfg.benchmarkResamples(), + samples.data(), + samples.data() + samples.size() ); + auto outliers = Catch::Benchmark::Detail::classify_outliers( + samples.data(), samples.data() + samples.size() ); + + auto wrap_estimate = [](Estimate<double> e) { + return Estimate<FDuration>{ + FDuration( e.point ), + FDuration( e.lower_bound ), + FDuration( e.upper_bound ), + e.confidence_interval, + }; + }; + std::vector<FDuration> samples2; + samples2.reserve(samples.size()); + for (auto s : samples) { + samples2.push_back( FDuration( s ) ); + } + + return { + CATCH_MOVE(samples2), + wrap_estimate(analysis.mean), + wrap_estimate(analysis.standard_deviation), + outliers, + analysis.outlier_variance, + }; + } else { + std::vector<FDuration> samples; + samples.reserve(static_cast<size_t>(last - first)); + + FDuration mean = FDuration(0); + int i = 0; + for (auto it = first; it < last; ++it, ++i) { + samples.push_back(*it); + mean += *it; + } + mean /= i; + + return SampleAnalysis{ + |