criterion/
measurement.rs

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
6use crate::format::short;
7use crate::Throughput;
8use 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.
20pub 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///
75pub 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
105pub(crate) struct DurationFormatter;
106impl 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}
172impl 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.
216pub struct WallTime;
217impl 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}