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::Throughput; 8 use std::time::{Duration, Instant}; 9 10 /// Trait providing functions to format measured values to string so that they can be displayed on 11 /// the command line or in the reports. The functions of this trait take measured values in f64 12 /// form; implementors can assume that the values are of the same scale as those produced by the 13 /// associated [MeasuredValue](trait.MeasuredValue.html) (eg. if your measurement produces values in 14 /// nanoseconds, the values passed to the formatter will be in nanoseconds). 15 /// 16 /// Implementors are encouraged to format the values in a way that is intuitive for humans and 17 /// uses the SI prefix system. For example, the format used by [WallTime](struct.WallTime.html) 18 /// can display the value in units ranging from picoseconds to seconds depending on the magnitude 19 /// of the elapsed time in nanoseconds. 20 pub trait ValueFormatter { 21 /// Format the value (with appropriate unit) and return it as a string. format_value(&self, value: f64) -> String22 fn format_value(&self, value: f64) -> String { 23 let mut values = [value]; 24 let unit = self.scale_values(value, &mut values); 25 format!("{:>6} {}", short(values[0]), unit) 26 } 27 28 /// Format the value as a throughput measurement. The value represents the measurement value; 29 /// the implementor will have to calculate bytes per second, iterations per cycle, etc. format_throughput(&self, throughput: &Throughput, value: f64) -> String30 fn format_throughput(&self, throughput: &Throughput, value: f64) -> String { 31 let mut values = [value]; 32 let unit = self.scale_throughputs(value, throughput, &mut values); 33 format!("{:>6} {}", short(values[0]), unit) 34 } 35 36 /// Scale the given values to some appropriate unit and return the unit string. 37 /// 38 /// The given typical value should be used to choose the unit. This function may be called 39 /// multiple times with different datasets; the typical value will remain the same to ensure 40 /// that the units remain consistent within a graph. The typical value will not be NaN. 41 /// 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 str42 fn scale_values(&self, typical_value: f64, values: &mut [f64]) -> &'static str; 43 44 /// Convert the given measured values into throughput numbers based on the given throughput 45 /// value, scale them to some appropriate unit, and return the unit string. 46 /// 47 /// The given typical value should be used to choose the unit. This function may be called 48 /// multiple times with different datasets; the typical value will remain the same to ensure 49 /// that the units remain consistent within a graph. The typical value will not be NaN. 50 /// 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 str51 fn scale_throughputs( 52 &self, 53 typical_value: f64, 54 throughput: &Throughput, 55 values: &mut [f64], 56 ) -> &'static str; 57 58 /// Scale the values and return a unit string designed for machines. 59 /// 60 /// For example, this is used for the CSV file output. Implementations should modify the given 61 /// values slice to apply the desired scaling (if any) and return a string representing the unit 62 /// the modified values are in. scale_for_machines(&self, values: &mut [f64]) -> &'static str63 fn scale_for_machines(&self, values: &mut [f64]) -> &'static str; 64 } 65 66 /// Trait for all types which define something Criterion.rs can measure. The only measurement 67 /// currently provided is [WallTime](struct.WallTime.html), but third party crates or benchmarks 68 /// may define more. 69 /// 70 /// This trait defines two core methods, `start` and `end`. `start` is called at the beginning of 71 /// a measurement to produce some intermediate value (for example, the wall-clock time at the start 72 /// of that set of iterations) and `end` is called at the end of the measurement with the value 73 /// returned by `start`. 74 /// 75 pub trait Measurement { 76 /// This type represents an intermediate value for the measurements. It will be produced by the 77 /// start function and passed to the end function. An example might be the wall-clock time as 78 /// of the `start` call. 79 type Intermediate; 80 81 /// This type is the measured value. An example might be the elapsed wall-clock time between the 82 /// `start` and `end` calls. 83 type Value; 84 85 /// Criterion.rs will call this before iterating the benchmark. start(&self) -> Self::Intermediate86 fn start(&self) -> Self::Intermediate; 87 88 /// Criterion.rs will call this after iterating the benchmark to get the measured value. end(&self, i: Self::Intermediate) -> Self::Value89 fn end(&self, i: Self::Intermediate) -> Self::Value; 90 91 /// Combine two values. Criterion.rs sometimes needs to perform measurements in multiple batches 92 /// 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::Value93 fn add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value; 94 95 /// Return a "zero" value for the Value type which can be added to another value. zero(&self) -> Self::Value96 fn zero(&self) -> Self::Value; 97 98 /// Converts the measured value to f64 so that it can be used in statistical analysis. to_f64(&self, value: &Self::Value) -> f6499 fn to_f64(&self, value: &Self::Value) -> f64; 100 101 /// Return a trait-object reference to the value formatter for this measurement. formatter(&self) -> &dyn ValueFormatter102 fn formatter(&self) -> &dyn ValueFormatter; 103 } 104 105 pub(crate) struct DurationFormatter; 106 impl DurationFormatter { bytes_per_second(&self, bytes: f64, typical: f64, values: &mut [f64]) -> &'static str107 fn bytes_per_second(&self, bytes: f64, typical: f64, values: &mut [f64]) -> &'static str { 108 let bytes_per_second = bytes * (1e9 / typical); 109 let (denominator, unit) = if bytes_per_second < 1024.0 { 110 (1.0, " B/s") 111 } else if bytes_per_second < 1024.0 * 1024.0 { 112 (1024.0, "KiB/s") 113 } else if bytes_per_second < 1024.0 * 1024.0 * 1024.0 { 114 (1024.0 * 1024.0, "MiB/s") 115 } else { 116 (1024.0 * 1024.0 * 1024.0, "GiB/s") 117 }; 118 119 for val in values { 120 let bytes_per_second = bytes * (1e9 / *val); 121 *val = bytes_per_second / denominator; 122 } 123 124 unit 125 } 126 bytes_per_second_decimal( &self, bytes: f64, typical: f64, values: &mut [f64], ) -> &'static str127 fn bytes_per_second_decimal( 128 &self, 129 bytes: f64, 130 typical: f64, 131 values: &mut [f64], 132 ) -> &'static str { 133 let bytes_per_second = bytes * (1e9 / typical); 134 let (denominator, unit) = if bytes_per_second < 1000.0 { 135 (1.0, " B/s") 136 } else if bytes_per_second < 1000.0 * 1000.0 { 137 (1000.0, "KB/s") 138 } else if bytes_per_second < 1000.0 * 1000.0 * 1000.0 { 139 (1000.0 * 1000.0, "MB/s") 140 } else { 141 (1000.0 * 1000.0 * 1000.0, "GB/s") 142 }; 143 144 for val in values { 145 let bytes_per_second = bytes * (1e9 / *val); 146 *val = bytes_per_second / denominator; 147 } 148 149 unit 150 } 151 elements_per_second(&self, elems: f64, typical: f64, values: &mut [f64]) -> &'static str152 fn elements_per_second(&self, elems: f64, typical: f64, values: &mut [f64]) -> &'static str { 153 let elems_per_second = elems * (1e9 / typical); 154 let (denominator, unit) = if elems_per_second < 1000.0 { 155 (1.0, " elem/s") 156 } else if elems_per_second < 1000.0 * 1000.0 { 157 (1000.0, "Kelem/s") 158 } else if elems_per_second < 1000.0 * 1000.0 * 1000.0 { 159 (1000.0 * 1000.0, "Melem/s") 160 } else { 161 (1000.0 * 1000.0 * 1000.0, "Gelem/s") 162 }; 163 164 for val in values { 165 let elems_per_second = elems * (1e9 / *val); 166 *val = elems_per_second / denominator; 167 } 168 169 unit 170 } 171 } 172 impl ValueFormatter for DurationFormatter { scale_throughputs( &self, typical: f64, throughput: &Throughput, values: &mut [f64], ) -> &'static str173 fn scale_throughputs( 174 &self, 175 typical: f64, 176 throughput: &Throughput, 177 values: &mut [f64], 178 ) -> &'static str { 179 match *throughput { 180 Throughput::Bytes(bytes) => self.bytes_per_second(bytes as f64, typical, values), 181 Throughput::BytesDecimal(bytes) => { 182 self.bytes_per_second_decimal(bytes as f64, typical, values) 183 } 184 Throughput::Elements(elems) => self.elements_per_second(elems as f64, typical, values), 185 } 186 } 187 scale_values(&self, ns: f64, values: &mut [f64]) -> &'static str188 fn scale_values(&self, ns: f64, values: &mut [f64]) -> &'static str { 189 let (factor, unit) = if ns < 10f64.powi(0) { 190 (10f64.powi(3), "ps") 191 } else if ns < 10f64.powi(3) { 192 (10f64.powi(0), "ns") 193 } else if ns < 10f64.powi(6) { 194 (10f64.powi(-3), "µs") 195 } else if ns < 10f64.powi(9) { 196 (10f64.powi(-6), "ms") 197 } else { 198 (10f64.powi(-9), "s") 199 }; 200 201 for val in values { 202 *val *= factor; 203 } 204 205 unit 206 } 207 scale_for_machines(&self, _values: &mut [f64]) -> &'static str208 fn scale_for_machines(&self, _values: &mut [f64]) -> &'static str { 209 // no scaling is needed 210 "ns" 211 } 212 } 213 214 /// `WallTime` is the default measurement in Criterion.rs. It measures the elapsed time from the 215 /// beginning of a series of iterations to the end. 216 pub struct WallTime; 217 impl Measurement for WallTime { 218 type Intermediate = Instant; 219 type Value = Duration; 220 start(&self) -> Self::Intermediate221 fn start(&self) -> Self::Intermediate { 222 Instant::now() 223 } end(&self, i: Self::Intermediate) -> Self::Value224 fn end(&self, i: Self::Intermediate) -> Self::Value { 225 i.elapsed() 226 } add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value227 fn add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value { 228 *v1 + *v2 229 } zero(&self) -> Self::Value230 fn zero(&self) -> Self::Value { 231 Duration::from_secs(0) 232 } to_f64(&self, val: &Self::Value) -> f64233 fn to_f64(&self, val: &Self::Value) -> f64 { 234 val.as_nanos() as f64 235 } formatter(&self) -> &dyn ValueFormatter236 fn formatter(&self) -> &dyn ValueFormatter { 237 &DurationFormatter 238 } 239 } 240