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
${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_run_for_at_least.cpp
${SOURCES_DIR}/benchmark/detail/catch_stats.cpp

View File

@ -45,16 +45,18 @@ namespace Catch {
: fun(CATCH_MOVE(func)), name(CATCH_MOVE(benchmarkName)) {}
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 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));
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>
void run() {
static_assert( Clock::is_steady,
"Benchmarking clock should be steady" );
auto const* cfg = getCurrentContext().getConfig();
auto env = Detail::measure_environment<Clock>();
@ -81,8 +83,8 @@ namespace Catch {
return plan.template run<Clock>(*cfg, env);
});
auto analysis = Detail::analyse(*cfg, env, samples.begin(), samples.end());
BenchmarkStats<FloatDuration<Clock>> stats{ CATCH_MOVE(info), CATCH_MOVE(analysis.samples), analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance };
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_ANON (TestFailureException const&) {
getResultCapture().benchmarkFailed("Benchmark failed due to failed assertion"_sr);

View File

@ -32,7 +32,10 @@ namespace Catch {
void start() override { started = 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> finished;

View File

@ -11,28 +11,16 @@
#define CATCH_CLOCK_HPP_INCLUDED
#include <chrono>
#include <ratio>
namespace Catch {
namespace Benchmark {
template <typename Clock>
using ClockDuration = typename Clock::duration;
template <typename Clock>
using FloatDuration = std::chrono::duration<double, typename Clock::period>;
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;
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 Catch

View File

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

View File

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

View File

@ -21,33 +21,31 @@
namespace Catch {
namespace Benchmark {
template <typename Duration>
struct ExecutionPlan {
int iterations_per_sample;
Duration estimated_duration;
FDuration estimated_duration;
Detail::BenchmarkFunction benchmark;
Duration warmup_time;
FDuration warmup_time;
int warmup_iterations;
template <typename Duration2>
operator ExecutionPlan<Duration2>() const {
return { iterations_per_sample, estimated_duration, benchmark, warmup_time, warmup_iterations };
}
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
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();
times.reserve( num_samples );
for ( size_t i = 0; i < num_samples; ++i ) {
Detail::ChronometerModel<Clock> model;
this->benchmark( Chronometer( model, iterations_per_sample ) );
auto sample_time = model.elapsed() - env.clock_cost.mean;
if ( sample_time < FloatDuration<Clock>::zero() ) {
sample_time = FloatDuration<Clock>::zero();
if ( sample_time < FDuration::zero() ) {
sample_time = FDuration::zero();
}
times.push_back(sample_time / iterations_per_sample);
}

View File

@ -12,35 +12,18 @@
#include <catch2/benchmark/catch_estimate.hpp>
#include <catch2/benchmark/catch_outlier_classification.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
#include <catch2/benchmark/catch_clock.hpp>
#include <vector>
namespace Catch {
namespace Benchmark {
template <typename Duration>
struct SampleAnalysis {
std::vector<Duration> samples;
Estimate<Duration> mean;
Estimate<Duration> standard_deviation;
std::vector<FDuration> samples;
Estimate<FDuration> mean;
Estimate<FDuration> standard_deviation;
OutlierClassification outliers;
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 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
#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/detail/catch_stats.hpp>
#include <catch2/interfaces/catch_interfaces_config.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
#include <vector>
namespace Catch {
class IConfig;
namespace Benchmark {
namespace Detail {
template <typename Duration, typename Iterator>
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
};
}
}
SampleAnalysis analyse(const IConfig &cfg, FDuration* first, FDuration* last);
} // namespace Detail
} // namespace Benchmark
} // namespace Catch

View File

@ -8,7 +8,6 @@
#ifndef 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_outlier_classification.hpp>
// The fwd decl & default specialization needs to be seen by VS2017 before
@ -30,32 +29,17 @@ namespace Catch {
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 {
BenchmarkInfo info;
std::vector<Duration> samples;
Benchmark::Estimate<Duration> mean;
Benchmark::Estimate<Duration> standardDeviation;
std::vector<Benchmark::FDuration> samples;
Benchmark::Estimate<Benchmark::FDuration> mean;
Benchmark::Estimate<Benchmark::FDuration> standardDeviation;
Benchmark::OutlierClassification outliers;
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
#define CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED
#include <chrono>
#include <catch2/benchmark/catch_clock.hpp>
namespace Catch {
// We cannot forward declare the type with default template argument
// multiple times, so it is split out into a separate header so that
// we can prevent multiple declarations in dependees
template <typename Duration = std::chrono::duration<double, std::nano>>
template <typename Duration = Benchmark::FDuration>
struct BenchmarkStats;
} // end namespace Catch

View File

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

View File

@ -18,7 +18,7 @@ namespace Catch {
namespace Benchmark {
namespace Detail {
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&& r = Detail::complete_invoke(fun, CATCH_FORWARD(args)...);
auto end = Clock::now();

View File

@ -24,11 +24,11 @@ namespace Catch {
namespace Benchmark {
namespace Detail {
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);
}
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;
auto&& result = Detail::complete_invoke(fun, Chronometer(meter, iters));
@ -43,8 +43,8 @@ namespace Catch {
void throw_optimized_away_error();
template <typename Clock, typename Fun>
TimingOf<Clock, Fun, run_for_at_least_argument_t<Clock, Fun>>
run_for_at_least(ClockDuration<Clock> how_long,
TimingOf<Fun, run_for_at_least_argument_t<Clock, Fun>>
run_for_at_least(IDuration how_long,
const int initial_iterations,
Fun&& fun) {
auto iters = initial_iterations;

View File

@ -11,7 +11,9 @@
#include <catch2/internal/catch_compiler_capabilities.hpp>
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <numeric>
#include <random>
@ -257,6 +259,27 @@ namespace Catch {
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) {
return erf_inv(1.0 - x);
@ -278,6 +301,64 @@ namespace Catch {
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,
unsigned int n_resamples,
double* first,

View File

@ -13,9 +13,7 @@
#include <catch2/benchmark/catch_estimate.hpp>
#include <catch2/benchmark/catch_outlier_classification.hpp>
#include <algorithm>
#include <vector>
#include <cmath>
namespace Catch {
namespace Benchmark {
@ -36,78 +34,23 @@ namespace Catch {
double mean( double const* first, double const* last );
template <typename Estimator>
sample jackknife(Estimator&& estimator,
double* first,
double* last) {
auto n = static_cast<size_t>(last - first);
auto second = first;
++second;
sample results;
results.reserve(n);
sample jackknife( double ( *estimator )( double const*,
double const* ),
double* first,
double* last );
for (auto it = first; it != last; ++it) {
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 normal_cdf( double x );
double erfc_inv(double x);
double normal_quantile(double p);
template <typename Estimator>
Estimate<double> bootstrap( double confidence_level,
double* first,
double* last,
sample const& resample,
Estimator&& estimator ) {
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 };
}
Estimate<double>
bootstrap( double confidence_level,
double* first,
double* last,
sample const& resample,
double ( *estimator )( double const*, double const* ) );
struct bootstrap_analysis {
Estimate<double> mean;

View File

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

View File

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

View File

@ -292,15 +292,13 @@ TEST_CASE("analyse", "[approvals][benchmark]") {
data.benchmarkSamples = 99;
Catch::Config config{data};
using Duration = Catch::Benchmark::FloatDuration<Catch::Benchmark::default_clock>;
Catch::Benchmark::Environment<Duration> env;
std::vector<Duration> samples(99);
using FDuration = Catch::Benchmark::FDuration;
std::vector<FDuration> samples(99);
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.lower_bound.count() < 23 );
CHECK(analysis.mean.lower_bound.count() > 22);
@ -333,15 +331,13 @@ TEST_CASE("analyse no analysis", "[benchmark]") {
data.benchmarkSamples = 99;
Catch::Config config{ data };
using Duration = Catch::Benchmark::FloatDuration<Catch::Benchmark::default_clock>;
Catch::Benchmark::Environment<Duration> env;
std::vector<Duration> samples(99);
using FDuration = Catch::Benchmark::FDuration;
std::vector<FDuration> samples(99);
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.lower_bound.count() == 23);
CHECK(analysis.mean.upper_bound.count() == 23);