Skip to content

Commit 4d50522

Browse files
committed
Split libtest into several smaller modules
1 parent a16dca3 commit 4d50522

File tree

15 files changed

+1462
-1385
lines changed

15 files changed

+1462
-1385
lines changed

src/libtest/bench.rs

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

0 commit comments

Comments
 (0)