1 //! Benchmarking module.
2 pub use std
::hint
::black_box
;
5 event
::CompletedTest
, helpers
::sink
::Sink
, options
::BenchMode
, test_result
::TestResult
,
6 types
::TestDesc
, Sender
,
12 use std
::panic
::{catch_unwind, AssertUnwindSafe}
;
13 use std
::sync
::{Arc, Mutex}
;
14 use std
::time
::{Duration, Instant}
;
16 /// Manager of the benchmarking runs.
18 /// This is fed into functions marked with `#[bench]` to allow for
19 /// set-up & tear-down before running a piece of code repeatedly via a
24 summary
: Option
<stats
::Summary
>,
29 /// Callback for benchmark functions to run in their body.
30 pub fn iter
<T
, F
>(&mut self, mut inner
: F
)
34 if self.mode
== BenchMode
::Single
{
35 ns_iter_inner(&mut inner
, 1);
39 self.summary
= Some(iter(&mut inner
));
42 pub fn bench
<F
>(&mut self, mut f
: F
) -> Option
<stats
::Summary
>
44 F
: FnMut(&mut Bencher
),
51 #[derive(Debug, Clone, PartialEq)]
52 pub struct BenchSamples
{
53 pub ns_iter_summ
: stats
::Summary
,
57 pub fn fmt_bench_samples(bs
: &BenchSamples
) -> String
{
59 let mut output
= String
::new();
61 let median
= bs
.ns_iter_summ
.median
as usize;
62 let deviation
= (bs
.ns_iter_summ
.max
- bs
.ns_iter_summ
.min
) as usize;
66 "{:>11} ns/iter (+/- {})",
67 fmt_thousands_sep(median
, '
,'
),
68 fmt_thousands_sep(deviation
, '
,'
)
72 write
!(output
, " = {} MB/s", bs
.mb_s
).unwrap();
77 // Format a number with thousands separators
78 fn fmt_thousands_sep(mut n
: usize, sep
: char) -> String
{
80 let mut output
= String
::new();
81 let mut trailing
= false;
82 for &pow
in &[9, 6, 3, 0] {
83 let base
= 10_usize
.pow(pow
);
84 if pow
== 0 || trailing
|| n
/ base
!= 0 {
86 write
!(output
, "{}", n
/ base
).unwrap();
88 write
!(output
, "{:03}", n
/ base
).unwrap();
101 fn ns_iter_inner
<T
, F
>(inner
: &mut F
, k
: u64) -> u64
105 let start
= Instant
::now();
109 start
.elapsed().as_nanos() as u64
112 pub fn iter
<T
, F
>(inner
: &mut F
) -> stats
::Summary
116 // Initial bench run to get ballpark figure.
117 let ns_single
= ns_iter_inner(inner
, 1);
119 // Try to estimate iter count for 1ms falling back to 1m
120 // iterations if first run took < 1ns.
121 let ns_target_total
= 1_000_000; // 1ms
122 let mut n
= ns_target_total
/ cmp
::max(1, ns_single
);
124 // if the first run took more than 1ms we don't want to just
125 // be left doing 0 iterations on every loop. The unfortunate
126 // side effect of not being able to do as many runs is
127 // automatically handled by the statistical analysis below
128 // (i.e., larger error bars).
131 let mut total_run
= Duration
::new(0, 0);
132 let samples
: &mut [f64] = &mut [0.0_f64; 50];
134 let loop_start
= Instant
::now();
136 for p
in &mut *samples
{
137 *p
= ns_iter_inner(inner
, n
) as f64 / n
as f64;
140 stats
::winsorize(samples
, 5.0);
141 let summ
= stats
::Summary
::new(samples
);
143 for p
in &mut *samples
{
144 let ns
= ns_iter_inner(inner
, 5 * n
);
145 *p
= ns
as f64 / (5 * n
) as f64;
148 stats
::winsorize(samples
, 5.0);
149 let summ5
= stats
::Summary
::new(samples
);
151 let loop_run
= loop_start
.elapsed();
153 // If we've run for 100ms and seem to have converged to a
155 if loop_run
> Duration
::from_millis(100)
156 && summ
.median_abs_dev_pct
< 1.0
157 && summ
.median
- summ5
.median
< summ5
.median_abs_dev
162 total_run
+= loop_run
;
163 // Longest we ever run for is 3s.
164 if total_run
> Duration
::from_secs(3) {
168 // If we overflow here just return the results so far. We check a
169 // multiplier of 10 because we're about to multiply by 2 and the
170 // next iteration of the loop will also multiply by 5 (to calculate
172 n
= match n
.checked_mul(10) {
181 pub fn benchmark
<F
>(desc
: TestDesc
, monitor_ch
: Sender
<CompletedTest
>, nocapture
: bool
, f
: F
)
183 F
: FnMut(&mut Bencher
),
185 let mut bs
= Bencher { mode: BenchMode::Auto, summary: None, bytes: 0 }
;
187 let data
= Arc
::new(Mutex
::new(Vec
::new()));
188 let oldio
= if !nocapture
{
190 io
::set_print(Some(Sink
::new_boxed(&data
))),
191 io
::set_panic(Some(Sink
::new_boxed(&data
))),
197 let result
= catch_unwind(AssertUnwindSafe(|| bs
.bench(f
)));
199 if let Some((printio
, panicio
)) = oldio
{
200 io
::set_print(printio
);
201 io
::set_panic(panicio
);
204 let test_result
= match result
{
206 Ok(Some(ns_iter_summ
)) => {
207 let ns_iter
= cmp
::max(ns_iter_summ
.median
as u64, 1);
208 let mb_s
= bs
.bytes
* 1000 / ns_iter
;
210 let bs
= BenchSamples { ns_iter_summ, mb_s: mb_s as usize }
;
211 TestResult
::TrBench(bs
)
214 // iter not called, so no data.
215 // FIXME: error in this case?
216 let samples
: &mut [f64] = &mut [0.0_f64; 1];
217 let bs
= BenchSamples { ns_iter_summ: stats::Summary::new(samples), mb_s: 0 }
;
218 TestResult
::TrBench(bs
)
220 Err(_
) => TestResult
::TrFailed
,
223 let stdout
= data
.lock().unwrap().to_vec();
224 let message
= CompletedTest
::new(desc
, test_result
, None
, stdout
);
225 monitor_ch
.send(message
).unwrap();
228 pub fn run_once
<F
>(f
: F
)
230 F
: FnMut(&mut Bencher
),
232 let mut bs
= Bencher { mode: BenchMode::Single, summary: None, bytes: 0 }
;