/ thirdparty / hyperfine / src / benchmark / relative_speed.rs
relative_speed.rs
  1  use std::cmp::Ordering;
  2  
  3  use super::benchmark_result::BenchmarkResult;
  4  use crate::{options::SortOrder, util::units::Scalar};
  5  
  6  #[derive(Debug)]
  7  pub struct BenchmarkResultWithRelativeSpeed<'a> {
  8      pub result: &'a BenchmarkResult,
  9      pub relative_speed: Scalar,
 10      pub relative_speed_stddev: Option<Scalar>,
 11      pub is_reference: bool,
 12      // Less means faster
 13      pub relative_ordering: Ordering,
 14  }
 15  
 16  pub fn compare_mean_time(l: &BenchmarkResult, r: &BenchmarkResult) -> Ordering {
 17      l.mean.partial_cmp(&r.mean).unwrap_or(Ordering::Equal)
 18  }
 19  
 20  pub fn fastest_of(results: &[BenchmarkResult]) -> &BenchmarkResult {
 21      results
 22          .iter()
 23          .min_by(|&l, &r| compare_mean_time(l, r))
 24          .expect("at least one benchmark result")
 25  }
 26  
 27  fn compute_relative_speeds<'a>(
 28      results: &'a [BenchmarkResult],
 29      reference: &'a BenchmarkResult,
 30      sort_order: SortOrder,
 31  ) -> Vec<BenchmarkResultWithRelativeSpeed<'a>> {
 32      let mut results: Vec<_> = results
 33          .iter()
 34          .map(|result| {
 35              let is_reference = result == reference;
 36              let relative_ordering = compare_mean_time(result, reference);
 37  
 38              if result.mean == 0.0 {
 39                  return BenchmarkResultWithRelativeSpeed {
 40                      result,
 41                      relative_speed: if is_reference { 1.0 } else { f64::INFINITY },
 42                      relative_speed_stddev: None,
 43                      is_reference,
 44                      relative_ordering,
 45                  };
 46              }
 47  
 48              let ratio = match relative_ordering {
 49                  Ordering::Less => reference.mean / result.mean,
 50                  Ordering::Equal => 1.0,
 51                  Ordering::Greater => result.mean / reference.mean,
 52              };
 53  
 54              // https://en.wikipedia.org/wiki/Propagation_of_uncertainty#Example_formulas
 55              // Covariance asssumed to be 0, i.e. variables are assumed to be independent
 56              let ratio_stddev = match (result.stddev, reference.stddev) {
 57                  (Some(result_stddev), Some(fastest_stddev)) => Some(
 58                      ratio
 59                          * ((result_stddev / result.mean).powi(2)
 60                              + (fastest_stddev / reference.mean).powi(2))
 61                          .sqrt(),
 62                  ),
 63                  _ => None,
 64              };
 65  
 66              BenchmarkResultWithRelativeSpeed {
 67                  result,
 68                  relative_speed: ratio,
 69                  relative_speed_stddev: ratio_stddev,
 70                  is_reference,
 71                  relative_ordering,
 72              }
 73          })
 74          .collect();
 75  
 76      match sort_order {
 77          SortOrder::Command => {}
 78          SortOrder::MeanTime => {
 79              results.sort_unstable_by(|r1, r2| compare_mean_time(r1.result, r2.result));
 80          }
 81      }
 82  
 83      results
 84  }
 85  
 86  pub fn compute_with_check_from_reference<'a>(
 87      results: &'a [BenchmarkResult],
 88      reference: &'a BenchmarkResult,
 89      sort_order: SortOrder,
 90  ) -> Option<Vec<BenchmarkResultWithRelativeSpeed<'a>>> {
 91      if fastest_of(results).mean == 0.0 || reference.mean == 0.0 {
 92          return None;
 93      }
 94  
 95      Some(compute_relative_speeds(results, reference, sort_order))
 96  }
 97  
 98  pub fn compute_with_check(
 99      results: &[BenchmarkResult],
100      sort_order: SortOrder,
101  ) -> Option<Vec<BenchmarkResultWithRelativeSpeed<'_>>> {
102      let fastest = fastest_of(results);
103  
104      if fastest.mean == 0.0 {
105          return None;
106      }
107  
108      Some(compute_relative_speeds(results, fastest, sort_order))
109  }
110  
111  /// Same as compute_with_check, potentially resulting in relative speeds of infinity
112  pub fn compute(
113      results: &[BenchmarkResult],
114      sort_order: SortOrder,
115  ) -> Vec<BenchmarkResultWithRelativeSpeed<'_>> {
116      let fastest = fastest_of(results);
117  
118      compute_relative_speeds(results, fastest, sort_order)
119  }
120  
121  #[cfg(test)]
122  fn create_result(name: &str, mean: Scalar) -> BenchmarkResult {
123      use std::collections::BTreeMap;
124  
125      BenchmarkResult {
126          command: name.into(),
127          command_with_unused_parameters: name.into(),
128          mean,
129          stddev: Some(1.0),
130          median: mean,
131          user: mean,
132          system: 0.0,
133          min: mean,
134          max: mean,
135          times: None,
136          memory_usage_byte: None,
137          exit_codes: Vec::new(),
138          parameters: BTreeMap::new(),
139      }
140  }
141  
142  #[test]
143  fn test_compute_relative_speed() {
144      use approx::assert_relative_eq;
145  
146      let results = vec![
147          create_result("cmd1", 3.0),
148          create_result("cmd2", 2.0),
149          create_result("cmd3", 5.0),
150      ];
151  
152      let annotated_results = compute_with_check(&results, SortOrder::Command).unwrap();
153  
154      assert_relative_eq!(1.5, annotated_results[0].relative_speed);
155      assert_relative_eq!(1.0, annotated_results[1].relative_speed);
156      assert_relative_eq!(2.5, annotated_results[2].relative_speed);
157  }
158  
159  #[test]
160  fn test_compute_relative_speed_with_reference() {
161      use approx::assert_relative_eq;
162  
163      let results = vec![create_result("cmd2", 2.0), create_result("cmd3", 5.0)];
164      let reference = create_result("cmd2", 4.0);
165  
166      let annotated_results =
167          compute_with_check_from_reference(&results, &reference, SortOrder::Command).unwrap();
168  
169      assert_relative_eq!(2.0, annotated_results[0].relative_speed);
170      assert_relative_eq!(1.25, annotated_results[1].relative_speed);
171  }
172  
173  #[test]
174  fn test_compute_relative_speed_for_zero_times() {
175      let results = vec![create_result("cmd1", 1.0), create_result("cmd2", 0.0)];
176  
177      let annotated_results = compute_with_check(&results, SortOrder::Command);
178  
179      assert!(annotated_results.is_none());
180  }