1use crate::stats;
2
3use std::{
4 fs::{File, OpenOptions},
5 io::{self, Write},
6 iter::repeat,
7 path::PathBuf,
8 process,
9 sync::{
10 atomic::{self, AtomicBool},
11 Arc,
12 },
13 time::Instant,
14};
15
16use ctrlc;
17use rand::{Rng, SeedableRng};
18use rand_chacha::ChaChaRng;
19
20#[derive(Copy, Clone)]
22pub struct BenchName(pub &'static str);
23
24impl BenchName {
25 fn padded(&self, column_count: usize) -> String {
26 let mut name = self.0.to_string();
27 let fill = column_count.saturating_sub(name.len());
28 let pad = repeat(" ").take(fill).collect::<String>();
29 name.push_str(&pad);
30
31 name
32 }
33}
34
35pub type BenchRng = ChaChaRng;
38
39pub type BenchFn = fn(&mut CtRunner, &mut BenchRng);
41
42#[derive(Clone)]
44enum BenchEvent {
45 BContStart,
46 BBegin(Vec<BenchName>),
47 BWait(BenchName),
48 BResult(MonitorMsg),
49 BSeed(u64, BenchName),
50}
51
52type MonitorMsg = (BenchName, stats::CtSummary);
53
54struct CtBencher {
57 samples: (Vec<u64>, Vec<u64>),
58 ctx: Option<stats::CtCtx>,
59 file_out: Option<File>,
60 rng: BenchRng,
61}
62
63impl CtBencher {
64 pub fn new() -> CtBencher {
66 CtBencher {
67 samples: (Vec::new(), Vec::new()),
68 ctx: None,
69 file_out: None,
70 rng: BenchRng::seed_from_u64(0u64),
71 }
72 }
73
74 fn go(&mut self, f: BenchFn) -> stats::CtSummary {
76 let mut runner = CtRunner::default();
78 f(&mut runner, &mut self.rng);
79 self.samples = runner.runtimes;
80
81 let old_self = ::std::mem::replace(self, CtBencher::new());
83 let (summ, new_ctx) = stats::update_ct_stats(old_self.ctx, &old_self.samples);
84
85 self.samples = old_self.samples;
87 self.file_out = old_self.file_out;
88 self.ctx = Some(new_ctx);
89 self.rng = old_self.rng;
90
91 summ
92 }
93
94 fn rand_seed() -> u64 {
96 rand::thread_rng().gen()
97 }
98
99 pub fn seed_with(&mut self, seed: u64) {
101 self.rng = BenchRng::seed_from_u64(seed);
102 }
103
104 fn clear_data(&mut self) {
106 self.samples = (Vec::new(), Vec::new());
107 self.ctx = None;
108 }
109}
110
111pub struct BenchMetadata {
113 pub name: BenchName,
114 pub seed: Option<u64>,
115 pub benchfn: BenchFn,
116}
117
118#[derive(Default)]
128pub struct BenchOpts {
129 pub continuous: bool,
130 pub filter: Option<String>,
131 pub file_out: Option<PathBuf>,
132}
133
134#[derive(Default)]
135struct ConsoleBenchState {
136 max_name_len: usize,
138}
139
140impl ConsoleBenchState {
141 fn write_plain(&mut self, s: &str) -> io::Result<()> {
142 let mut stdout = io::stdout();
143 stdout.write_all(s.as_bytes())?;
144 stdout.flush()
145 }
146
147 fn write_bench_start(&mut self, name: &BenchName) -> io::Result<()> {
148 let name = name.padded(self.max_name_len);
149 self.write_plain(&format!("bench {} ... ", name))
150 }
151
152 fn write_seed(&mut self, seed: u64, name: &BenchName) -> io::Result<()> {
153 let name = name.padded(self.max_name_len);
154 self.write_plain(&format!("bench {} seeded with 0x{:016x}\n", name, seed))
155 }
156
157 fn write_run_start(&mut self, len: usize) -> io::Result<()> {
158 let noun = if len != 1 { "benches" } else { "bench" };
159 self.write_plain(&format!("\nrunning {} {}\n", len, noun))
160 }
161
162 fn write_continuous_start(&mut self) -> io::Result<()> {
163 self.write_plain("running 1 benchmark continuously\n")
164 }
165
166 fn write_result(&mut self, summ: &stats::CtSummary) -> io::Result<()> {
167 self.write_plain(&format!(": {}\n", summ.fmt()))
168 }
169
170 fn write_run_finish(&mut self) -> io::Result<()> {
171 self.write_plain("\ndudect benches complete\n\n")
172 }
173}
174
175pub fn run_benches_console(opts: BenchOpts, benches: Vec<BenchMetadata>) -> io::Result<()> {
177 fn callback(event: &BenchEvent, st: &mut ConsoleBenchState) -> io::Result<()> {
180 match (*event).clone() {
181 BenchEvent::BContStart => st.write_continuous_start(),
182 BenchEvent::BBegin(ref filtered_benches) => st.write_run_start(filtered_benches.len()),
183 BenchEvent::BWait(ref b) => st.write_bench_start(b),
184 BenchEvent::BResult(msg) => {
185 let (_, summ) = msg;
186 st.write_result(&summ)
187 }
188 BenchEvent::BSeed(seed, ref name) => st.write_seed(seed, name),
189 }
190 }
191
192 let mut st = ConsoleBenchState::default();
193 st.max_name_len = benches.iter().map(|t| t.name.0.len()).max().unwrap_or(0);
194
195 run_benches(&opts, benches, |x| callback(&x, &mut st))?;
196 st.write_run_finish()
197}
198
199fn setup_kill_bit() -> Arc<AtomicBool> {
201 let x = Arc::new(AtomicBool::new(false));
202 let y = x.clone();
203
204 ctrlc::set_handler(move || y.store(true, atomic::Ordering::SeqCst))
205 .expect("Error setting Ctrl-C handler");
206
207 x
208}
209
210fn run_benches<F>(opts: &BenchOpts, benches: Vec<BenchMetadata>, mut callback: F) -> io::Result<()>
211where
212 F: FnMut(BenchEvent) -> io::Result<()>,
213{
214 use self::BenchEvent::*;
215
216 let filter = &opts.filter;
217 let filtered_benches = filter_benches(filter, benches);
218 let filtered_names = filtered_benches.iter().map(|b| b.name).collect();
219
220 let mut file_out = opts.file_out.as_ref().map(|filename| {
222 OpenOptions::new()
223 .write(true)
224 .truncate(true)
225 .create(true)
226 .open(filename)
227 .expect(&*format!(
228 "Could not open file '{:?}' for writing",
229 filename
230 ))
231 });
232 file_out.as_mut().map(|f| {
233 f.write(b"benchname,class,runtime")
234 .expect("Error writing CSV header to file")
235 });
236
237 let mut cb: CtBencher = {
239 let mut d = CtBencher::new();
240 d.file_out = file_out;
241 d
242 };
243
244 if opts.continuous {
245 callback(BContStart)?;
246
247 if filtered_benches.is_empty() {
248 match *filter {
249 Some(ref f) => panic!("No benchmark matching '{}' was found", f),
250 None => return Ok(()),
251 }
252 }
253
254 let kill_bit = setup_kill_bit();
256
257 let mut filtered_benches = filtered_benches;
259 let bench = filtered_benches.remove(0);
260
261 let seed = bench.seed.unwrap_or_else(CtBencher::rand_seed);
263 cb.seed_with(seed);
264 callback(BSeed(seed, bench.name))?;
265
266 loop {
267 callback(BWait(bench.name))?;
268 let msg = run_bench_with_bencher(&bench.name, bench.benchfn, &mut cb);
269 callback(BResult(msg))?;
270
271 if kill_bit.load(atomic::Ordering::SeqCst) {
273 process::exit(0);
274 }
275 }
276 } else {
277 callback(BBegin(filtered_names))?;
278
279 for bench in filtered_benches {
281 cb.clear_data();
283
284 let seed = bench.seed.unwrap_or_else(CtBencher::rand_seed);
286 cb.seed_with(seed);
287 callback(BSeed(seed, bench.name))?;
288
289 callback(BWait(bench.name))?;
290 let msg = run_bench_with_bencher(&bench.name, bench.benchfn, &mut cb);
291 callback(BResult(msg))?;
292 }
293 Ok(())
294 }
295}
296
297fn run_bench_with_bencher(name: &BenchName, benchfn: BenchFn, cb: &mut CtBencher) -> MonitorMsg {
298 let summ = cb.go(benchfn);
299
300 let samples_iter = cb.samples.0.iter().zip(cb.samples.1.iter());
302 if let Some(f) = cb.file_out.as_mut() {
303 for (x, y) in samples_iter {
304 write!(f, "\n{},0,{}", name.0, x).expect("Error writing data to file");
305 write!(f, "\n{},0,{}", name.0, y).expect("Error writing data to file");
306 }
307 };
308
309 (*name, summ)
310}
311
312fn filter_benches(filter: &Option<String>, bs: Vec<BenchMetadata>) -> Vec<BenchMetadata> {
313 let mut filtered = bs;
314
315 filtered = match *filter {
317 None => filtered,
318 Some(ref filter) => filtered
319 .into_iter()
320 .filter(|b| b.name.0.contains(&filter[..]))
321 .collect(),
322 };
323
324 filtered.sort_by(|b1, b2| b1.name.0.cmp(&b2.name.0));
326
327 filtered
328}
329
330#[cfg(not(feature = "core-hint-black-box"))]
337fn black_box<T>(dummy: T) -> T {
338 unsafe {
339 let ret = ::std::ptr::read_volatile(&dummy);
340 ::std::mem::forget(dummy);
341 ret
342 }
343}
344
345#[cfg(feature = "core-hint-black-box")]
346#[inline]
347fn black_box<T>(dummy: T) -> T {
348 ::core::hint::black_box(dummy)
349}
350
351#[derive(Copy, Clone)]
353pub enum Class {
354 Left,
355 Right,
356}
357
358#[derive(Default)]
360pub struct CtRunner {
361 runtimes: (Vec<u64>, Vec<u64>),
363}
364
365impl CtRunner {
366 pub fn run_one<T, F>(&mut self, class: Class, f: F)
368 where
369 F: Fn() -> T,
370 {
371 let start = Instant::now();
372 black_box(f());
373 let end = Instant::now();
374
375 let runtime = {
376 let dur = end.duration_since(start);
377 dur.as_secs() * 1_000_000_000 + u64::from(dur.subsec_nanos())
378 };
379
380 match class {
381 Class::Left => self.runtimes.0.push(runtime),
382 Class::Right => self.runtimes.1.push(runtime),
383 }
384 }
385}