• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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