criterion_inverted_throughput/
lib.rs1use criterion::measurement::{Measurement, ValueFormatter, WallTime};
53use criterion::Throughput;
54
55pub struct InvertedThroughput(WallTime);
60
61impl InvertedThroughput {
62 pub fn new() -> Self {
64 InvertedThroughput(WallTime)
65 }
66}
67
68impl Default for InvertedThroughput {
69 fn default() -> Self {
70 Self::new()
71 }
72}
73
74impl Measurement for InvertedThroughput {
75 type Intermediate = <WallTime as Measurement>::Intermediate;
76 type Value = <WallTime as Measurement>::Value;
77 fn start(&self) -> Self::Intermediate {
78 self.0.start()
79 }
80 fn end(&self, i: Self::Intermediate) -> Self::Value {
81 self.0.end(i)
82 }
83
84 fn add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value {
85 self.0.add(v1, v2)
86 }
87 fn zero(&self) -> Self::Value {
88 self.0.zero()
89 }
90 fn to_f64(&self, val: &Self::Value) -> f64 {
91 self.0.to_f64(val)
92 }
93
94 fn formatter(&self) -> &dyn ValueFormatter {
95 self
96 }
97}
98
99impl InvertedThroughput {
100 fn time_per_unit(&self, units: f64, typical_value: f64, values: &mut [f64]) -> &'static str {
101 let typical_time = typical_value / units;
102 for val in &mut *values {
103 let val_per_unit = *val / units;
104 *val = val_per_unit;
105 }
106 self.0.formatter().scale_values(typical_time, values)
107 }
108
109 fn static_denom(&self, time_denom: &str, unit_denom: &str) -> &'static str {
110 match (unit_denom, time_denom) {
111 ("byte", "ps") => "ps/byte",
112 ("byte", "ns") => "ns/byte",
113 ("byte", "µs") => "µs/byte",
114 ("byte", "ms") => "ms/byte",
115 ("byte", "s") => "s/byte",
116 ("elem", "ps") => "ps/elem",
117 ("elem", "ns") => "ns/elem",
118 ("elem", "µs") => "µs/elem",
119 ("elem", "ms") => "ms/elem",
120 ("elem", "s") => "s/elem",
121 _ => "UNEXPECTED",
122 }
123 }
124}
125
126impl ValueFormatter for InvertedThroughput {
127 fn scale_values(&self, typical_value: f64, values: &mut [f64]) -> &'static str {
128 self.0.formatter().scale_values(typical_value, values)
129 }
130
131 fn scale_throughputs(
132 &self,
133 typical_value: f64,
134 throughput: &Throughput,
135 values: &mut [f64],
136 ) -> &'static str {
137 let (t_val, t_unit) = match *throughput {
138 Throughput::Bytes(v) => (v as f64, "byte"),
139 Throughput::BytesDecimal(v) => (v as f64, "byte"),
140 Throughput::Elements(v) => (v as f64, "elem"),
141 };
142 self.static_denom(self.time_per_unit(t_val, typical_value, values), t_unit)
143 }
144
145 fn scale_for_machines(&self, values: &mut [f64]) -> &'static str {
146 self.0.formatter().scale_for_machines(values)
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use test_case::test_case;
154
155 #[derive(Clone)]
156 struct Data {
157 typical_value: f64,
158 values: Vec<f64>,
159 throughput: Throughput,
160 }
161
162 impl Data {
163 fn new(typical_value: f64, throughput: Throughput) -> Self {
164 let mut values: Vec<f64> = vec![];
165 for x in -5..5 {
166 values.push(typical_value * (1f64 - (x as f64 * 0.02)))
168 }
169 Self {
170 typical_value,
171 values,
172 throughput,
173 }
174 }
175 }
176
177 enum Unit {
178 Element,
179 Byte,
180 ByteDecimal,
181 }
182
183 fn normalize_time(denom: &str, value: f64) -> f64 {
184 if denom.to_string().starts_with("ps") {
185 value / 1e12
186 } else if denom.to_string().starts_with("ns") {
187 value / 1e9
188 } else if denom.to_string().starts_with("µs") {
189 value / 1e6
190 } else if denom.to_string().starts_with("ms") {
191 value / 1e3
192 } else if denom.to_string().starts_with("s") {
193 value
194 } else {
195 panic!("Unexpected denom for time: {}", denom)
196 }
197 }
198
199 fn normalize_amount(denom: &str, value: f64) -> f64 {
200 if denom.to_string().starts_with("G") {
201 value * 1e9
202 } else if denom.to_string().starts_with("M") {
203 value * 1e6
204 } else if denom.to_string().starts_with("K") {
205 value * 1e3
206 } else {
207 value
208 }
209 }
210
211 fn assert_nearly_eq(a: Vec<f64>, b: Vec<f64>) {
212 assert_eq!(a.len(), b.len(), "left: {:?} !~= right: {:?}", a, b);
213 for i in 0..a.len() {
214 assert_ne!(a[i].abs(), 0.0, "left: {:?} !~= right: {:?}", a, b);
215 assert!(
216 (a[i] - b[i]).abs() < a[i].abs() * 1e-12,
217 "left: {:?} !~= right: {:?}",
218 a,
219 b
220 )
221 }
222 }
223
224 fn assert_nearly_inversion(a: Vec<f64>, b: Vec<f64>) {
225 assert_eq!(
226 a.len(),
227 b.len(),
228 "left: {:?} <not inversion> right: {:?}",
229 a,
230 b
231 );
232 for i in 0..a.len() {
233 assert_ne!(
234 a[i].abs(),
235 0.0,
236 "left: {:?} <not inversion> right: {:?}",
237 a,
238 b
239 );
240 assert!(
241 (a[i] * b[i] - 1f64).abs() < 0.075,
242 "left: {:?} <not inversion> right: {:?} (index: {}, abs(sub(1.0)): {})",
243 a,
244 b,
245 i,
246 (a[i] * b[i] - 1f64).abs(),
247 )
248 }
249 }
250
251 #[test_case(Unit::Element, 1, 1e3 ; "test 1 elements")]
252 #[test_case(Unit::Element, 10, 1e6 ; "test 10 elements")]
253 #[test_case(Unit::Byte, 100, 1e9 ; "test 100 bytes")]
254 #[test_case(Unit::ByteDecimal, 1000, 1e12 ; "test 1000 bytesdecimal")]
255 #[test_case(Unit::Element, 123, 1.234e15 ; "test 123 elements")]
256 #[test_case(Unit::Byte, 123_456_789, 1.234e6 ; "test big bytes")]
257 fn test_invert_throughput(unit: Unit, amount: u64, typical_value: f64) {
258 let throughput = match unit {
260 Unit::Element => Throughput::Elements(amount),
261 Unit::Byte => Throughput::Bytes(amount),
262 Unit::ByteDecimal => Throughput::BytesDecimal(amount),
263 };
264 let data = Data::new(typical_value, throughput.clone());
265
266 let default_measure = WallTime;
268 let our_measure = InvertedThroughput(WallTime);
269
270 let mut values_by_default = data.values.clone();
272 let mut throughputs_by_default = data.values.clone();
273 let mut inverted_throughputs = data.values.clone();
274
275 let unit_by_default = default_measure
276 .formatter()
277 .scale_values(data.typical_value, &mut values_by_default);
278 let unit_by_default_throughputs = default_measure.formatter().scale_throughputs(
279 data.typical_value,
280 &data.throughput,
281 &mut throughputs_by_default,
282 );
283 let unit_inverted_throughputs = our_measure.scale_throughputs(
284 data.typical_value,
285 &data.throughput,
286 &mut inverted_throughputs,
287 );
288
289 let expected_inverted_throuputs: Vec<f64> = values_by_default
290 .iter()
291 .map(|x| normalize_time(unit_by_default, *x) / amount as f64)
292 .collect();
293 let normalized_default_throuputs: Vec<f64> = throughputs_by_default
294 .iter()
295 .map(|x| normalize_amount(unit_by_default_throughputs, *x))
296 .collect();
297 let normalized_inverted_throuputs: Vec<f64> = inverted_throughputs
298 .iter()
299 .map(|x| normalize_time(unit_inverted_throughputs, *x))
300 .collect();
301
302 assert_nearly_eq(
303 expected_inverted_throuputs,
304 normalized_inverted_throuputs.clone(),
305 );
306 assert_nearly_inversion(normalized_inverted_throuputs, normalized_default_throuputs);
307 }
308}