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 }