Reduce the number of templates in Benchmarking

The basic idea was to reduce the number of things dependent on the `Clock`
type. To that end, I replaced `Duration<Clock>` with `IDuration` typedef
for `std::nanoseconds`, and `FloatDuration<Clock>` with `FDuration`
typedef for `Duration<double, std::nano>`. We can generally assume that
any clock's duration can be expressed in nanoseconds, as long as we insert
`duration_cast`s into the right places.

Note that we cannot remove all dependence on `Clock` as a template
arguments, because functions that actually measure the elapsed time have
to use the Clock.

We also changed some template function arguments to pass plain function
pointers, so that the actual implementation can be placed into a cpp file.
This commit is contained in:
Martin Hořeňovský 2023-08-31 21:52:08 +02:00
parent fb96279aed
commit 47a2c96938
No known key found for this signature in database
GPG Key ID: DE48307B8B0D381A
20 changed files with 254 additions and 261 deletions

View File

@ -33,6 +33,7 @@ set(BENCHMARK_HEADERS
) )
set(BENCHMARK_SOURCES set(BENCHMARK_SOURCES
${SOURCES_DIR}/benchmark/catch_chronometer.cpp ${SOURCES_DIR}/benchmark/catch_chronometer.cpp
${SOURCES_DIR}/benchmark/detail/catch_analyse.cpp
${SOURCES_DIR}/benchmark/detail/catch_benchmark_function.cpp ${SOURCES_DIR}/benchmark/detail/catch_benchmark_function.cpp
${SOURCES_DIR}/benchmark/detail/catch_run_for_at_least.cpp ${SOURCES_DIR}/benchmark/detail/catch_run_for_at_least.cpp
${SOURCES_DIR}/benchmark/detail/catch_stats.cpp ${SOURCES_DIR}/benchmark/detail/catch_stats.cpp

View File

@ -45,16 +45,18 @@ namespace Catch {
: fun(CATCH_MOVE(func)), name(CATCH_MOVE(benchmarkName)) {} : fun(CATCH_MOVE(func)), name(CATCH_MOVE(benchmarkName)) {}
template <typename Clock> template <typename Clock>
ExecutionPlan<FloatDuration<Clock>> prepare(const IConfig &cfg, Environment<FloatDuration<Clock>> env) const { ExecutionPlan prepare(const IConfig &cfg, Environment env) const {
auto min_time = env.clock_resolution.mean * Detail::minimum_ticks; 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 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<ClockDuration<Clock>>(run_time), 1, fun); 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)); 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(), fun, std::chrono::duration_cast<FloatDuration<Clock>>(cfg.benchmarkWarmupTime()), Detail::warmup_iterations }; return { new_iters, test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), fun, std::chrono::duration_cast<FDuration>(cfg.benchmarkWarmupTime()), Detail::warmup_iterations };
} }
template <typename Clock = default_clock> template <typename Clock = default_clock>
void run() { void run() {
static_assert( Clock::is_steady,
"Benchmarking clock should be steady" );
auto const* cfg = getCurrentContext().getConfig(); auto const* cfg = getCurrentContext().getConfig();
auto env = Detail::measure_environment<Clock>(); auto env = Detail::measure_environment<Clock>();
@ -81,8 +83,8 @@ namespace Catch {
return plan.template run<Clock>(*cfg, env); return plan.template run<Clock>(*cfg, env);
}); });
auto analysis = Detail::analyse(*cfg, env, samples.begin(), samples.end()); auto analysis = Detail::analyse(*cfg, samples.data(), samples.data() + samples.size());
BenchmarkStats<FloatDuration<Clock>> stats{ CATCH_MOVE(info), CATCH_MOVE(analysis.samples), analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance }; BenchmarkStats<> stats{ CATCH_MOVE(info), CATCH_MOVE(analysis.samples), analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance };
getResultCapture().benchmarkEnded(stats); getResultCapture().benchmarkEnded(stats);
} CATCH_CATCH_ANON (TestFailureException const&) { } CATCH_CATCH_ANON (TestFailureException const&) {
getResultCapture().benchmarkFailed("Benchmark failed due to failed assertion"_sr); getResultCapture().benchmarkFailed("Benchmark failed due to failed assertion"_sr);

View File

@ -32,7 +32,10 @@ namespace Catch {
void start() override { started = Clock::now(); } void start() override { started = Clock::now(); }
void finish() override { finished = Clock::now(); } void finish() override { finished = Clock::now(); }
ClockDuration<Clock> elapsed() const { return finished - started; } IDuration elapsed() const {
return std::chrono::duration_cast<std::chrono::nanoseconds>(
finished - started );
}
TimePoint<Clock> started; TimePoint<Clock> started;
TimePoint<Clock> finished; TimePoint<Clock> finished;

View File

@ -11,28 +11,16 @@
#define CATCH_CLOCK_HPP_INCLUDED #define CATCH_CLOCK_HPP_INCLUDED
#include <chrono> #include <chrono>
#include <ratio>
namespace Catch { namespace Catch {
namespace Benchmark { namespace Benchmark {
template <typename Clock> using IDuration = std::chrono::nanoseconds;
using ClockDuration = typename Clock::duration; using FDuration = std::chrono::duration<double, std::nano>;
template <typename Clock>
using FloatDuration = std::chrono::duration<double, typename Clock::period>;
template <typename Clock> template <typename Clock>
using TimePoint = typename Clock::time_point; using TimePoint = typename Clock::time_point;
using default_clock = std::chrono::steady_clock; using default_clock = std::chrono::steady_clock;
template <typename Clock>
struct now {
TimePoint<Clock> operator()() const {
return Clock::now();
}
};
using fp_seconds = std::chrono::duration<double, std::ratio<1>>;
} // namespace Benchmark } // namespace Benchmark
} // namespace Catch } // namespace Catch

View File

@ -15,20 +15,13 @@
namespace Catch { namespace Catch {
namespace Benchmark { namespace Benchmark {
template <typename Duration>
struct EnvironmentEstimate { struct EnvironmentEstimate {
Duration mean; FDuration mean;
OutlierClassification outliers; OutlierClassification outliers;
template <typename Duration2>
operator EnvironmentEstimate<Duration2>() const {
return { mean, outliers };
}
}; };
template <typename Clock>
struct Environment { struct Environment {
EnvironmentEstimate<FloatDuration<Clock>> clock_resolution; EnvironmentEstimate clock_resolution;
EnvironmentEstimate<FloatDuration<Clock>> clock_cost; EnvironmentEstimate clock_cost;
}; };
} // namespace Benchmark } // namespace Benchmark
} // namespace Catch } // namespace Catch

View File

@ -12,17 +12,12 @@
namespace Catch { namespace Catch {
namespace Benchmark { namespace Benchmark {
template <typename Duration> template <typename Type>
struct Estimate { struct Estimate {
Duration point; Type point;
Duration lower_bound; Type lower_bound;
Duration upper_bound; Type upper_bound;
double confidence_interval; double confidence_interval;
template <typename Duration2>
operator Estimate<Duration2>() const {
return { point, lower_bound, upper_bound, confidence_interval };
}
}; };
} // namespace Benchmark } // namespace Benchmark
} // namespace Catch } // namespace Catch

View File

@ -21,33 +21,31 @@
namespace Catch { namespace Catch {
namespace Benchmark { namespace Benchmark {
template <typename Duration>
struct ExecutionPlan { struct ExecutionPlan {
int iterations_per_sample; int iterations_per_sample;
Duration estimated_duration; FDuration estimated_duration;
Detail::BenchmarkFunction benchmark; Detail::BenchmarkFunction benchmark;
Duration warmup_time; FDuration warmup_time;
int warmup_iterations; int warmup_iterations;
template <typename Duration2>
operator ExecutionPlan<Duration2>() const {
return { iterations_per_sample, estimated_duration, benchmark, warmup_time, warmup_iterations };
}
template <typename Clock> template <typename Clock>
std::vector<FloatDuration<Clock>> run(const IConfig &cfg, Environment<FloatDuration<Clock>> env) const { std::vector<FDuration> run(const IConfig &cfg, Environment env) const {
// warmup a bit // warmup a bit
Detail::run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(warmup_time), warmup_iterations, Detail::repeat(now<Clock>{})); Detail::run_for_at_least<Clock>(
std::chrono::duration_cast<IDuration>( warmup_time ),
warmup_iterations,
Detail::repeat( []() { return Clock::now(); } )
);
std::vector<FloatDuration<Clock>> times; std::vector<FDuration> times;
const auto num_samples = cfg.benchmarkSamples(); const auto num_samples = cfg.benchmarkSamples();
times.reserve( num_samples ); times.reserve( num_samples );
for ( size_t i = 0; i < num_samples; ++i ) { for ( size_t i = 0; i < num_samples; ++i ) {
Detail::ChronometerModel<Clock> model; Detail::ChronometerModel<Clock> model;
this->benchmark( Chronometer( model, iterations_per_sample ) ); this->benchmark( Chronometer( model, iterations_per_sample ) );
auto sample_time = model.elapsed() - env.clock_cost.mean; auto sample_time = model.elapsed() - env.clock_cost.mean;
if ( sample_time < FloatDuration<Clock>::zero() ) { if ( sample_time < FDuration::zero() ) {
sample_time = FloatDuration<Clock>::zero(); sample_time = FDuration::zero();
} }
times.push_back(sample_time / iterations_per_sample); times.push_back(sample_time / iterations_per_sample);
} }

View File

@ -12,35 +12,18 @@
#include <catch2/benchmark/catch_estimate.hpp> #include <catch2/benchmark/catch_estimate.hpp>
#include <catch2/benchmark/catch_outlier_classification.hpp> #include <catch2/benchmark/catch_outlier_classification.hpp>
#include <catch2/internal/catch_move_and_forward.hpp> #include <catch2/benchmark/catch_clock.hpp>
#include <vector> #include <vector>
namespace Catch { namespace Catch {
namespace Benchmark { namespace Benchmark {
template <typename Duration>
struct SampleAnalysis { struct SampleAnalysis {
std::vector<Duration> samples; std::vector<FDuration> samples;
Estimate<Duration> mean; Estimate<FDuration> mean;
Estimate<Duration> standard_deviation; Estimate<FDuration> standard_deviation;
OutlierClassification outliers; OutlierClassification outliers;
double outlier_variance; double outlier_variance;
template <typename Duration2>
operator SampleAnalysis<Duration2>() const {
std::vector<Duration2> samples2;
samples2.reserve(samples.size());
for (auto const& d : samples) {
samples2.push_back(Duration2(d));
}
return {
CATCH_MOVE(samples2),
mean,
standard_deviation,
outliers,
outlier_variance,
};
}
}; };
} // namespace Benchmark } // namespace Benchmark
} // namespace Catch } // namespace Catch

View File

@ -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 <catch2/benchmark/detail/catch_analyse.hpp>
#include <catch2/benchmark/catch_clock.hpp>
#include <catch2/benchmark/catch_sample_analysis.hpp>
#include <catch2/benchmark/detail/catch_stats.hpp>
#include <catch2/interfaces/catch_interfaces_config.hpp>
#include <catch2/internal/catch_move_and_forward.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(FDuration(*it));
mean += FDuration(*it);
}
mean /= i;
return SampleAnalysis{
CATCH_MOVE(samples),
Estimate<FDuration>{ mean, mean, mean, 0.0 },
Estimate<FDuration>{ FDuration( 0 ),
FDuration( 0 ),
FDuration( 0 ),
0.0 },
OutlierClassification{},
0.0
};
}
}
} // namespace Detail
} // namespace Benchmark
} // namespace Catch

View File

@ -10,76 +10,16 @@
#ifndef CATCH_ANALYSE_HPP_INCLUDED #ifndef CATCH_ANALYSE_HPP_INCLUDED
#define CATCH_ANALYSE_HPP_INCLUDED #define CATCH_ANALYSE_HPP_INCLUDED
#include <catch2/benchmark/catch_environment.hpp> #include <catch2/benchmark/catch_clock.hpp>
#include <catch2/benchmark/catch_sample_analysis.hpp> #include <catch2/benchmark/catch_sample_analysis.hpp>
#include <catch2/benchmark/detail/catch_stats.hpp>
#include <catch2/interfaces/catch_interfaces_config.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
#include <vector>
namespace Catch { namespace Catch {
class IConfig;
namespace Benchmark { namespace Benchmark {
namespace Detail { namespace Detail {
template <typename Duration, typename Iterator> SampleAnalysis analyse(const IConfig &cfg, FDuration* first, FDuration* last);
SampleAnalysis<Duration> analyse(const IConfig &cfg, Environment<Duration>, Iterator first, Iterator 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<Duration> {
Duration(e.point),
Duration(e.lower_bound),
Duration(e.upper_bound),
e.confidence_interval,
};
};
std::vector<Duration> samples2;
samples2.reserve(samples.size());
for (auto s : samples) {
samples2.push_back( Duration( s ) );
}
return {
CATCH_MOVE(samples2),
wrap_estimate(analysis.mean),
wrap_estimate(analysis.standard_deviation),
outliers,
analysis.outlier_variance,
};
} else {
std::vector<Duration> samples;
samples.reserve(static_cast<size_t>(last - first));
Duration mean = Duration(0);
int i = 0;
for (auto it = first; it < last; ++it, ++i) {
samples.push_back(Duration(*it));
mean += Duration(*it);
}
mean /= i;
return {
CATCH_MOVE(samples),
Estimate<Duration>{mean, mean, mean, 0.0},
Estimate<Duration>{Duration(0), Duration(0), Duration(0), 0.0},
OutlierClassification{},
0.0
};
}
}
} // namespace Detail } // namespace Detail
} // namespace Benchmark } // namespace Benchmark
} // namespace Catch } // namespace Catch

View File

@ -8,7 +8,6 @@
#ifndef CATCH_BENCHMARK_STATS_HPP_INCLUDED #ifndef CATCH_BENCHMARK_STATS_HPP_INCLUDED
#define CATCH_BENCHMARK_STATS_HPP_INCLUDED #define CATCH_BENCHMARK_STATS_HPP_INCLUDED
#include <catch2/internal/catch_move_and_forward.hpp>
#include <catch2/benchmark/catch_estimate.hpp> #include <catch2/benchmark/catch_estimate.hpp>
#include <catch2/benchmark/catch_outlier_classification.hpp> #include <catch2/benchmark/catch_outlier_classification.hpp>
// The fwd decl & default specialization needs to be seen by VS2017 before // The fwd decl & default specialization needs to be seen by VS2017 before
@ -30,32 +29,17 @@ namespace Catch {
double clockCost; double clockCost;
}; };
template <class Duration> // We need to keep template parameter for backwards compatibility,
// but we also do not want to use the template paraneter.
template <class Dummy>
struct BenchmarkStats { struct BenchmarkStats {
BenchmarkInfo info; BenchmarkInfo info;
std::vector<Duration> samples; std::vector<Benchmark::FDuration> samples;
Benchmark::Estimate<Duration> mean; Benchmark::Estimate<Benchmark::FDuration> mean;
Benchmark::Estimate<Duration> standardDeviation; Benchmark::Estimate<Benchmark::FDuration> standardDeviation;
Benchmark::OutlierClassification outliers; Benchmark::OutlierClassification outliers;
double outlierVariance; double outlierVariance;
template <typename Duration2>
operator BenchmarkStats<Duration2>() const {
std::vector<Duration2> samples2;
samples2.reserve(samples.size());
for (auto const& sample : samples) {
samples2.push_back(Duration2(sample));
}
return {
info,
CATCH_MOVE(samples2),
mean,
standardDeviation,
outliers,
outlierVariance,
};
}
}; };

View File

@ -8,14 +8,14 @@
#ifndef CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED #ifndef CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED
#define CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED #define CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED
#include <chrono> #include <catch2/benchmark/catch_clock.hpp>
namespace Catch { namespace Catch {
// We cannot forward declare the type with default template argument // We cannot forward declare the type with default template argument
// multiple times, so it is split out into a separate header so that // multiple times, so it is split out into a separate header so that
// we can prevent multiple declarations in dependees // we can prevent multiple declarations in dependees
template <typename Duration = std::chrono::duration<double, std::nano>> template <typename Duration = Benchmark::FDuration>
struct BenchmarkStats; struct BenchmarkStats;
} // end namespace Catch } // end namespace Catch

View File

@ -55,23 +55,23 @@ namespace Catch {
template <typename Clock> template <typename Clock>
int warmup() { int warmup() {
return run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(warmup_time), warmup_seed, &resolution<Clock>) return run_for_at_least<Clock>(std::chrono::duration_cast<IDuration>(warmup_time), warmup_seed, &resolution<Clock>)
.iterations; .iterations;
} }
template <typename Clock> template <typename Clock>
EnvironmentEstimate<FloatDuration<Clock>> estimate_clock_resolution(int iterations) { EnvironmentEstimate estimate_clock_resolution(int iterations) {
auto r = run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(clock_resolution_estimation_time), iterations, &resolution<Clock>) auto r = run_for_at_least<Clock>(std::chrono::duration_cast<IDuration>(clock_resolution_estimation_time), iterations, &resolution<Clock>)
.result; .result;
return { return {
FloatDuration<Clock>(mean(r.data(), r.data() + r.size())), FDuration(mean(r.data(), r.data() + r.size())),
classify_outliers(r.data(), r.data() + r.size()), classify_outliers(r.data(), r.data() + r.size()),
}; };
} }
template <typename Clock> template <typename Clock>
EnvironmentEstimate<FloatDuration<Clock>> estimate_clock_cost(FloatDuration<Clock> resolution) { EnvironmentEstimate estimate_clock_cost(FDuration resolution) {
auto time_limit = (std::min)( auto time_limit = (std::min)(
resolution * clock_cost_estimation_tick_limit, resolution * clock_cost_estimation_tick_limit,
FloatDuration<Clock>(clock_cost_estimation_time_limit)); FDuration(clock_cost_estimation_time_limit));
auto time_clock = [](int k) { auto time_clock = [](int k) {
return Detail::measure<Clock>([k] { return Detail::measure<Clock>([k] {
for (int i = 0; i < k; ++i) { for (int i = 0; i < k; ++i) {
@ -82,7 +82,7 @@ namespace Catch {
}; };
time_clock(1); time_clock(1);
int iters = clock_cost_estimation_iterations; int iters = clock_cost_estimation_iterations;
auto&& r = run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(clock_cost_estimation_time), iters, time_clock); auto&& r = run_for_at_least<Clock>(std::chrono::duration_cast<IDuration>(clock_cost_estimation_time), iters, time_clock);
std::vector<double> times; std::vector<double> times;
int nsamples = static_cast<int>(std::ceil(time_limit / r.elapsed)); int nsamples = static_cast<int>(std::ceil(time_limit / r.elapsed));
times.reserve(static_cast<size_t>(nsamples)); times.reserve(static_cast<size_t>(nsamples));
@ -92,18 +92,18 @@ namespace Catch {
.count() ) ); .count() ) );
} }
return { return {
FloatDuration<Clock>(mean(times.data(), times.data() + times.size())), FDuration(mean(times.data(), times.data() + times.size())),
classify_outliers(times.data(), times.data() + times.size()), classify_outliers(times.data(), times.data() + times.size()),
}; };
} }
template <typename Clock> template <typename Clock>
Environment<FloatDuration<Clock>> measure_environment() { Environment measure_environment() {
#if defined(__clang__) #if defined(__clang__)
# pragma clang diagnostic push # pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wexit-time-destructors" # pragma clang diagnostic ignored "-Wexit-time-destructors"
#endif #endif
static Catch::Detail::unique_ptr<Environment<FloatDuration<Clock>>> env; static Catch::Detail::unique_ptr<Environment> env;
#if defined(__clang__) #if defined(__clang__)
# pragma clang diagnostic pop # pragma clang diagnostic pop
#endif #endif
@ -115,7 +115,7 @@ namespace Catch {
auto resolution = Detail::estimate_clock_resolution<Clock>(iters); auto resolution = Detail::estimate_clock_resolution<Clock>(iters);
auto cost = Detail::estimate_clock_cost<Clock>(resolution.mean); auto cost = Detail::estimate_clock_cost<Clock>(resolution.mean);
env = Catch::Detail::make_unique<Environment<FloatDuration<Clock>>>( Environment<FloatDuration<Clock>>{resolution, cost} ); env = Catch::Detail::make_unique<Environment>( Environment{resolution, cost} );
return *env; return *env;
} }
} // namespace Detail } // namespace Detail

View File

@ -18,7 +18,7 @@ namespace Catch {
namespace Benchmark { namespace Benchmark {
namespace Detail { namespace Detail {
template <typename Clock, typename Fun, typename... Args> template <typename Clock, typename Fun, typename... Args>
TimingOf<Clock, Fun, Args...> measure(Fun&& fun, Args&&... args) { TimingOf<Fun, Args...> measure(Fun&& fun, Args&&... args) {
auto start = Clock::now(); auto start = Clock::now();
auto&& r = Detail::complete_invoke(fun, CATCH_FORWARD(args)...); auto&& r = Detail::complete_invoke(fun, CATCH_FORWARD(args)...);
auto end = Clock::now(); auto end = Clock::now();

View File

@ -24,11 +24,11 @@ namespace Catch {
namespace Benchmark { namespace Benchmark {
namespace Detail { namespace Detail {
template <typename Clock, typename Fun> template <typename Clock, typename Fun>
TimingOf<Clock, Fun, int> measure_one(Fun&& fun, int iters, std::false_type) { TimingOf<Fun, int> measure_one(Fun&& fun, int iters, std::false_type) {
return Detail::measure<Clock>(fun, iters); return Detail::measure<Clock>(fun, iters);
} }
template <typename Clock, typename Fun> template <typename Clock, typename Fun>
TimingOf<Clock, Fun, Chronometer> measure_one(Fun&& fun, int iters, std::true_type) { TimingOf<Fun, Chronometer> measure_one(Fun&& fun, int iters, std::true_type) {
Detail::ChronometerModel<Clock> meter; Detail::ChronometerModel<Clock> meter;
auto&& result = Detail::complete_invoke(fun, Chronometer(meter, iters)); auto&& result = Detail::complete_invoke(fun, Chronometer(meter, iters));
@ -43,8 +43,8 @@ namespace Catch {
void throw_optimized_away_error(); void throw_optimized_away_error();
template <typename Clock, typename Fun> template <typename Clock, typename Fun>
TimingOf<Clock, Fun, run_for_at_least_argument_t<Clock, Fun>> TimingOf<Fun, run_for_at_least_argument_t<Clock, Fun>>
run_for_at_least(ClockDuration<Clock> how_long, run_for_at_least(IDuration how_long,
const int initial_iterations, const int initial_iterations,
Fun&& fun) { Fun&& fun) {
auto iters = initial_iterations; auto iters = initial_iterations;

View File

@ -11,7 +11,9 @@
#include <catch2/internal/catch_compiler_capabilities.hpp> #include <catch2/internal/catch_compiler_capabilities.hpp>
#include <algorithm>
#include <cassert> #include <cassert>
#include <cmath>
#include <cstddef> #include <cstddef>
#include <numeric> #include <numeric>
#include <random> #include <random>
@ -257,6 +259,27 @@ namespace Catch {
return sum / static_cast<double>(count); return sum / static_cast<double>(count);
} }
sample jackknife( double ( *estimator )( double const*,
double const* ),
double* first,
double* last ) {
auto n = static_cast<size_t>( last - first );
auto second = first;
++second;
sample results;
results.reserve( n );
for ( auto it = first; it != last; ++it ) {
std::iter_swap( it, first );
results.push_back( estimator( second, last ) );
}
return results;
}
double normal_cdf( double x ) {
return std::erfc( -x / std::sqrt( 2.0 ) ) / 2.0;
}
double erfc_inv(double x) { double erfc_inv(double x) {
return erf_inv(1.0 - x); return erf_inv(1.0 - x);
@ -278,6 +301,64 @@ namespace Catch {
return result; return result;
} }
Estimate<double>
bootstrap( double confidence_level,
double* first,
double* last,
sample const& resample,
double ( *estimator )( double const*, double const* ) ) {
auto n_samples = last - first;
double point = estimator( first, last );
// Degenerate case with a single sample
if ( n_samples == 1 )
return { point, point, point, confidence_level };
sample jack = jackknife( estimator, first, last );
double jack_mean =
mean( jack.data(), jack.data() + jack.size() );
double sum_squares = 0, sum_cubes = 0;
for ( double x : jack ) {
auto difference = jack_mean - x;
auto square = difference * difference;
auto cube = square * difference;
sum_squares += square;
sum_cubes += cube;
}
double accel = sum_cubes / ( 6 * std::pow( sum_squares, 1.5 ) );
long n = static_cast<long>( resample.size() );
double prob_n =
std::count_if( resample.begin(),
resample.end(),
[point]( double x ) { return x < point; } ) /
static_cast<double>( n );
// degenerate case with uniform samples
if ( directCompare( prob_n, 0. ) ) {
return { point, point, point, confidence_level };
}
double bias = normal_quantile( prob_n );
double z1 = normal_quantile( ( 1. - confidence_level ) / 2. );
auto cumn = [n]( double x ) -> long {
return std::lround( normal_cdf( x ) *
static_cast<double>( n ) );
};
auto a = [bias, accel]( double b ) {
return bias + b / ( 1. - accel * b );
};
double b1 = bias + z1;
double b2 = bias - z1;
double a1 = a( b1 );
double a2 = a( b2 );
auto lo = static_cast<size_t>( (std::max)( cumn( a1 ), 0l ) );
auto hi =
static_cast<size_t>( (std::min)( cumn( a2 ), n - 1 ) );
return { point, resample[lo], resample[hi], confidence_level };
}
bootstrap_analysis analyse_samples(double confidence_level, bootstrap_analysis analyse_samples(double confidence_level,
unsigned int n_resamples, unsigned int n_resamples,
double* first, double* first,

View File

@ -13,9 +13,7 @@
#include <catch2/benchmark/catch_estimate.hpp> #include <catch2/benchmark/catch_estimate.hpp>
#include <catch2/benchmark/catch_outlier_classification.hpp> #include <catch2/benchmark/catch_outlier_classification.hpp>
#include <algorithm>
#include <vector> #include <vector>
#include <cmath>
namespace Catch { namespace Catch {
namespace Benchmark { namespace Benchmark {
@ -36,78 +34,23 @@ namespace Catch {
double mean( double const* first, double const* last ); double mean( double const* first, double const* last );
template <typename Estimator> sample jackknife( double ( *estimator )( double const*,
sample jackknife(Estimator&& estimator, double const* ),
double* first, double* first,
double* last) { double* last );
auto n = static_cast<size_t>(last - first);
auto second = first;
++second;
sample results;
results.reserve(n);
for (auto it = first; it != last; ++it) { double normal_cdf( double x );
std::iter_swap(it, first);
results.push_back(estimator(second, last));
}
return results;
}
inline double normal_cdf(double x) {
return std::erfc(-x / std::sqrt(2.0)) / 2.0;
}
double erfc_inv(double x); double erfc_inv(double x);
double normal_quantile(double p); double normal_quantile(double p);
template <typename Estimator> Estimate<double>
Estimate<double> bootstrap( double confidence_level, bootstrap( double confidence_level,
double* first, double* first,
double* last, double* last,
sample const& resample, sample const& resample,
Estimator&& estimator ) { double ( *estimator )( double const*, double const* ) );
auto n_samples = last - first;
double point = estimator(first, last);
// Degenerate case with a single sample
if (n_samples == 1) return { point, point, point, confidence_level };
sample jack = jackknife(estimator, first, last);
double jack_mean = mean(jack.data(), jack.data() + jack.size());
double sum_squares = 0, sum_cubes = 0;
for (double x : jack) {
auto difference = jack_mean - x;
auto square = difference * difference;
auto cube = square * difference;
sum_squares += square; sum_cubes += cube;
}
double accel = sum_cubes / (6 * std::pow(sum_squares, 1.5));
long n = static_cast<long>(resample.size());
double prob_n = std::count_if(resample.begin(), resample.end(), [point](double x) { return x < point; }) / static_cast<double>(n);
// degenerate case with uniform samples
if ( directCompare( prob_n, 0. ) ) {
return { point, point, point, confidence_level };
}
double bias = normal_quantile(prob_n);
double z1 = normal_quantile((1. - confidence_level) / 2.);
auto cumn = [n]( double x ) -> long {
return std::lround( normal_cdf( x ) * static_cast<double>(n) );
};
auto a = [bias, accel](double b) { return bias + b / (1. - accel * b); };
double b1 = bias + z1;
double b2 = bias - z1;
double a1 = a(b1);
double a2 = a(b2);
auto lo = static_cast<size_t>((std::max)(cumn(a1), 0l));
auto hi = static_cast<size_t>((std::min)(cumn(a2), n - 1));
return { point, resample[lo], resample[hi], confidence_level };
}
struct bootstrap_analysis { struct bootstrap_analysis {
Estimate<double> mean; Estimate<double> mean;

View File

@ -17,14 +17,14 @@
namespace Catch { namespace Catch {
namespace Benchmark { namespace Benchmark {
template <typename Duration, typename Result> template <typename Result>
struct Timing { struct Timing {
Duration elapsed; IDuration elapsed;
Result result; Result result;
int iterations; int iterations;
}; };
template <typename Clock, typename Func, typename... Args> template <typename Func, typename... Args>
using TimingOf = Timing<ClockDuration<Clock>, Detail::CompleteType_t<FunctionReturnType<Func, Args...>>>; using TimingOf = Timing<Detail::CompleteType_t<FunctionReturnType<Func, Args...>>>;
} // namespace Benchmark } // namespace Benchmark
} // namespace Catch } // namespace Catch

View File

@ -45,6 +45,7 @@ benchmark_headers = [
benchmark_sources = files( benchmark_sources = files(
'benchmark/catch_chronometer.cpp', 'benchmark/catch_chronometer.cpp',
'benchmark/detail/catch_analyse.cpp',
'benchmark/detail/catch_benchmark_function.cpp', 'benchmark/detail/catch_benchmark_function.cpp',
'benchmark/detail/catch_run_for_at_least.cpp', 'benchmark/detail/catch_run_for_at_least.cpp',
'benchmark/detail/catch_stats.cpp', 'benchmark/detail/catch_stats.cpp',

View File

@ -292,15 +292,13 @@ TEST_CASE("analyse", "[approvals][benchmark]") {
data.benchmarkSamples = 99; data.benchmarkSamples = 99;
Catch::Config config{data}; Catch::Config config{data};
using Duration = Catch::Benchmark::FloatDuration<Catch::Benchmark::default_clock>; using FDuration = Catch::Benchmark::FDuration;
std::vector<FDuration> samples(99);
Catch::Benchmark::Environment<Duration> env;
std::vector<Duration> samples(99);
for (size_t i = 0; i < samples.size(); ++i) { for (size_t i = 0; i < samples.size(); ++i) {
samples[i] = Duration(23 + (i % 3 - 1)); samples[i] = FDuration(23 + (i % 3 - 1));
} }
auto analysis = Catch::Benchmark::Detail::analyse(config, env, samples.begin(), samples.end()); auto analysis = Catch::Benchmark::Detail::analyse(config, samples.data(), samples.data() + samples.size());
CHECK( analysis.mean.point.count() == 23 ); CHECK( analysis.mean.point.count() == 23 );
CHECK( analysis.mean.lower_bound.count() < 23 ); CHECK( analysis.mean.lower_bound.count() < 23 );
CHECK(analysis.mean.lower_bound.count() > 22); CHECK(analysis.mean.lower_bound.count() > 22);
@ -333,15 +331,13 @@ TEST_CASE("analyse no analysis", "[benchmark]") {
data.benchmarkSamples = 99; data.benchmarkSamples = 99;
Catch::Config config{ data }; Catch::Config config{ data };
using Duration = Catch::Benchmark::FloatDuration<Catch::Benchmark::default_clock>; using FDuration = Catch::Benchmark::FDuration;
std::vector<FDuration> samples(99);
Catch::Benchmark::Environment<Duration> env;
std::vector<Duration> samples(99);
for (size_t i = 0; i < samples.size(); ++i) { for (size_t i = 0; i < samples.size(); ++i) {
samples[i] = Duration(23 + (i % 3 - 1)); samples[i] = FDuration(23 + (i % 3 - 1));
} }
auto analysis = Catch::Benchmark::Detail::analyse(config, env, samples.begin(), samples.end()); auto analysis = Catch::Benchmark::Detail::analyse(config, samples.data(), samples.data() + samples.size());
CHECK(analysis.mean.point.count() == 23); CHECK(analysis.mean.point.count() == 23);
CHECK(analysis.mean.lower_bound.count() == 23); CHECK(analysis.mean.lower_bound.count() == 23);
CHECK(analysis.mean.upper_bound.count() == 23); CHECK(analysis.mean.upper_bound.count() == 23);