use std::fmt; use std::time::{Instant, Duration}; const MIN_DUR: f64 = 1.0; const MIN_TIMES: usize = 4; pub struct Bench { pub executions: usize, pub duration: f64, pub duration_stderr: f64, } impl fmt::Display for Bench { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} secs ± {} ({}%)", self.duration, self.duration_stderr, self.duration_stderr / self.duration * 100.0) } } fn duration_secs(d: Duration) -> f64 { d.as_secs() as f64 + d.subsec_nanos() as f64 / 1.0e9 } // Returns estimate of single-iteration time fn warmup A>(func: F) -> f64 { let mut ntimes = 1; let mut time_taken = 0.0; while time_taken < 0.5 { let start = Instant::now(); for _ in 0..ntimes { func(); } let end = Instant::now(); time_taken = duration_secs(end - start); ntimes *= 2; } time_taken / ntimes as f64 } pub fn benchmark A>(func: F) -> Bench { let estimate = warmup(&func); let ntimes_guess = if MIN_TIMES as f64 * estimate >= MIN_DUR { MIN_TIMES } else { (MIN_DUR / estimate).ceil() as usize }; let nparts = if ntimes_guess < 10 { ntimes_guess } else { std::cmp::max(10, ntimes_guess / 100) }; let nperpart = (ntimes_guess + nparts - 1) / nparts; // eprintln!("BENCH: estimate={} ntimes_guess={} nparts={} nperpart={} ntimes={}", estimate, ntimes_guess, nparts, nperpart, ntimes); let mut times = Vec::new(); let mut mean = 0.0; let mut stderr = 0.0; eprint!("BENCH: Running {} blocks of {} calls...", nparts, nperpart); for _ in 0..6 { for _ in 0..nparts { let start = Instant::now(); for _ in 0..nperpart { func(); } let end = Instant::now(); times.push(duration_secs(end - start)); } let partmean = times.iter().map(|&t| t).sum::() / times.len() as f64; let stddev = (times.iter().map(|&t| (t - partmean) * (t - partmean)).sum::() / (times.len() - 1) as f64).sqrt(); stderr = stddev / (times.len() as f64).sqrt(); mean = partmean / nperpart as f64; if stderr / mean < 0.05 { break; } eprint!(" again({},{})", mean, stderr); } eprintln!(""); Bench { executions: times.len() * nperpart, duration: mean, duration_stderr: stderr } }