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