]> git.proxmox.com Git - rustc.git/blame - library/test/src/bench.rs
New upstream version 1.50.0+dfsg1
[rustc.git] / library / test / src / bench.rs
CommitLineData
e74abb32
XL
1//! Benchmarking module.
2pub use std::hint::black_box;
3
4use super::{
fc512014 5 event::CompletedTest, options::BenchMode, test_result::TestResult, types::TestDesc, Sender,
e74abb32
XL
6};
7
8use crate::stats;
e74abb32
XL
9use std::cmp;
10use std::io;
11use std::panic::{catch_unwind, AssertUnwindSafe};
12use std::sync::{Arc, Mutex};
dfeec247 13use std::time::{Duration, Instant};
e74abb32
XL
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)]
21pub struct Bencher {
22 mode: BenchMode,
23 summary: Option<stats::Summary>,
24 pub bytes: u64,
25}
26
27impl 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)]
51pub struct BenchSamples {
52 pub ns_iter_summ: stats::Summary,
53 pub mb_s: usize,
54}
55
56pub 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
1b1a35ee
XL
63 write!(
64 output,
65 "{:>11} ns/iter (+/- {})",
66 fmt_thousands_sep(median, ','),
67 fmt_thousands_sep(deviation, ',')
68 )
69 .unwrap();
e74abb32 70 if bs.mb_s != 0 {
1b1a35ee 71 write!(output, " = {} MB/s", bs.mb_s).unwrap();
e74abb32
XL
72 }
73 output
74}
75
76// Format a number with thousands separators
77fn 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 {
1b1a35ee 85 write!(output, "{}", n / base).unwrap();
e74abb32 86 } else {
1b1a35ee 87 write!(output, "{:03}", n / base).unwrap();
e74abb32
XL
88 }
89 if pow != 0 {
90 output.push(sep);
91 }
92 trailing = true;
93 }
94 n %= base;
95 }
96
97 output
98}
99
e74abb32
XL
100fn ns_iter_inner<T, F>(inner: &mut F, k: u64) -> u64
101where
102 F: FnMut() -> T,
103{
104 let start = Instant::now();
105 for _ in 0..k {
106 black_box(inner());
107 }
1b1a35ee 108 start.elapsed().as_nanos() as u64
e74abb32
XL
109}
110
111pub fn iter<T, F>(inner: &mut F) -> stats::Summary
112where
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
1b1a35ee 161 total_run += loop_run;
e74abb32
XL
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
180pub fn benchmark<F>(desc: TestDesc, monitor_ch: Sender<CompletedTest>, nocapture: bool, f: F)
181where
182 F: FnMut(&mut Bencher),
183{
dfeec247 184 let mut bs = Bencher { mode: BenchMode::Auto, summary: None, bytes: 0 };
e74abb32
XL
185
186 let data = Arc::new(Mutex::new(Vec::new()));
fc512014
XL
187
188 if !nocapture {
189 io::set_output_capture(Some(data.clone()));
190 }
e74abb32
XL
191
192 let result = catch_unwind(AssertUnwindSafe(|| bs.bench(f)));
193
fc512014 194 io::set_output_capture(None);
e74abb32
XL
195
196 let test_result = match result {
197 //bs.bench(f) {
198 Ok(Some(ns_iter_summ)) => {
199 let ns_iter = cmp::max(ns_iter_summ.median as u64, 1);
200 let mb_s = bs.bytes * 1000 / ns_iter;
201
dfeec247 202 let bs = BenchSamples { ns_iter_summ, mb_s: mb_s as usize };
e74abb32
XL
203 TestResult::TrBench(bs)
204 }
205 Ok(None) => {
206 // iter not called, so no data.
207 // FIXME: error in this case?
208 let samples: &mut [f64] = &mut [0.0_f64; 1];
dfeec247 209 let bs = BenchSamples { ns_iter_summ: stats::Summary::new(samples), mb_s: 0 };
e74abb32
XL
210 TestResult::TrBench(bs)
211 }
212 Err(_) => TestResult::TrFailed,
213 };
214
215 let stdout = data.lock().unwrap().to_vec();
216 let message = CompletedTest::new(desc, test_result, None, stdout);
217 monitor_ch.send(message).unwrap();
218}
219
220pub fn run_once<F>(f: F)
221where
222 F: FnMut(&mut Bencher),
223{
dfeec247 224 let mut bs = Bencher { mode: BenchMode::Single, summary: None, bytes: 0 };
e74abb32
XL
225 bs.bench(f);
226}