1#![doc = include_str!("../README.md")]
2
3use std::cmp::max;
4use std::fmt::{self, Debug, Display, Formatter};
5use std::mem;
6use std::ops::Add;
7use std::ptr;
8use std::rc::Rc;
9use std::time::Duration;
10
11use precision::*;
12
13#[derive(Clone, Debug)]
15pub struct Options {
16 pub iterations: u64,
18 pub warmup_iterations: u64,
20 pub min_samples: usize,
22 pub max_samples: usize,
24 pub max_rsd: f64,
26 pub max_duration: Option<Duration>,
28 pub verbose: bool,
30}
31
32impl Default for Options {
33 fn default() -> Self {
34 let mut verbose = false;
35 std::env::var("BENCHMARK_VERBOSE")
36 .map(|_| verbose = true)
37 .ok();
38
39 Self {
40 iterations: 1,
41 warmup_iterations: 0,
42 min_samples: 3,
43 max_samples: 5,
44 max_rsd: 5.0,
45 verbose,
46 max_duration: None,
47 }
48 }
49}
50
51#[derive(Clone)]
53pub struct BenchResult {
54 elapsed: Elapsed,
55 precision: Precision,
56 options: Rc<Options>,
57}
58
59impl Add for BenchResult {
60 type Output = BenchResult;
61
62 fn add(self, other: BenchResult) -> Self::Output {
63 BenchResult {
64 elapsed: self.elapsed + other.elapsed,
65 precision: self.precision,
66 options: self.options,
67 }
68 }
69}
70
71impl BenchResult {
72 pub fn ticks(&self) -> u64 {
74 self.elapsed.ticks()
75 }
76
77 pub fn as_secs(&self) -> u64 {
79 self.elapsed.as_secs(&self.precision)
80 }
81
82 pub fn as_secs_f64(&self) -> f64 {
84 self.elapsed.as_secs_f64(&self.precision)
85 }
86
87 pub fn as_millis(&self) -> u64 {
89 self.elapsed.as_millis(&self.precision)
90 }
91
92 pub fn as_ns(&self) -> u64 {
94 self.elapsed.as_ns(&self.precision)
95 }
96
97 pub fn throughput(self, mut volume: u128) -> Throughput {
100 volume *= self.options.iterations as u128;
101 Throughput {
102 volume: volume as f64,
103 result: self,
104 unit: Unit::None,
105 }
106 }
107
108 pub fn throughput_bits(self, mut volume: u128) -> Throughput {
111 volume *= self.options.iterations as u128;
112 volume *= 8;
113 Throughput {
114 volume: volume as f64,
115 result: self,
116 unit: Unit::Bits,
117 }
118 }
119
120 pub fn throughput_bytes(self, mut volume: u128) -> Throughput {
123 volume *= self.options.iterations as u128;
124 volume *= 8;
125 Throughput {
126 volume: volume as f64,
127 result: self,
128 unit: Unit::Bytes,
129 }
130 }
131}
132
133impl Display for BenchResult {
134 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
135 write!(f, "{:.2}s", self.as_secs_f64())
136 }
137}
138
139impl Debug for BenchResult {
140 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
141 write!(f, "{}", self)
142 }
143}
144
145#[derive(Clone, Copy, Debug, Eq, PartialEq)]
147#[derive(Default)]
148pub enum Unit {
149 #[default]
151 None,
152 Bytes,
154 Bits,
156}
157
158impl Display for Unit {
159 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
160 match self {
161 Unit::None => write!(f, ""),
162 Unit::Bytes => write!(f, "B"),
163 Unit::Bits => write!(f, "b"),
164 }
165 }
166}
167
168
169#[derive(Clone)]
171pub struct Throughput {
172 volume: f64,
173 result: BenchResult,
174 unit: Unit,
175}
176
177impl Throughput {
178 pub fn as_f64(&self) -> f64 {
180 self.volume * 1_000_000_000f64 / (max(1, self.result.as_ns()) as f64)
181 }
182
183 pub fn as_u128(&self) -> u128 {
185 self.volume as u128 * 1_000_000_000 / (max(1, self.result.as_ns()) as u128)
186 }
187
188 pub fn as_kib(&self) -> f64 {
190 self.volume * 1_000_000_000f64 / (max(1, self.result.as_ns()) as f64) / 1024.0
191 }
192
193 pub fn as_mib(&self) -> f64 {
195 self.volume * 1_000_000_000f64 / (max(1, self.result.as_ns()) as f64) / (1024.0 * 1024.0)
196 }
197
198 pub fn as_gib(&self) -> f64 {
200 self.volume * 1_000_000_000f64
201 / (max(1, self.result.as_ns()) as f64)
202 / (1024.0 * 1024.0 * 1024.0)
203 }
204
205 pub fn as_kb(&self) -> f64 {
207 self.volume * 1_000_000_000f64 / (max(1, self.result.as_ns()) as f64) / 1000.0
208 }
209
210 pub fn as_mb(&self) -> f64 {
212 self.volume * 1_000_000_000f64 / (max(1, self.result.as_ns()) as f64) / (1000.0 * 1000.0)
213 }
214
215 pub fn as_gb(&self) -> f64 {
217 self.volume * 1_000_000_000f64
218 / (max(1, self.result.as_ns()) as f64)
219 / (1000.0 * 1000.0 * 1000.0)
220 }
221
222 pub fn as_kb8(&self) -> f64 {
224 self.volume * 8_000_000_000f64 / (max(1, self.result.as_ns()) as f64) / 1000.0
225 }
226
227 pub fn as_mb8(&self) -> f64 {
229 self.volume * 8_000_000_000f64 / (max(1, self.result.as_ns()) as f64) / (1000.0 * 1000.0)
230 }
231
232 pub fn as_gb8(&self) -> f64 {
234 self.volume * 8_000_000_000f64
235 / (max(1, self.result.as_ns()) as f64)
236 / (1000.0 * 1000.0 * 1000.0)
237 }
238}
239
240impl Display for Throughput {
241 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
242 match self.unit {
243 Unit::None => match self.as_u128() {
244 0..=999 => write!(f, "{:.2} /s", self.as_f64()),
245 1_000..=999_999 => write!(f, "{:.2} K/s", self.as_kb()),
246 1_000_000..=999_999_999 => write!(f, "{:.2} M/s", self.as_mb()),
247 _ => write!(f, "{:.2} G/s", self.as_gb()),
248 },
249 Unit::Bytes => match self.as_u128() {
250 0..=999 => write!(f, "{:.2} B/s", self.as_f64()),
251 1_000..=999_999 => write!(f, "{:.2} KB/s", self.as_kb()),
252 1_000_000..=999_999_999 => write!(f, "{:.2} MB/s", self.as_mb()),
253 _ => write!(f, "{:.2} GB/s", self.as_gb()),
254 },
255 Unit::Bits => match self.as_u128() {
256 0..=999 => write!(f, "{:.2} b/s", self.as_f64()),
257 1_000..=999_999 => write!(f, "{:.2} Kb/s", self.as_kb()),
258 1_000_000..=999_999_999 => write!(f, "{:.2} Mb/s", self.as_mb()),
259 _ => write!(f, "{:.2} Gb/s", self.as_gb()),
260 },
261 }
262 }
263}
264
265impl Debug for Throughput {
266 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
267 write!(f, "{}", self)
268 }
269}
270
271#[derive(Clone)]
273pub struct Bench {
274 precision: Precision,
275}
276
277impl Bench {
278 pub fn new() -> Self {
280 let precision = Precision::new(Default::default()).unwrap();
281 Bench { precision }
282 }
283
284 fn run_once<F, G>(&self, options: Rc<Options>, f: &mut F) -> BenchResult
285 where
286 F: FnMut() -> G,
287 {
288 let iterations = options.iterations;
289 let start = self.precision.now();
290 for _ in 0..iterations {
291 black_box(f());
292 }
293 let elapsed = self.precision.now() - start;
294 BenchResult {
295 elapsed,
296 precision: self.precision.clone(),
297 options,
298 }
299 }
300
301 pub fn run<F, G>(&self, options: &Options, mut f: F) -> BenchResult
303 where
304 F: FnMut() -> G,
305 {
306 let options = Rc::new(options.clone());
307 let max_samples = std::cmp::max(1, options.max_samples);
308 let verbose = options.verbose;
309
310 if verbose {
311 println!("Starting a new benchmark.");
312 if options.warmup_iterations > 0 {
313 println!("Warming up for {} iterations.", options.warmup_iterations);
314 }
315 }
316 for _ in 0..options.warmup_iterations {
317 black_box(f());
318 }
319 let mut results = Vec::with_capacity(max_samples);
320 let start = self.precision.now();
321 for i in 1..=max_samples {
322 if verbose {
323 println!("Running iteration {}.", i);
324 }
325 let result = self.run_once(options.clone(), &mut f);
326 results.push(result);
327 if results.len() <= 1 {
328 if verbose {
329 println!("Iteration {}: {}", i, results.last().unwrap());
330 }
331 continue;
332 }
333 let mean = results.iter().map(|r| r.as_secs_f64()).sum::<f64>() / results.len() as f64;
334 let std_dev = (results
335 .iter()
336 .map(|r| (r.as_secs_f64() - mean).powi(2))
337 .sum::<f64>()
338 / (results.len() - 1) as f64)
339 .sqrt();
340 let rsd = std_dev * 100.0 / mean;
341 if verbose {
342 println!("Iteration {}: {:.2}s ± {:.2}%", i, mean, rsd);
343 }
344 if i >= options.min_samples && rsd < options.max_rsd {
345 if verbose {
346 println!("Enough samples have been collected.");
347 }
348 break;
349 }
350 if let Some(max_duration) = options.max_duration {
351 let elapsed =
352 Duration::from_secs((self.precision.now() - start).as_secs(&self.precision));
353 if elapsed >= max_duration {
354 if verbose {
355 println!("Timeout.");
356 }
357 break;
358 }
359 }
360 }
361 let result = results.into_iter().min_by_key(|r| r.as_ns()).unwrap();
362 if verbose {
363 println!("Result: {}", result);
364 }
365 result
366 }
367}
368
369impl Default for Bench {
370 fn default() -> Self {
371 Self::new()
372 }
373}
374
375#[inline(never)]
378pub fn black_box<T>(dummy: T) -> T {
379 let ret = unsafe { ptr::read_volatile(&dummy) };
380 mem::forget(dummy);
381 ret
382}