1use std::panic::{AssertUnwindSafe, catch_unwind};
4use std::sync::{Arc, Mutex};
5use std::time::{Duration, Instant};
6use std::{cmp, io};
7
8use super::Sender;
9use super::event::CompletedTest;
10use super::options::BenchMode;
11use super::test_result::TestResult;
12use super::types::{TestDesc, TestId};
13use crate::stats;
14
15#[inline(always)]
20pub fn black_box<T>(dummy: T) -> T {
21 std::hint::black_box(dummy)
22}
23
24#[derive(Clone)]
30pub struct Bencher {
31 mode: BenchMode,
32 summary: Option<stats::Summary>,
33 pub bytes: u64,
34}
35
36impl Bencher {
37 pub fn iter<T, F>(&mut self, mut inner: F)
39 where
40 F: FnMut() -> T,
41 {
42 if self.mode == BenchMode::Single {
43 ns_iter_inner(&mut inner, 1);
44 return;
45 }
46
47 self.summary = Some(iter(&mut inner));
48 }
49
50 pub fn bench<F>(&mut self, mut f: F) -> Result<Option<stats::Summary>, String>
51 where
52 F: FnMut(&mut Bencher) -> Result<(), String>,
53 {
54 let result = f(self);
55 result.map(|_| self.summary)
56 }
57}
58
59#[derive(Debug, Clone, PartialEq)]
60pub struct BenchSamples {
61 pub ns_iter_summ: stats::Summary,
62 pub mb_s: usize,
63}
64
65pub fn fmt_bench_samples(bs: &BenchSamples) -> String {
66 use std::fmt::Write;
67 let mut output = String::new();
68
69 let median = bs.ns_iter_summ.median;
70 let deviation = bs.ns_iter_summ.max - bs.ns_iter_summ.min;
71
72 write!(
73 output,
74 "{:>14} ns/iter (+/- {})",
75 fmt_thousands_sep(median, ','),
76 fmt_thousands_sep(deviation, ',')
77 )
78 .unwrap();
79 if bs.mb_s != 0 {
80 write!(output, " = {} MB/s", bs.mb_s).unwrap();
81 }
82 output
83}
84
85fn fmt_thousands_sep(mut n: f64, sep: char) -> String {
87 use std::fmt::Write;
88 let mut output = String::new();
89 let mut trailing = false;
90 for &pow in &[9, 6, 3, 0] {
91 let base = 10_usize.pow(pow);
92 if pow == 0 || trailing || n / base as f64 >= 1.0 {
93 match (pow, trailing) {
94 (0, true) => write!(output, "{:06.2}", n / base as f64).unwrap(),
97 (0, false) => write!(output, "{:.2}", n / base as f64).unwrap(),
98 (_, true) => write!(output, "{:03}", n as usize / base).unwrap(),
99 _ => write!(output, "{}", n as usize / base).unwrap(),
100 }
101 if pow != 0 {
102 output.push(sep);
103 }
104 trailing = true;
105 }
106 n %= base as f64;
107 }
108
109 output
110}
111
112fn ns_iter_inner<T, F>(inner: &mut F, k: u64) -> u64
113where
114 F: FnMut() -> T,
115{
116 let start = Instant::now();
117 for _ in 0..k {
118 black_box(inner());
119 }
120 start.elapsed().as_nanos() as u64
121}
122
123pub fn iter<T, F>(inner: &mut F) -> stats::Summary
124where
125 F: FnMut() -> T,
126{
127 let ns_single = ns_iter_inner(inner, 1);
129
130 let ns_target_total = 1_000_000; let mut n = ns_target_total / cmp::max(1, ns_single);
134
135 n = cmp::max(1, n);
141
142 let mut total_run = Duration::new(0, 0);
143 let samples: &mut [f64] = &mut [0.0_f64; 50];
144 loop {
145 let loop_start = Instant::now();
146
147 for p in &mut *samples {
148 *p = ns_iter_inner(inner, n) as f64 / n as f64;
149 }
150
151 stats::winsorize(samples, 5.0);
152 let summ = stats::Summary::new(samples);
153
154 for p in &mut *samples {
155 let ns = ns_iter_inner(inner, 5 * n);
156 *p = ns as f64 / (5 * n) as f64;
157 }
158
159 stats::winsorize(samples, 5.0);
160 let summ5 = stats::Summary::new(samples);
161
162 let loop_run = loop_start.elapsed();
163
164 if loop_run > Duration::from_millis(100)
167 && summ.median_abs_dev_pct < 1.0
168 && summ.median - summ5.median < summ5.median_abs_dev
169 {
170 return summ5;
171 }
172
173 total_run += loop_run;
174 if total_run > Duration::from_secs(3) {
176 return summ5;
177 }
178
179 n = match n.checked_mul(10) {
184 Some(_) => n * 2,
185 None => {
186 return summ5;
187 }
188 };
189 }
190}
191
192pub fn benchmark<F>(
193 id: TestId,
194 desc: TestDesc,
195 monitor_ch: Sender<CompletedTest>,
196 nocapture: bool,
197 f: F,
198) where
199 F: FnMut(&mut Bencher) -> Result<(), String>,
200{
201 let mut bs = Bencher { mode: BenchMode::Auto, summary: None, bytes: 0 };
202
203 let data = Arc::new(Mutex::new(Vec::new()));
204
205 if !nocapture {
206 io::set_output_capture(Some(data.clone()));
207 }
208
209 let result = catch_unwind(AssertUnwindSafe(|| bs.bench(f)));
210
211 io::set_output_capture(None);
212
213 let test_result = match result {
214 Ok(Ok(Some(ns_iter_summ))) => {
216 let ns_iter = cmp::max(ns_iter_summ.median as u64, 1);
217 let mb_s = bs.bytes * 1000 / ns_iter;
218
219 let bs = BenchSamples { ns_iter_summ, mb_s: mb_s as usize };
220 TestResult::TrBench(bs)
221 }
222 Ok(Ok(None)) => {
223 let samples: &mut [f64] = &mut [0.0_f64; 1];
226 let bs = BenchSamples { ns_iter_summ: stats::Summary::new(samples), mb_s: 0 };
227 TestResult::TrBench(bs)
228 }
229 Err(_) => TestResult::TrFailed,
230 Ok(Err(_)) => TestResult::TrFailed,
231 };
232
233 let stdout = data.lock().unwrap().to_vec();
234 let message = CompletedTest::new(id, desc, test_result, None, stdout);
235 monitor_ch.send(message).unwrap();
236}
237
238pub fn run_once<F>(f: F) -> Result<(), String>
239where
240 F: FnMut(&mut Bencher) -> Result<(), String>,
241{
242 let mut bs = Bencher { mode: BenchMode::Single, summary: None, bytes: 0 };
243 bs.bench(f).map(|_| ())
244}