2021-08-24 22:12:42 +02:00
|
|
|
use std::cmp::Ordering;
|
|
|
|
|
|
2022-02-20 13:44:34 +01:00
|
|
|
use super::benchmark_result::BenchmarkResult;
|
2026-10-05 13:41:20 +02:00
|
|
|
use crate::{
|
|
|
|
|
options::SortOrder,
|
|
|
|
|
quantity::{self, Ratio, Time, Zero},
|
|
|
|
|
};
|
2021-08-24 22:12:42 +02:00
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct BenchmarkResultWithRelativeSpeed<'a> {
|
|
|
|
|
pub result: &'a BenchmarkResult,
|
2026-10-05 10:46:51 +02:00
|
|
|
pub relative_speed: f64,
|
|
|
|
|
pub relative_speed_stddev: Option<f64>,
|
2024-04-27 20:16:24 +02:00
|
|
|
pub is_reference: bool,
|
|
|
|
|
// Less means faster
|
|
|
|
|
pub relative_ordering: Ordering,
|
2021-08-24 22:12:42 +02:00
|
|
|
}
|
|
|
|
|
|
2026-10-05 15:07:46 +02:00
|
|
|
impl BenchmarkResultWithRelativeSpeed<'_> {
|
|
|
|
|
pub fn reference_label(&self) -> &'static str {
|
|
|
|
|
if self.is_reference {
|
|
|
|
|
" (reference)"
|
|
|
|
|
} else {
|
|
|
|
|
match self.relative_ordering {
|
|
|
|
|
Ordering::Less => " (faster)",
|
|
|
|
|
Ordering::Equal => " (same speed)",
|
|
|
|
|
Ordering::Greater => " (slower)",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-24 22:12:42 +02:00
|
|
|
pub fn compare_mean_time(l: &BenchmarkResult, r: &BenchmarkResult) -> Ordering {
|
2026-10-05 14:13:58 +02:00
|
|
|
l.mean().partial_cmp(&r.mean()).unwrap_or(Ordering::Equal)
|
2021-08-24 22:12:42 +02:00
|
|
|
}
|
|
|
|
|
|
2024-04-27 20:16:24 +02:00
|
|
|
pub fn fastest_of(results: &[BenchmarkResult]) -> &BenchmarkResult {
|
2023-04-20 12:43:08 +02:00
|
|
|
results
|
2021-08-24 22:12:42 +02:00
|
|
|
.iter()
|
|
|
|
|
.min_by(|&l, &r| compare_mean_time(l, r))
|
2023-04-20 12:43:08 +02:00
|
|
|
.expect("at least one benchmark result")
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-05 15:07:46 +02:00
|
|
|
fn compute_relative_speeds<'a>(
|
2023-04-20 12:43:08 +02:00
|
|
|
results: &'a [BenchmarkResult],
|
2024-04-27 20:16:24 +02:00
|
|
|
reference: &'a BenchmarkResult,
|
2023-06-03 21:06:50 +02:00
|
|
|
sort_order: SortOrder,
|
2023-04-20 12:43:08 +02:00
|
|
|
) -> Vec<BenchmarkResultWithRelativeSpeed<'a>> {
|
2023-06-03 21:06:50 +02:00
|
|
|
let mut results: Vec<_> = results
|
2023-04-20 12:43:08 +02:00
|
|
|
.iter()
|
|
|
|
|
.map(|result| {
|
2026-10-05 14:59:18 +02:00
|
|
|
// Separate benchmarks can have identical names and measurements.
|
|
|
|
|
let is_reference = std::ptr::eq(result, reference);
|
2024-04-27 20:16:24 +02:00
|
|
|
let relative_ordering = compare_mean_time(result, reference);
|
2023-04-20 12:43:08 +02:00
|
|
|
|
2026-10-05 14:59:18 +02:00
|
|
|
if result.mean_wall_clock_time() == Time::zero()
|
|
|
|
|
|| reference.mean_wall_clock_time() == Time::zero()
|
|
|
|
|
{
|
2023-04-20 12:43:08 +02:00
|
|
|
return BenchmarkResultWithRelativeSpeed {
|
|
|
|
|
result,
|
2024-04-27 20:16:24 +02:00
|
|
|
relative_speed: if is_reference { 1.0 } else { f64::INFINITY },
|
2023-04-20 12:43:08 +02:00
|
|
|
relative_speed_stddev: None,
|
2024-04-27 20:16:24 +02:00
|
|
|
is_reference,
|
|
|
|
|
relative_ordering,
|
2023-04-20 12:43:08 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-27 20:16:24 +02:00
|
|
|
let ratio = match relative_ordering {
|
2026-10-05 14:13:58 +02:00
|
|
|
Ordering::Less => reference.mean() / result.mean(),
|
|
|
|
|
Ordering::Equal => 1.0,
|
|
|
|
|
Ordering::Greater => result.mean() / reference.mean(),
|
2024-04-27 20:16:24 +02:00
|
|
|
};
|
2023-04-20 12:43:08 +02:00
|
|
|
|
|
|
|
|
// https://en.wikipedia.org/wiki/Propagation_of_uncertainty#Example_formulas
|
2026-03-13 13:29:46 +08:00
|
|
|
// Covariance assumed to be 0, i.e. variables are assumed to be independent
|
2026-10-05 12:46:10 +02:00
|
|
|
let ratio_stddev = match (
|
2026-10-05 13:41:20 +02:00
|
|
|
result.measurements.stddev(),
|
|
|
|
|
reference.measurements.stddev(),
|
2026-10-05 12:46:10 +02:00
|
|
|
) {
|
2026-10-05 14:59:18 +02:00
|
|
|
(Some(result_stddev), Some(reference_stddev)) => Some(
|
2023-04-20 12:43:08 +02:00
|
|
|
ratio
|
2026-10-05 13:41:20 +02:00
|
|
|
* ((result_stddev / result.mean_wall_clock_time())
|
|
|
|
|
.powi(uom::typenum::P2::new())
|
2026-10-05 14:59:18 +02:00
|
|
|
+ (reference_stddev / reference.mean_wall_clock_time())
|
2026-10-05 13:41:20 +02:00
|
|
|
.powi(uom::typenum::P2::new()))
|
2023-04-20 12:43:08 +02:00
|
|
|
.sqrt(),
|
|
|
|
|
),
|
|
|
|
|
_ => None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
BenchmarkResultWithRelativeSpeed {
|
|
|
|
|
result,
|
2026-10-05 13:41:20 +02:00
|
|
|
relative_speed: ratio.get::<quantity::ratio>(),
|
|
|
|
|
relative_speed_stddev: ratio_stddev.map(|r| r.get::<quantity::ratio>()),
|
2024-04-27 20:16:24 +02:00
|
|
|
is_reference,
|
|
|
|
|
relative_ordering,
|
2023-04-20 12:43:08 +02:00
|
|
|
}
|
|
|
|
|
})
|
2023-06-03 21:06:50 +02:00
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
match sort_order {
|
|
|
|
|
SortOrder::Command => {}
|
|
|
|
|
SortOrder::MeanTime => {
|
|
|
|
|
results.sort_unstable_by(|r1, r2| compare_mean_time(r1.result, r2.result));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
results
|
2023-04-20 12:43:08 +02:00
|
|
|
}
|
|
|
|
|
|
2024-04-27 20:16:24 +02:00
|
|
|
pub fn compute_with_check_from_reference<'a>(
|
|
|
|
|
results: &'a [BenchmarkResult],
|
|
|
|
|
reference: &'a BenchmarkResult,
|
|
|
|
|
sort_order: SortOrder,
|
|
|
|
|
) -> Option<Vec<BenchmarkResultWithRelativeSpeed<'a>>> {
|
2026-10-05 14:13:58 +02:00
|
|
|
if fastest_of(results).mean() == 0.0 || reference.mean() == 0.0 {
|
2024-04-27 20:16:24 +02:00
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Some(compute_relative_speeds(results, reference, sort_order))
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-20 12:43:08 +02:00
|
|
|
pub fn compute_with_check(
|
|
|
|
|
results: &[BenchmarkResult],
|
2023-06-03 21:06:50 +02:00
|
|
|
sort_order: SortOrder,
|
2025-09-03 20:42:07 +02:00
|
|
|
) -> Option<Vec<BenchmarkResultWithRelativeSpeed<'_>>> {
|
2023-04-20 12:43:08 +02:00
|
|
|
let fastest = fastest_of(results);
|
2021-08-24 22:12:42 +02:00
|
|
|
|
2026-10-05 14:13:58 +02:00
|
|
|
if fastest.mean() == 0.0 {
|
2021-08-24 22:12:42 +02:00
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-03 21:06:50 +02:00
|
|
|
Some(compute_relative_speeds(results, fastest, sort_order))
|
2023-04-20 12:43:08 +02:00
|
|
|
}
|
2021-08-24 22:12:42 +02:00
|
|
|
|
2026-10-05 15:07:46 +02:00
|
|
|
/// Compute relative speeds against the given reference, or the fastest result.
|
|
|
|
|
pub fn compute<'a>(
|
|
|
|
|
results: &'a [BenchmarkResult],
|
2023-06-03 21:06:50 +02:00
|
|
|
sort_order: SortOrder,
|
2026-10-05 15:07:46 +02:00
|
|
|
reference: Option<&'a BenchmarkResult>,
|
|
|
|
|
) -> Vec<BenchmarkResultWithRelativeSpeed<'a>> {
|
|
|
|
|
let reference = reference.unwrap_or_else(|| fastest_of(results));
|
|
|
|
|
compute_relative_speeds(results, reference, sort_order)
|
2021-08-24 22:12:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
2026-10-05 10:46:51 +02:00
|
|
|
fn create_result(name: &str, mean: f64) -> BenchmarkResult {
|
2021-08-24 22:12:42 +02:00
|
|
|
use std::collections::BTreeMap;
|
|
|
|
|
|
2026-10-05 14:13:58 +02:00
|
|
|
use crate::benchmark::benchmark_result::BenchmarkRun;
|
2026-10-05 12:46:10 +02:00
|
|
|
|
2021-08-24 22:12:42 +02:00
|
|
|
BenchmarkResult {
|
|
|
|
|
command: name.into(),
|
2026-10-05 14:13:58 +02:00
|
|
|
runs: vec![BenchmarkRun {
|
|
|
|
|
wall_clock_time: mean,
|
2026-10-05 14:14:10 +02:00
|
|
|
user_time: mean,
|
|
|
|
|
system_time: 0.,
|
2026-10-05 14:14:25 +02:00
|
|
|
memory_usage_byte: 1024,
|
|
|
|
|
exit_code: Some(0),
|
2026-10-05 14:13:58 +02:00
|
|
|
}],
|
2021-08-24 22:12:42 +02:00
|
|
|
parameters: BTreeMap::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_compute_relative_speed() {
|
|
|
|
|
use approx::assert_relative_eq;
|
|
|
|
|
|
|
|
|
|
let results = vec![
|
|
|
|
|
create_result("cmd1", 3.0),
|
|
|
|
|
create_result("cmd2", 2.0),
|
|
|
|
|
create_result("cmd3", 5.0),
|
|
|
|
|
];
|
|
|
|
|
|
2023-06-03 21:06:50 +02:00
|
|
|
let annotated_results = compute_with_check(&results, SortOrder::Command).unwrap();
|
2021-08-24 22:12:42 +02:00
|
|
|
|
|
|
|
|
assert_relative_eq!(1.5, annotated_results[0].relative_speed);
|
|
|
|
|
assert_relative_eq!(1.0, annotated_results[1].relative_speed);
|
|
|
|
|
assert_relative_eq!(2.5, annotated_results[2].relative_speed);
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-23 18:38:07 +02:00
|
|
|
#[test]
|
|
|
|
|
fn test_compute_relative_speed_with_reference() {
|
|
|
|
|
use approx::assert_relative_eq;
|
|
|
|
|
|
|
|
|
|
let results = vec![create_result("cmd2", 2.0), create_result("cmd3", 5.0)];
|
|
|
|
|
let reference = create_result("cmd2", 4.0);
|
|
|
|
|
|
|
|
|
|
let annotated_results =
|
|
|
|
|
compute_with_check_from_reference(&results, &reference, SortOrder::Command).unwrap();
|
|
|
|
|
|
|
|
|
|
assert_relative_eq!(2.0, annotated_results[0].relative_speed);
|
|
|
|
|
assert_relative_eq!(1.25, annotated_results[1].relative_speed);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-24 22:12:42 +02:00
|
|
|
#[test]
|
|
|
|
|
fn test_compute_relative_speed_for_zero_times() {
|
|
|
|
|
let results = vec![create_result("cmd1", 1.0), create_result("cmd2", 0.0)];
|
|
|
|
|
|
2023-06-03 21:06:50 +02:00
|
|
|
let annotated_results = compute_with_check(&results, SortOrder::Command);
|
2021-08-24 22:12:42 +02:00
|
|
|
|
|
|
|
|
assert!(annotated_results.is_none());
|
|
|
|
|
}
|
2026-10-05 14:59:18 +02:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn reference_identity_distinguishes_equal_results() {
|
|
|
|
|
let results = vec![create_result("same", 1.0), create_result("same", 1.0)];
|
|
|
|
|
let entries = compute_relative_speeds(&results, &results[0], SortOrder::Command);
|
|
|
|
|
|
|
|
|
|
assert!(entries[0].is_reference);
|
|
|
|
|
assert!(!entries[1].is_reference);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn reference_ratios_handle_zero_times() {
|
|
|
|
|
let mut results = vec![create_result("reference", 2.0), create_result("zero", 0.0)];
|
|
|
|
|
for result in &mut results {
|
|
|
|
|
let measurement = result.measurements.measurements[0];
|
|
|
|
|
result.measurements.measurements.push(measurement);
|
|
|
|
|
}
|
|
|
|
|
let entries = compute_relative_speeds(&results, &results[0], SortOrder::Command);
|
|
|
|
|
assert_eq!(entries[1].relative_speed, f64::INFINITY);
|
|
|
|
|
|
|
|
|
|
let entries = compute_relative_speeds(&results, &results[1], SortOrder::Command);
|
|
|
|
|
assert_eq!(entries[0].relative_speed, f64::INFINITY);
|
|
|
|
|
assert_eq!(entries[0].relative_speed_stddev, None);
|
|
|
|
|
assert_eq!(entries[1].relative_speed, 1.0);
|
|
|
|
|
}
|