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