]>
Commit | Line | Data |
---|---|---|
e74abb32 XL |
1 | //! Benchmarking module. |
2 | pub use std::hint::black_box; | |
3 | ||
4 | use super::{ | |
fc512014 | 5 | event::CompletedTest, options::BenchMode, test_result::TestResult, types::TestDesc, Sender, |
e74abb32 XL |
6 | }; |
7 | ||
8 | use crate::stats; | |
e74abb32 XL |
9 | use std::cmp; |
10 | use std::io; | |
11 | use std::panic::{catch_unwind, AssertUnwindSafe}; | |
12 | use std::sync::{Arc, Mutex}; | |
dfeec247 | 13 | use 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)] | |
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 | ||
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 | |
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 { | |
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 |
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 | } | |
1b1a35ee | 108 | start.elapsed().as_nanos() as u64 |
e74abb32 XL |
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 | ||
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 | ||
180 | pub fn benchmark<F>(desc: TestDesc, monitor_ch: Sender<CompletedTest>, nocapture: bool, f: F) | |
181 | where | |
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 | ||
220 | pub fn run_once<F>(f: F) | |
221 | where | |
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 | } |