1 //! This module defines a set of traits that can be used to plug different measurements (eg. 2 //! Unix's Processor Time, CPU or GPU performance counters, etc.) into Criterion.rs. It also 3 //! includes the [WallTime](struct.WallTime.html) struct which defines the default wall-clock time 4 //! measurement. 5 6 use crate::format::short; 7 use crate::DurationExt; 8 use crate::Throughput; 9 use std::time::{Duration, Instant}; 10 11 /// Trait providing functions to format measured values to string so that they can be displayed on 12 /// the command line or in the reports. The functions of this trait take measured values in f64 13 /// form; implementors can assume that the values are of the same scale as those produced by the 14 /// associated [MeasuredValue](trait.MeasuredValue.html) (eg. if your measurement produces values in 15 /// nanoseconds, the values passed to the formatter will be in nanoseconds). 16 /// 17 /// Implementors are encouraged to format the values in a way that is intuitive for humans and 18 /// uses the SI prefix system. For example, the format used by [WallTime](struct.WallTime.html) 19 /// can display the value in units ranging from picoseconds to seconds depending on the magnitude 20 /// of the elapsed time in nanoseconds. 21 pub trait ValueFormatter { 22 /// Format the value (with appropriate unit) and return it as a string. format_value(&self, value: f64) -> String23 fn format_value(&self, value: f64) -> String { 24 let mut values = [value]; 25 let unit = self.scale_values(value, &mut values); 26 format!("{:>6} {}", short(values[0]), unit) 27 } 28 29 /// Format the value as a throughput measurement. The value represents the measurement value; 30 /// the implementor will have to calculate bytes per second, iterations per cycle, etc. format_throughput(&self, throughput: &Throughput, value: f64) -> String31 fn format_throughput(&self, throughput: &Throughput, value: f64) -> String { 32 let mut values = [value]; 33 let unit = self.scale_throughputs(value, throughput, &mut values); 34 format!("{:>6} {}", short(values[0]), unit) 35 } 36 37 /// Scale the given values to some appropriate unit and return the unit string. 38 /// 39 /// The given typical value should be used to choose the unit. This function may be called 40 /// multiple times with different datasets; the typical value will remain the same to ensure 41 /// that the units remain consistent within a graph. The typical value will not be NaN. 42 /// Values will not contain NaN as input, and the transformed values must not contain NaN. scale_values(&self, typical_value: f64, values: &mut [f64]) -> &'static str43 fn scale_values(&self, typical_value: f64, values: &mut [f64]) -> &'static str; 44 45 /// Convert the given measured values into throughput numbers based on the given throughput 46 /// value, scale them to some appropriate unit, and return the unit string. 47 /// 48 /// The given typical value should be used to choose the unit. This function may be called 49 /// multiple times with different datasets; the typical value will remain the same to ensure 50 /// that the units remain consistent within a graph. The typical value will not be NaN. 51 /// Values will not contain NaN as input, and the transformed values must not contain NaN. scale_throughputs( &self, typical_value: f64, throughput: &Throughput, values: &mut [f64], ) -> &'static str52 fn scale_throughputs( 53 &self, 54 typical_value: f64, 55 throughput: &Throughput, 56 values: &mut [f64], 57 ) -> &'static str; 58 59 /// Scale the values and return a unit string designed for machines. 60 /// 61 /// For example, this is used for the CSV file output. Implementations should modify the given 62 /// values slice to apply the desired scaling (if any) and return a string representing the unit 63 /// the modified values are in. scale_for_machines(&self, values: &mut [f64]) -> &'static str64 fn scale_for_machines(&self, values: &mut [f64]) -> &'static str; 65 } 66 67 /// Trait for all types which define something Criterion.rs can measure. The only measurement 68 /// currently provided is [WallTime](struct.WallTime.html), but third party crates or benchmarks 69 /// may define more. 70 /// 71 /// This trait defines two core methods, `start` and `end`. `start` is called at the beginning of 72 /// a measurement to produce some intermediate value (for example, the wall-clock time at the start 73 /// of that set of iterations) and `end` is called at the end of the measurement with the value 74 /// returned by `start`. 75 /// 76 pub trait Measurement { 77 /// This type represents an intermediate value for the measurements. It will be produced by the 78 /// start function and passed to the end function. An example might be the wall-clock time as 79 /// of the `start` call. 80 type Intermediate; 81 82 /// This type is the measured value. An example might be the elapsed wall-clock time between the 83 /// `start` and `end` calls. 84 type Value; 85 86 /// Criterion.rs will call this before iterating the benchmark. start(&self) -> Self::Intermediate87 fn start(&self) -> Self::Intermediate; 88 89 /// Criterion.rs will call this after iterating the benchmark to get the measured value. end(&self, i: Self::Intermediate) -> Self::Value90 fn end(&self, i: Self::Intermediate) -> Self::Value; 91 92 /// Combine two values. Criterion.rs sometimes needs to perform measurements in multiple batches 93 /// of iterations, so the value from one batch must be added to the sum of the previous batches. add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value94 fn add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value; 95 96 /// Return a "zero" value for the Value type which can be added to another value. zero(&self) -> Self::Value97 fn zero(&self) -> Self::Value; 98 99 /// Converts the measured value to f64 so that it can be used in statistical analysis. to_f64(&self, value: &Self::Value) -> f64100 fn to_f64(&self, value: &Self::Value) -> f64; 101 102 /// Return a trait-object reference to the value formatter for this measurement. formatter(&self) -> &dyn ValueFormatter103 fn formatter(&self) -> &dyn ValueFormatter; 104 } 105 106 pub(crate) struct DurationFormatter; 107 impl DurationFormatter { bytes_per_second(&self, bytes: f64, typical: f64, values: &mut [f64]) -> &'static str108 fn bytes_per_second(&self, bytes: f64, typical: f64, values: &mut [f64]) -> &'static str { 109 let bytes_per_second = bytes * (1e9 / typical); 110 let (denominator, unit) = if bytes_per_second < 1024.0 { 111 (1.0, " B/s") 112 } else if bytes_per_second < 1024.0 * 1024.0 { 113 (1024.0, "KiB/s") 114 } else if bytes_per_second < 1024.0 * 1024.0 * 1024.0 { 115 (1024.0 * 1024.0, "MiB/s") 116 } else { 117 (1024.0 * 1024.0 * 1024.0, "GiB/s") 118 }; 119 120 for val in values { 121 let bytes_per_second = bytes * (1e9 / *val); 122 *val = bytes_per_second / denominator; 123 } 124 125 unit 126 } 127 elements_per_second(&self, elems: f64, typical: f64, values: &mut [f64]) -> &'static str128 fn elements_per_second(&self, elems: f64, typical: f64, values: &mut [f64]) -> &'static str { 129 let elems_per_second = elems * (1e9 / typical); 130 let (denominator, unit) = if elems_per_second < 1000.0 { 131 (1.0, " elem/s") 132 } else if elems_per_second < 1000.0 * 1000.0 { 133 (1000.0, "Kelem/s") 134 } else if elems_per_second < 1000.0 * 1000.0 * 1000.0 { 135 (1000.0 * 1000.0, "Melem/s") 136 } else { 137 (1000.0 * 1000.0 * 1000.0, "Gelem/s") 138 }; 139 140 for val in values { 141 let elems_per_second = elems * (1e9 / *val); 142 *val = elems_per_second / denominator; 143 } 144 145 unit 146 } 147 } 148 impl ValueFormatter for DurationFormatter { scale_throughputs( &self, typical: f64, throughput: &Throughput, values: &mut [f64], ) -> &'static str149 fn scale_throughputs( 150 &self, 151 typical: f64, 152 throughput: &Throughput, 153 values: &mut [f64], 154 ) -> &'static str { 155 match *throughput { 156 Throughput::Bytes(bytes) => self.bytes_per_second(bytes as f64, typical, values), 157 Throughput::Elements(elems) => self.elements_per_second(elems as f64, typical, values), 158 } 159 } 160 scale_values(&self, ns: f64, values: &mut [f64]) -> &'static str161 fn scale_values(&self, ns: f64, values: &mut [f64]) -> &'static str { 162 let (factor, unit) = if ns < 10f64.powi(0) { 163 (10f64.powi(3), "ps") 164 } else if ns < 10f64.powi(3) { 165 (10f64.powi(0), "ns") 166 } else if ns < 10f64.powi(6) { 167 (10f64.powi(-3), "us") 168 } else if ns < 10f64.powi(9) { 169 (10f64.powi(-6), "ms") 170 } else { 171 (10f64.powi(-9), "s") 172 }; 173 174 for val in values { 175 *val *= factor; 176 } 177 178 unit 179 } 180 scale_for_machines(&self, _values: &mut [f64]) -> &'static str181 fn scale_for_machines(&self, _values: &mut [f64]) -> &'static str { 182 // no scaling is needed 183 "ns" 184 } 185 } 186 187 /// `WallTime` is the default measurement in Criterion.rs. It measures the elapsed time from the 188 /// beginning of a series of iterations to the end. 189 pub struct WallTime; 190 impl Measurement for WallTime { 191 type Intermediate = Instant; 192 type Value = Duration; 193 start(&self) -> Self::Intermediate194 fn start(&self) -> Self::Intermediate { 195 Instant::now() 196 } end(&self, i: Self::Intermediate) -> Self::Value197 fn end(&self, i: Self::Intermediate) -> Self::Value { 198 i.elapsed() 199 } add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value200 fn add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value { 201 *v1 + *v2 202 } zero(&self) -> Self::Value203 fn zero(&self) -> Self::Value { 204 Duration::from_secs(0) 205 } to_f64(&self, val: &Self::Value) -> f64206 fn to_f64(&self, val: &Self::Value) -> f64 { 207 val.to_nanos() as f64 208 } formatter(&self) -> &dyn ValueFormatter209 fn formatter(&self) -> &dyn ValueFormatter { 210 &DurationFormatter 211 } 212 } 213