1use crate::stats::univariate::Sample;
2use crate::stats::univariate::{self, mixed};
3use crate::stats::Distribution;
4
5use crate::benchmark::BenchmarkConfig;
6use crate::error::Result;
7use crate::estimate::{
8 build_change_estimates, ChangeDistributions, ChangeEstimates, ChangePointEstimates, Estimates,
9};
10use crate::measurement::Measurement;
11use crate::report::BenchmarkId;
12use crate::{fs, Criterion, SavedSample};
13
14// Common comparison procedure
15#[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
16pub(crate) fn common<M: Measurement>(
17 id: &BenchmarkId,
18 avg_times: &Sample<f64>,
19 config: &BenchmarkConfig,
20 criterion: &Criterion<M>,
21) -> Result<(
22 f64,
23 Distribution<f64>,
24 ChangeEstimates,
25 ChangeDistributions,
26 Vec<f64>,
27 Vec<f64>,
28 Vec<f64>,
29 Estimates,
30)> {
31 let mut sample_file = criterion.output_directory.clone();
32 sample_file.push(id.as_directory_name());
33 sample_file.push(&criterion.baseline_directory);
34 sample_file.push("sample.json");
35 let sample: SavedSample = fs::load(&sample_file)?;
36 let SavedSample { iters, times, .. } = sample;
37
38 let mut estimates_file = criterion.output_directory.clone();
39 estimates_file.push(id.as_directory_name());
40 estimates_file.push(&criterion.baseline_directory);
41 estimates_file.push("estimates.json");
42 let base_estimates: Estimates = fs::load(&estimates_file)?;
43
44 let base_avg_times: Vec<f64> = iters
45 .iter()
46 .zip(times.iter())
47 .map(|(iters, elapsed)| elapsed / iters)
48 .collect();
49 let base_avg_time_sample = Sample::new(&base_avg_times);
50
51 let mut change_dir = criterion.output_directory.clone();
52 change_dir.push(id.as_directory_name());
53 change_dir.push("change");
54 fs::mkdirp(&change_dir)?;
55 let (t_statistic, t_distribution) = t_test(avg_times, base_avg_time_sample, config);
56
57 let (estimates, relative_distributions) =
58 estimates(id, avg_times, base_avg_time_sample, config, criterion);
59 Ok((
60 t_statistic,
61 t_distribution,
62 estimates,
63 relative_distributions,
64 iters,
65 times,
66 base_avg_times.clone(),
67 base_estimates,
68 ))
69}
70
71// Performs a two sample t-test
72fn t_test(
73 avg_times: &Sample<f64>,
74 base_avg_times: &Sample<f64>,
75 config: &BenchmarkConfig,
76) -> (f64, Distribution<f64>) {
77 let nresamples = config.nresamples;
78
79 let t_statistic = avg_times.t(base_avg_times);
80 let t_distribution = elapsed!(
81 "Bootstrapping the T distribution",
82 mixed::bootstrap(avg_times, base_avg_times, nresamples, |a, b| (a.t(b),))
83 )
84 .0;
85
86 // HACK: Filter out non-finite numbers, which can happen sometimes when sample size is very small.
87 // Downstream code doesn't like non-finite values here.
88 let t_distribution = Distribution::from(
89 t_distribution
90 .iter()
91 .filter(|a| a.is_finite())
92 .cloned()
93 .collect::<Vec<_>>()
94 .into_boxed_slice(),
95 );
96
97 (t_statistic, t_distribution)
98}
99
100// Estimates the relative change in the statistics of the population
101fn estimates<M: Measurement>(
102 id: &BenchmarkId,
103 avg_times: &Sample<f64>,
104 base_avg_times: &Sample<f64>,
105 config: &BenchmarkConfig,
106 criterion: &Criterion<M>,
107) -> (ChangeEstimates, ChangeDistributions) {
108 fn stats(a: &Sample<f64>, b: &Sample<f64>) -> (f64, f64) {
109 (
110 a.mean() / b.mean() - 1.,
111 a.percentiles().median() / b.percentiles().median() - 1.,
112 )
113 }
114
115 let cl = config.confidence_level;
116 let nresamples = config.nresamples;
117
118 let (dist_mean, dist_median) = elapsed!(
119 "Bootstrapping the relative statistics",
120 univariate::bootstrap(avg_times, base_avg_times, nresamples, stats)
121 );
122
123 let distributions = ChangeDistributions {
124 mean: dist_mean,
125 median: dist_median,
126 };
127
128 let (mean, median) = stats(avg_times, base_avg_times);
129 let points = ChangePointEstimates { mean, median };
130
131 let estimates = build_change_estimates(&distributions, &points, cl);
132
133 {
134 log_if_err!({
135 let mut estimates_path = criterion.output_directory.clone();
136 estimates_path.push(id.as_directory_name());
137 estimates_path.push("change");
138 estimates_path.push("estimates.json");
139 fs::save(&estimates, &estimates_path)
140 });
141 }
142 (estimates, distributions)
143}
144