|
| 1 | +use alloc::vec::Vec; |
| 2 | + |
| 3 | +use super::benchmark::BenchmarkConfig; |
| 4 | +use super::estimate::{ |
| 5 | + ConfidenceInterval, Distributions, Estimate, Estimates, PointEstimates, build_estimates, |
| 6 | +}; |
| 7 | +use super::measurement::Measurement; |
| 8 | +use super::report::{BenchmarkId, Report}; |
| 9 | +use super::routine::Routine; |
| 10 | +use super::stats::bivariate::Data; |
| 11 | +use super::stats::bivariate::regression::Slope; |
| 12 | +use super::stats::univariate::Sample; |
| 13 | +use super::stats::{Distribution, Tails}; |
| 14 | +use super::{Criterion, SavedSample, baseline, compare}; |
| 15 | + |
| 16 | +// Common analysis procedure |
| 17 | +pub(crate) async fn common<M: Measurement>( |
| 18 | + id: &BenchmarkId, |
| 19 | + routine: &mut dyn Routine<M>, |
| 20 | + config: &BenchmarkConfig, |
| 21 | + criterion: &Criterion<M>, |
| 22 | +) { |
| 23 | + criterion.report.benchmark_start(id); |
| 24 | + |
| 25 | + let (sampling_mode, iters, times); |
| 26 | + let sample = routine |
| 27 | + .sample(&criterion.measurement, id, config, criterion) |
| 28 | + .await; |
| 29 | + sampling_mode = sample.0; |
| 30 | + iters = sample.1; |
| 31 | + times = sample.2; |
| 32 | + |
| 33 | + criterion.report.analysis(id); |
| 34 | + |
| 35 | + if times.contains(&0.0) { |
| 36 | + return; |
| 37 | + } |
| 38 | + |
| 39 | + let avg_times = iters |
| 40 | + .iter() |
| 41 | + .zip(times.iter()) |
| 42 | + .map(|(&iters, &elapsed)| elapsed / iters) |
| 43 | + .collect::<Vec<f64>>(); |
| 44 | + let avg_times = Sample::new(&avg_times); |
| 45 | + let labeled_sample = super::stats::univariate::outliers::tukey::classify(avg_times); |
| 46 | + |
| 47 | + let data = Data::new(&iters, ×); |
| 48 | + let (mut distributions, mut estimates) = estimates(avg_times, config); |
| 49 | + if sampling_mode.is_linear() { |
| 50 | + let (distribution, slope) = regression(&data, config); |
| 51 | + |
| 52 | + estimates.slope = Some(slope); |
| 53 | + distributions.slope = Some(distribution); |
| 54 | + } |
| 55 | + |
| 56 | + let comparison = compare::common(id, avg_times, config).map( |
| 57 | + |(t_value, t_distribution, relative_estimates, ..)| { |
| 58 | + let p_value = t_distribution.p_value(t_value, Tails::Two); |
| 59 | + super::report::ComparisonData { |
| 60 | + p_value, |
| 61 | + relative_estimates, |
| 62 | + significance_threshold: config.significance_level, |
| 63 | + noise_threshold: config.noise_threshold, |
| 64 | + } |
| 65 | + }, |
| 66 | + ); |
| 67 | + |
| 68 | + let measurement_data = super::report::MeasurementData { |
| 69 | + avg_times: labeled_sample, |
| 70 | + absolute_estimates: estimates.clone(), |
| 71 | + comparison, |
| 72 | + }; |
| 73 | + |
| 74 | + criterion |
| 75 | + .report |
| 76 | + .measurement_complete(id, &measurement_data, criterion.measurement.formatter()); |
| 77 | + |
| 78 | + baseline::write( |
| 79 | + id.desc(), |
| 80 | + baseline::BenchmarkBaseline { |
| 81 | + file: criterion.location.as_ref().map(|l| l.file.clone()), |
| 82 | + module_path: criterion.location.as_ref().map(|l| l.module_path.clone()), |
| 83 | + iters: data.x().as_ref().to_vec(), |
| 84 | + times: data.y().as_ref().to_vec(), |
| 85 | + sample: SavedSample { |
| 86 | + sampling_mode, |
| 87 | + iters: data.x().as_ref().to_vec(), |
| 88 | + times: data.y().as_ref().to_vec(), |
| 89 | + }, |
| 90 | + estimates, |
| 91 | + }, |
| 92 | + ); |
| 93 | +} |
| 94 | + |
| 95 | +// Performs a simple linear regression on the sample |
| 96 | +fn regression( |
| 97 | + data: &Data<'_, f64, f64>, |
| 98 | + config: &BenchmarkConfig, |
| 99 | +) -> (Distribution<f64>, Estimate) { |
| 100 | + let cl = config.confidence_level; |
| 101 | + |
| 102 | + let distribution = data.bootstrap(config.nresamples, |d| (Slope::fit(&d).0,)).0; |
| 103 | + |
| 104 | + let point = Slope::fit(data); |
| 105 | + let (lb, ub) = distribution.confidence_interval(config.confidence_level); |
| 106 | + let se = distribution.std_dev(None); |
| 107 | + |
| 108 | + ( |
| 109 | + distribution, |
| 110 | + Estimate { |
| 111 | + confidence_interval: ConfidenceInterval { |
| 112 | + confidence_level: cl, |
| 113 | + lower_bound: lb, |
| 114 | + upper_bound: ub, |
| 115 | + }, |
| 116 | + point_estimate: point.0, |
| 117 | + standard_error: se, |
| 118 | + }, |
| 119 | + ) |
| 120 | +} |
| 121 | + |
| 122 | +// Estimates the statistics of the population from the sample |
| 123 | +fn estimates(avg_times: &Sample<f64>, config: &BenchmarkConfig) -> (Distributions, Estimates) { |
| 124 | + fn stats(sample: &Sample<f64>) -> (f64, f64, f64, f64) { |
| 125 | + let mean = sample.mean(); |
| 126 | + let std_dev = sample.std_dev(Some(mean)); |
| 127 | + let median = sample.percentiles().median(); |
| 128 | + let mad = sample.median_abs_dev(Some(median)); |
| 129 | + |
| 130 | + (mean, std_dev, median, mad) |
| 131 | + } |
| 132 | + |
| 133 | + let cl = config.confidence_level; |
| 134 | + let nresamples = config.nresamples; |
| 135 | + |
| 136 | + let (mean, std_dev, median, mad) = stats(avg_times); |
| 137 | + let points = PointEstimates { |
| 138 | + mean, |
| 139 | + median, |
| 140 | + std_dev, |
| 141 | + median_abs_dev: mad, |
| 142 | + }; |
| 143 | + |
| 144 | + let (dist_mean, dist_stddev, dist_median, dist_mad) = avg_times.bootstrap(nresamples, stats); |
| 145 | + |
| 146 | + let distributions = Distributions { |
| 147 | + mean: dist_mean, |
| 148 | + slope: None, |
| 149 | + median: dist_median, |
| 150 | + median_abs_dev: dist_mad, |
| 151 | + std_dev: dist_stddev, |
| 152 | + }; |
| 153 | + |
| 154 | + let estimates = build_estimates(&distributions, &points, cl); |
| 155 | + |
| 156 | + (distributions, estimates) |
| 157 | +} |
0 commit comments