]> git.lizzy.rs Git - rust.git/blob - library/test/src/bench.rs
Merge remote-tracking branch 'upstream/master' into impl-16351-nightly
[rust.git] / library / test / src / bench.rs
1 //! Benchmarking module.
2 pub use std::hint::black_box;
3
4 use super::{
5     event::CompletedTest,
6     options::BenchMode,
7     test_result::TestResult,
8     types::{TestDesc, TestId},
9     Sender,
10 };
11
12 use crate::stats;
13 use std::cmp;
14 use std::io;
15 use std::panic::{catch_unwind, AssertUnwindSafe};
16 use std::sync::{Arc, Mutex};
17 use std::time::{Duration, Instant};
18
19 /// Manager of the benchmarking runs.
20 ///
21 /// This is fed into functions marked with `#[bench]` to allow for
22 /// set-up & tear-down before running a piece of code repeatedly via a
23 /// call to `iter`.
24 #[derive(Clone)]
25 pub struct Bencher {
26     mode: BenchMode,
27     summary: Option<stats::Summary>,
28     pub bytes: u64,
29 }
30
31 impl Bencher {
32     /// Callback for benchmark functions to run in their body.
33     pub fn iter<T, F>(&mut self, mut inner: F)
34     where
35         F: FnMut() -> T,
36     {
37         if self.mode == BenchMode::Single {
38             ns_iter_inner(&mut inner, 1);
39             return;
40         }
41
42         self.summary = Some(iter(&mut inner));
43     }
44
45     pub fn bench<F>(&mut self, mut f: F) -> Option<stats::Summary>
46     where
47         F: FnMut(&mut Bencher),
48     {
49         f(self);
50         self.summary
51     }
52 }
53
54 #[derive(Debug, Clone, PartialEq)]
55 pub struct BenchSamples {
56     pub ns_iter_summ: stats::Summary,
57     pub mb_s: usize,
58 }
59
60 pub fn fmt_bench_samples(bs: &BenchSamples) -> String {
61     use std::fmt::Write;
62     let mut output = String::new();
63
64     let median = bs.ns_iter_summ.median as usize;
65     let deviation = (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as usize;
66
67     write!(
68         output,
69         "{:>11} ns/iter (+/- {})",
70         fmt_thousands_sep(median, ','),
71         fmt_thousands_sep(deviation, ',')
72     )
73     .unwrap();
74     if bs.mb_s != 0 {
75         write!(output, " = {} MB/s", bs.mb_s).unwrap();
76     }
77     output
78 }
79
80 // Format a number with thousands separators
81 fn fmt_thousands_sep(mut n: usize, sep: char) -> String {
82     use std::fmt::Write;
83     let mut output = String::new();
84     let mut trailing = false;
85     for &pow in &[9, 6, 3, 0] {
86         let base = 10_usize.pow(pow);
87         if pow == 0 || trailing || n / base != 0 {
88             if !trailing {
89                 write!(output, "{}", n / base).unwrap();
90             } else {
91                 write!(output, "{:03}", n / base).unwrap();
92             }
93             if pow != 0 {
94                 output.push(sep);
95             }
96             trailing = true;
97         }
98         n %= base;
99     }
100
101     output
102 }
103
104 fn ns_iter_inner<T, F>(inner: &mut F, k: u64) -> u64
105 where
106     F: FnMut() -> T,
107 {
108     let start = Instant::now();
109     for _ in 0..k {
110         black_box(inner());
111     }
112     start.elapsed().as_nanos() as u64
113 }
114
115 pub fn iter<T, F>(inner: &mut F) -> stats::Summary
116 where
117     F: FnMut() -> T,
118 {
119     // Initial bench run to get ballpark figure.
120     let ns_single = ns_iter_inner(inner, 1);
121
122     // Try to estimate iter count for 1ms falling back to 1m
123     // iterations if first run took < 1ns.
124     let ns_target_total = 1_000_000; // 1ms
125     let mut n = ns_target_total / cmp::max(1, ns_single);
126
127     // if the first run took more than 1ms we don't want to just
128     // be left doing 0 iterations on every loop. The unfortunate
129     // side effect of not being able to do as many runs is
130     // automatically handled by the statistical analysis below
131     // (i.e., larger error bars).
132     n = cmp::max(1, n);
133
134     let mut total_run = Duration::new(0, 0);
135     let samples: &mut [f64] = &mut [0.0_f64; 50];
136     loop {
137         let loop_start = Instant::now();
138
139         for p in &mut *samples {
140             *p = ns_iter_inner(inner, n) as f64 / n as f64;
141         }
142
143         stats::winsorize(samples, 5.0);
144         let summ = stats::Summary::new(samples);
145
146         for p in &mut *samples {
147             let ns = ns_iter_inner(inner, 5 * n);
148             *p = ns as f64 / (5 * n) as f64;
149         }
150
151         stats::winsorize(samples, 5.0);
152         let summ5 = stats::Summary::new(samples);
153
154         let loop_run = loop_start.elapsed();
155
156         // If we've run for 100ms and seem to have converged to a
157         // stable median.
158         if loop_run > Duration::from_millis(100)
159             && summ.median_abs_dev_pct < 1.0
160             && summ.median - summ5.median < summ5.median_abs_dev
161         {
162             return summ5;
163         }
164
165         total_run += loop_run;
166         // Longest we ever run for is 3s.
167         if total_run > Duration::from_secs(3) {
168             return summ5;
169         }
170
171         // If we overflow here just return the results so far. We check a
172         // multiplier of 10 because we're about to multiply by 2 and the
173         // next iteration of the loop will also multiply by 5 (to calculate
174         // the summ5 result)
175         n = match n.checked_mul(10) {
176             Some(_) => n * 2,
177             None => {
178                 return summ5;
179             }
180         };
181     }
182 }
183
184 pub fn benchmark<F>(
185     id: TestId,
186     desc: TestDesc,
187     monitor_ch: Sender<CompletedTest>,
188     nocapture: bool,
189     f: F,
190 ) where
191     F: FnMut(&mut Bencher),
192 {
193     let mut bs = Bencher { mode: BenchMode::Auto, summary: None, bytes: 0 };
194
195     let data = Arc::new(Mutex::new(Vec::new()));
196
197     if !nocapture {
198         io::set_output_capture(Some(data.clone()));
199     }
200
201     let result = catch_unwind(AssertUnwindSafe(|| bs.bench(f)));
202
203     io::set_output_capture(None);
204
205     let test_result = match result {
206         //bs.bench(f) {
207         Ok(Some(ns_iter_summ)) => {
208             let ns_iter = cmp::max(ns_iter_summ.median as u64, 1);
209             let mb_s = bs.bytes * 1000 / ns_iter;
210
211             let bs = BenchSamples { ns_iter_summ, mb_s: mb_s as usize };
212             TestResult::TrBench(bs)
213         }
214         Ok(None) => {
215             // iter not called, so no data.
216             // FIXME: error in this case?
217             let samples: &mut [f64] = &mut [0.0_f64; 1];
218             let bs = BenchSamples { ns_iter_summ: stats::Summary::new(samples), mb_s: 0 };
219             TestResult::TrBench(bs)
220         }
221         Err(_) => TestResult::TrFailed,
222     };
223
224     let stdout = data.lock().unwrap().to_vec();
225     let message = CompletedTest::new(id, desc, test_result, None, stdout);
226     monitor_ch.send(message).unwrap();
227 }
228
229 pub fn run_once<F>(f: F)
230 where
231     F: FnMut(&mut Bencher),
232 {
233     let mut bs = Bencher { mode: BenchMode::Single, summary: None, bytes: 0 };
234     bs.bench(f);
235 }