| 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. |
| 22 | 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. |
| 30 | 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. |
| 42 | 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. |
| 51 | 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. |
| 63 | 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. |
| 86 | fn start(&self) -> Self::Intermediate; |
| 87 | |
| 88 | /// Criterion.rs will call this after iterating the benchmark to get the measured value. |
| 89 | 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. |
| 93 | 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. |
| 96 | fn zero(&self) -> Self::Value; |
| 97 | |
| 98 | /// Converts the measured value to f64 so that it can be used in statistical analysis. |
| 99 | fn to_f64(&self, value: &Self::Value) -> f64; |
| 100 | |
| 101 | /// Return a trait-object reference to the value formatter for this measurement. |
| 102 | fn formatter(&self) -> &dyn ValueFormatter; |
| 103 | } |
| 104 | |
| 105 | pub(crate) struct DurationFormatter; |
| 106 | impl DurationFormatter { |
| 107 | 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 | |
| 127 | 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 | |
| 152 | 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 { |
| 173 | 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 | |
| 188 | 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 | |
| 208 | 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 | |
| 221 | fn start(&self) -> Self::Intermediate { |
| 222 | Instant::now() |
| 223 | } |
| 224 | fn end(&self, i: Self::Intermediate) -> Self::Value { |
| 225 | i.elapsed() |
| 226 | } |
| 227 | fn add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value { |
| 228 | *v1 + *v2 |
| 229 | } |
| 230 | fn zero(&self) -> Self::Value { |
| 231 | Duration::from_secs(0) |
| 232 | } |
| 233 | fn to_f64(&self, val: &Self::Value) -> f64 { |
| 234 | val.as_nanos() as f64 |
| 235 | } |
| 236 | fn formatter(&self) -> &dyn ValueFormatter { |
| 237 | &DurationFormatter |
| 238 | } |
| 239 | } |
| 240 | |