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