use std::time::{Duration, Instant};
#[derive(Clone, Debug)]
pub struct Measurement {
pub name: String,
pub median: Duration,
pub best: Duration,
pub worst: Duration,
pub runs: usize,
pub iters_per_run: usize,
}
impl Measurement {
pub fn report(&self) -> String {
format!(
"{}: median {:?} (best {:?}, worst {:?}) over {} runs x {} iters",
self.name, self.median, self.best, self.worst, self.runs, self.iters_per_run
)
}
pub fn assert_under(&self, budget: Duration) {
assert!(
self.median <= budget,
"PERF BUDGET EXCEEDED: {} (budget {:?})",
self.report(),
budget
);
}
}
pub fn time_median<F: FnMut(usize)>(
name: &str,
warmup: usize,
runs: usize,
iters: usize,
mut f: F,
) -> Measurement {
assert!(
runs >= 1 && iters >= 1,
"need at least one run and one iter"
);
for i in 0..warmup {
f(i);
}
let mut per_iter: Vec<Duration> = Vec::with_capacity(runs);
for r in 0..runs {
let start = Instant::now();
for i in 0..iters {
f(r * iters + i);
}
per_iter.push(start.elapsed() / iters as u32);
}
per_iter.sort();
Measurement {
name: name.to_string(),
median: per_iter[per_iter.len() / 2],
best: per_iter[0],
worst: *per_iter.last().expect("runs >= 1"),
runs,
iters_per_run: iters,
}
}
pub fn sink<T>(value: T) -> T {
std::hint::black_box(value)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn median_is_computed() {
let mut n = 0u64;
let m = time_median("noop", 2, 5, 100, |i| {
n = n.wrapping_add(i as u64);
sink(n);
});
assert_eq!(m.runs, 5);
assert!(m.best <= m.median && m.median <= m.worst);
}
#[test]
#[should_panic(expected = "PERF BUDGET EXCEEDED")]
fn budget_violation_panics() {
let m = time_median("sleepy", 0, 1, 1, |_| {
std::thread::sleep(Duration::from_millis(2));
});
m.assert_under(Duration::from_nanos(1));
}
}