Coverage Report

Created: 2025-10-29 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}