1 //! Benchmarking module.
2 pub use std::hint::black_box;
9 test_result::TestResult,
14 use std::time::{Duration, Instant};
17 use std::panic::{catch_unwind, AssertUnwindSafe};
18 use std::sync::{Arc, Mutex};
20 /// Manager of the benchmarking runs.
22 /// This is fed into functions marked with `#[bench]` to allow for
23 /// set-up & tear-down before running a piece of code repeatedly via a
28 summary: Option<stats::Summary>,
33 /// Callback for benchmark functions to run in their body.
34 pub fn iter<T, F>(&mut self, mut inner: F)
38 if self.mode == BenchMode::Single {
39 ns_iter_inner(&mut inner, 1);
43 self.summary = Some(iter(&mut inner));
46 pub fn bench<F>(&mut self, mut f: F) -> Option<stats::Summary>
48 F: FnMut(&mut Bencher),
55 #[derive(Debug, Clone, PartialEq)]
56 pub struct BenchSamples {
57 pub ns_iter_summ: stats::Summary,
61 pub fn fmt_bench_samples(bs: &BenchSamples) -> String {
63 let mut output = String::new();
65 let median = bs.ns_iter_summ.median as usize;
66 let deviation = (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as usize;
69 .write_fmt(format_args!(
70 "{:>11} ns/iter (+/- {})",
71 fmt_thousands_sep(median, ','),
72 fmt_thousands_sep(deviation, ',')
77 .write_fmt(format_args!(" = {} MB/s", bs.mb_s))
83 // Format a number with thousands separators
84 fn fmt_thousands_sep(mut n: usize, sep: char) -> String {
86 let mut output = String::new();
87 let mut trailing = false;
88 for &pow in &[9, 6, 3, 0] {
89 let base = 10_usize.pow(pow);
90 if pow == 0 || trailing || n / base != 0 {
92 output.write_fmt(format_args!("{}", n / base)).unwrap();
94 output.write_fmt(format_args!("{:03}", n / base)).unwrap();
107 fn ns_from_dur(dur: Duration) -> u64 {
108 dur.as_secs() * 1_000_000_000 + (dur.subsec_nanos() as u64)
111 fn ns_iter_inner<T, F>(inner: &mut F, k: u64) -> u64
115 let start = Instant::now();
119 ns_from_dur(start.elapsed())
122 pub fn iter<T, F>(inner: &mut F) -> stats::Summary
126 // Initial bench run to get ballpark figure.
127 let ns_single = ns_iter_inner(inner, 1);
129 // Try to estimate iter count for 1ms falling back to 1m
130 // iterations if first run took < 1ns.
131 let ns_target_total = 1_000_000; // 1ms
132 let mut n = ns_target_total / cmp::max(1, ns_single);
134 // if the first run took more than 1ms we don't want to just
135 // be left doing 0 iterations on every loop. The unfortunate
136 // side effect of not being able to do as many runs is
137 // automatically handled by the statistical analysis below
138 // (i.e., larger error bars).
141 let mut total_run = Duration::new(0, 0);
142 let samples: &mut [f64] = &mut [0.0_f64; 50];
144 let loop_start = Instant::now();
146 for p in &mut *samples {
147 *p = ns_iter_inner(inner, n) as f64 / n as f64;
150 stats::winsorize(samples, 5.0);
151 let summ = stats::Summary::new(samples);
153 for p in &mut *samples {
154 let ns = ns_iter_inner(inner, 5 * n);
155 *p = ns as f64 / (5 * n) as f64;
158 stats::winsorize(samples, 5.0);
159 let summ5 = stats::Summary::new(samples);
161 let loop_run = loop_start.elapsed();
163 // If we've run for 100ms and seem to have converged to a
165 if loop_run > Duration::from_millis(100)
166 && summ.median_abs_dev_pct < 1.0
167 && summ.median - summ5.median < summ5.median_abs_dev
172 total_run = total_run + loop_run;
173 // Longest we ever run for is 3s.
174 if total_run > Duration::from_secs(3) {
178 // If we overflow here just return the results so far. We check a
179 // multiplier of 10 because we're about to multiply by 2 and the
180 // next iteration of the loop will also multiply by 5 (to calculate
182 n = match n.checked_mul(10) {
191 pub fn benchmark<F>(desc: TestDesc, monitor_ch: Sender<CompletedTest>, nocapture: bool, f: F)
193 F: FnMut(&mut Bencher),
195 let mut bs = Bencher {
196 mode: BenchMode::Auto,
201 let data = Arc::new(Mutex::new(Vec::new()));
202 let oldio = if !nocapture {
204 io::set_print(Some(Sink::new_boxed(&data))),
205 io::set_panic(Some(Sink::new_boxed(&data))),
211 let result = catch_unwind(AssertUnwindSafe(|| bs.bench(f)));
213 if let Some((printio, panicio)) = oldio {
214 io::set_print(printio);
215 io::set_panic(panicio);
218 let test_result = match result {
220 Ok(Some(ns_iter_summ)) => {
221 let ns_iter = cmp::max(ns_iter_summ.median as u64, 1);
222 let mb_s = bs.bytes * 1000 / ns_iter;
224 let bs = BenchSamples {
228 TestResult::TrBench(bs)
231 // iter not called, so no data.
232 // FIXME: error in this case?
233 let samples: &mut [f64] = &mut [0.0_f64; 1];
234 let bs = BenchSamples {
235 ns_iter_summ: stats::Summary::new(samples),
238 TestResult::TrBench(bs)
240 Err(_) => TestResult::TrFailed,
243 let stdout = data.lock().unwrap().to_vec();
244 let message = CompletedTest::new(desc, test_result, None, stdout);
245 monitor_ch.send(message).unwrap();
248 pub fn run_once<F>(f: F)
250 F: FnMut(&mut Bencher),
252 let mut bs = Bencher {
253 mode: BenchMode::Single,