blob: 1817b959f32f2fd18ad5c6c276929902fefee9da [file] [log] [blame]
Jeff Vander Stoepd036b622020-12-17 19:59:02 +01001// Copyright 2012-2016 The Rust Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution and at
3// http://rust-lang.org/COPYRIGHT.
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11//! Simplified stable-compatible benchmark runner.
12//!
13//! Almost all user code will only be interested in `Bencher` and the
14//! macros that are used to describe benchmarker functions and
15//! the benchmark runner.
16//!
17//! NOTE: There's no proper `black_box` yet in this stable port of the
18//! benchmark runner, only a workaround implementation. It may not work
19//! exactly like the upstream `test::black_box`.
20//!
21//! One way to use this crate is to use it as dev-dependency and setup
22//! cargo to compile a file in `benches/` that runs without the testing harness.
23//!
24//! In Cargo.toml:
25//!
26//! ```ignore
27//! [[bench]]
28//! name = "example"
29//! harness = false
30//! ```
31//!
32//! In benches/example.rs:
33//!
34//! ```
35//! #[macro_use]
36//! extern crate bencher;
37//!
38//! use bencher::Bencher;
39//!
40//! fn a(bench: &mut Bencher) {
41//! bench.iter(|| {
42//! (0..1000).fold(0, |x, y| x + y)
43//! })
44//! }
45//!
46//! fn b(bench: &mut Bencher) {
47//! const N: usize = 1024;
48//! bench.iter(|| {
49//! vec![0u8; N]
50//! });
51//!
52//! bench.bytes = N as u64;
53//! }
54//!
55//! benchmark_group!(benches, a, b);
56//! benchmark_main!(benches);
57//!
58//! # #[cfg(never)]
59//! # fn main() { }
60//! ```
61//!
62//! Use `cargo bench` as usual. A command line argument can be used to filter
63//! which benchmarks to run.
64
65pub use self::TestFn::*;
66use self::TestResult::*;
67use self::TestEvent::*;
68use self::NamePadding::*;
69use self::OutputLocation::*;
70
71use std::borrow::Cow;
72use std::cmp;
73use std::fmt;
74use std::fs::File;
75use std::io::prelude::*;
76use std::io;
77use std::iter::repeat;
78use std::mem::forget;
79use std::path::PathBuf;
80use std::ptr;
81use std::time::{Instant, Duration};
82
83pub mod stats;
84mod macros;
85
86// The name of a test. By convention this follows the rules for rust
87// paths; i.e. it should be a series of identifiers separated by double
88// colons. This way if some test runner wants to arrange the tests
89// hierarchically it may.
90
91pub type TestName = Cow<'static, str>;
92
93#[derive(Clone, Copy, PartialEq, Eq)]
94enum NamePadding {
95 PadOnRight,
96}
97
98impl TestDesc {
99 fn padded_name(&self, column_count: usize, align: NamePadding) -> String {
100 let mut name = self.name.to_string();
101 let fill = column_count.saturating_sub(name.len());
102 let pad = repeat(" ").take(fill).collect::<String>();
103 match align {
104 PadOnRight => {
105 name.push_str(&pad);
106 name
107 }
108 }
109 }
110}
111
112/// Represents a benchmark function.
113pub trait TDynBenchFn: Send {
114 fn run(&self, harness: &mut Bencher);
115}
116
117// A function that runs a test. If the function returns successfully,
118// the test succeeds; if the function panics then the test fails. We
119// may need to come up with a more clever definition of test in order
120// to support isolation of tests into threads.
121pub enum TestFn {
122 StaticBenchFn(fn(&mut Bencher)),
123 DynBenchFn(Box<TDynBenchFn + 'static>),
124}
125
126impl TestFn {
127 fn padding(&self) -> NamePadding {
128 match *self {
129 StaticBenchFn(..) |
130 DynBenchFn(..) => PadOnRight,
131 }
132 }
133}
134
135impl fmt::Debug for TestFn {
136 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
137 f.write_str(match *self {
138 StaticBenchFn(..) => "StaticBenchFn(..)",
139 DynBenchFn(..) => "DynBenchFn(..)",
140 })
141 }
142}
143
144/// Manager of the benchmarking runs.
145///
146/// This is fed into functions marked with `#[bench]` to allow for
147/// set-up & tear-down before running a piece of code repeatedly via a
148/// call to `iter`.
149#[derive(Copy, Clone)]
150pub struct Bencher {
151 iterations: u64,
152 dur: Duration,
153 pub bytes: u64,
154}
155
156// The definition of a single test. A test runner will run a list of
157// these.
158#[derive(Clone, Debug, PartialEq, Eq, Hash)]
159pub struct TestDesc {
160 pub name: TestName,
161 pub ignore: bool,
162}
163
164#[derive(Clone)]
165pub struct TestPaths {
166 pub file: PathBuf, // e.g., compile-test/foo/bar/baz.rs
167 pub base: PathBuf, // e.g., compile-test, auxiliary
168 pub relative_dir: PathBuf, // e.g., foo/bar
169}
170
171#[derive(Debug)]
172pub struct TestDescAndFn {
173 pub desc: TestDesc,
174 pub testfn: TestFn,
175}
176
177#[derive(Default)]
178pub struct TestOpts {
179 pub filter: Option<String>,
180 pub run_ignored: bool,
181 pub logfile: Option<PathBuf>,
182 pub quiet: bool,
183 pub test_threads: Option<usize>,
184}
185
186#[derive(Clone, PartialEq)]
187pub struct BenchSamples {
188 ns_iter_summ: stats::Summary,
189 mb_s: usize,
190}
191
192#[derive(Clone, PartialEq)]
193enum TestResult {
194 TrIgnored,
195 TrBench(BenchSamples),
196}
197
198unsafe impl Send for TestResult {}
199
200enum OutputLocation<T> {
201 Raw(T),
202}
203
204struct ConsoleTestState<T> {
205 log_out: Option<File>,
206 out: OutputLocation<T>,
207 quiet: bool,
208 total: usize,
209 passed: usize,
210 failed: usize,
211 ignored: usize,
212 measured: usize,
213 failures: Vec<(TestDesc, Vec<u8>)>,
214 max_name_len: usize, // number of columns to fill when aligning names
215}
216
217impl ConsoleTestState<()> {
218 pub fn new(opts: &TestOpts) -> io::Result<ConsoleTestState<io::Stdout>> {
219 let log_out = match opts.logfile {
220 Some(ref path) => Some(try!(File::create(path))),
221 None => None,
222 };
223 let out = Raw(io::stdout());
224
225 Ok(ConsoleTestState {
226 out: out,
227 log_out: log_out,
228 quiet: opts.quiet,
229 total: 0,
230 passed: 0,
231 failed: 0,
232 ignored: 0,
233 measured: 0,
234 failures: Vec::new(),
235 max_name_len: 0,
236 })
237 }
238}
239
240impl<T: Write> ConsoleTestState<T> {
241 pub fn write_ignored(&mut self) -> io::Result<()> {
242 self.write_short_result("ignored", "i")
243 }
244
245 pub fn write_bench(&mut self) -> io::Result<()> {
246 self.write_pretty("bench")
247 }
248
249 pub fn write_short_result(&mut self, verbose: &str, quiet: &str)
250 -> io::Result<()> {
251 if self.quiet {
252 self.write_pretty(quiet)
253 } else {
254 try!(self.write_pretty(verbose));
255 self.write_plain("\n")
256 }
257 }
258
259 pub fn write_pretty(&mut self, word: &str) -> io::Result<()> {
260 match self.out {
261 Raw(ref mut stdout) => {
262 try!(stdout.write_all(word.as_bytes()));
263 stdout.flush()
264 }
265 }
266 }
267
268 pub fn write_plain(&mut self, s: &str) -> io::Result<()> {
269 match self.out {
270 Raw(ref mut stdout) => {
271 try!(stdout.write_all(s.as_bytes()));
272 stdout.flush()
273 }
274 }
275 }
276
277 pub fn write_run_start(&mut self, len: usize) -> io::Result<()> {
278 self.total = len;
279 let noun = if len != 1 {
280 "tests"
281 } else {
282 "test"
283 };
284 self.write_plain(&format!("\nrunning {} {}\n", len, noun))
285 }
286
287 pub fn write_test_start(&mut self, test: &TestDesc, align: NamePadding) -> io::Result<()> {
288 if self.quiet && align != PadOnRight {
289 Ok(())
290 } else {
291 let name = test.padded_name(self.max_name_len, align);
292 self.write_plain(&format!("test {} ... ", name))
293 }
294 }
295
296 pub fn write_result(&mut self, result: &TestResult) -> io::Result<()> {
297 match *result {
298 TrIgnored => self.write_ignored(),
299 TrBench(ref bs) => {
300 try!(self.write_bench());
301 self.write_plain(&format!(": {}\n", fmt_bench_samples(bs)))
302 }
303 }
304 }
305
306 pub fn write_log(&mut self, test: &TestDesc, result: &TestResult) -> io::Result<()> {
307 match self.log_out {
308 None => Ok(()),
309 Some(ref mut o) => {
310 let s = format!("{} {}\n",
311 match *result {
312 TrIgnored => "ignored".to_owned(),
313 TrBench(ref bs) => fmt_bench_samples(bs),
314 },
315 test.name);
316 o.write_all(s.as_bytes())
317 }
318 }
319 }
320
321 pub fn write_failures(&mut self) -> io::Result<()> {
322 try!(self.write_plain("\nfailures:\n"));
323 let mut failures = Vec::new();
324 let mut fail_out = String::new();
325 for &(ref f, ref stdout) in &self.failures {
326 failures.push(f.name.to_string());
327 if !stdout.is_empty() {
328 fail_out.push_str(&format!("---- {} stdout ----\n\t", f.name));
329 let output = String::from_utf8_lossy(stdout);
330 fail_out.push_str(&output);
331 fail_out.push_str("\n");
332 }
333 }
334 if !fail_out.is_empty() {
335 try!(self.write_plain("\n"));
336 try!(self.write_plain(&fail_out));
337 }
338
339 try!(self.write_plain("\nfailures:\n"));
340 failures.sort();
341 for name in &failures {
342 try!(self.write_plain(&format!(" {}\n", name)));
343 }
344 Ok(())
345 }
346
347 pub fn write_run_finish(&mut self) -> io::Result<bool> {
348 assert_eq!(self.passed + self.failed + self.ignored + self.measured, self.total);
349
350 let success = self.failed == 0;
351 if !success {
352 try!(self.write_failures());
353 }
354
355 try!(self.write_plain("\ntest result: "));
356 if success {
357 // There's no parallelism at this point so it's safe to use color
358 try!(self.write_pretty("ok"));
359 } else {
360 try!(self.write_pretty("FAILED"));
361 }
362 let s = format!(". {} passed; {} failed; {} ignored; {} measured\n\n",
363 self.passed,
364 self.failed,
365 self.ignored,
366 self.measured);
367 try!(self.write_plain(&s));
368 Ok(success)
369 }
370}
371
372// Format a number with thousands separators
373fn fmt_thousands_sep(mut n: usize, sep: char) -> String {
374 use std::fmt::Write;
375 let mut output = String::new();
376 let mut trailing = false;
377 for &pow in &[9, 6, 3, 0] {
378 let base = 10_usize.pow(pow);
379 if pow == 0 || trailing || n / base != 0 {
380 if !trailing {
381 output.write_fmt(format_args!("{}", n / base)).unwrap();
382 } else {
383 output.write_fmt(format_args!("{:03}", n / base)).unwrap();
384 }
385 if pow != 0 {
386 output.push(sep);
387 }
388 trailing = true;
389 }
390 n %= base;
391 }
392
393 output
394}
395
396pub fn fmt_bench_samples(bs: &BenchSamples) -> String {
397 use std::fmt::Write;
398 let mut output = String::new();
399
400 let median = bs.ns_iter_summ.median as usize;
401 let deviation = (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as usize;
402
403 output.write_fmt(format_args!("{:>11} ns/iter (+/- {})",
404 fmt_thousands_sep(median, ','),
405 fmt_thousands_sep(deviation, ',')))
406 .unwrap();
407 if bs.mb_s != 0 {
408 output.write_fmt(format_args!(" = {} MB/s", bs.mb_s)).unwrap();
409 }
410 output
411}
412
413// A simple console test runner
414pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Result<bool> {
415
416 fn callback<T: Write>(event: &TestEvent, st: &mut ConsoleTestState<T>) -> io::Result<()> {
417 match (*event).clone() {
418 TeFiltered(ref filtered_tests) => st.write_run_start(filtered_tests.len()),
419 TeWait(ref test, padding) => st.write_test_start(test, padding),
420 TeResult(test, result, _) => {
421 try!(st.write_log(&test, &result));
422 try!(st.write_result(&result));
423 match result {
424 TrIgnored => st.ignored += 1,
425 TrBench(_) => {
426 st.measured += 1
427 }
428 }
429 Ok(())
430 }
431 }
432 }
433
434 let mut st = try!(ConsoleTestState::new(opts));
435 fn len_if_padded(t: &TestDescAndFn) -> usize {
436 match t.testfn.padding() {
437 PadOnRight => t.desc.name.len(),
438 }
439 }
440 if let Some(t) = tests.iter().max_by_key(|t| len_if_padded(*t)) {
441 let n = &t.desc.name;
442 st.max_name_len = n.len();
443 }
444 try!(run_tests(opts, tests, |x| callback(&x, &mut st)));
445 st.write_run_finish()
446}
447
448#[test]
449fn should_sort_failures_before_printing_them() {
450 let test_a = TestDesc {
451 name: Cow::from("a"),
452 ignore: false,
453 };
454
455 let test_b = TestDesc {
456 name: Cow::from("b"),
457 ignore: false,
458 };
459
460 let mut st = ConsoleTestState {
461 log_out: None,
462 out: Raw(Vec::new()),
463 quiet: false,
464 total: 0,
465 passed: 0,
466 failed: 0,
467 ignored: 0,
468 measured: 0,
469 max_name_len: 10,
470 failures: vec![(test_b, Vec::new()), (test_a, Vec::new())],
471 };
472
473 st.write_failures().unwrap();
474 let s = match st.out {
475 Raw(ref m) => String::from_utf8_lossy(&m[..]),
476 };
477
478 let apos = s.find("a").unwrap();
479 let bpos = s.find("b").unwrap();
480 assert!(apos < bpos);
481}
482
483#[derive(Clone)]
484enum TestEvent {
485 TeFiltered(Vec<TestDesc>),
486 TeWait(TestDesc, NamePadding),
487 TeResult(TestDesc, TestResult, Vec<u8>),
488}
489
490type MonitorMsg = (TestDesc, TestResult, Vec<u8>);
491
492
493fn run_tests<F>(opts: &TestOpts, tests: Vec<TestDescAndFn>, mut callback: F) -> io::Result<()>
494 where F: FnMut(TestEvent) -> io::Result<()>
495{
496
497 let filtered_tests = filter_tests(opts, tests);
498
499 let filtered_descs = filtered_tests.iter()
500 .map(|t| t.desc.clone())
501 .collect();
502
503 try!(callback(TeFiltered(filtered_descs)));
504
505 let filtered_benchs_and_metrics = filtered_tests;
506
507 // All benchmarks run at the end, in serial.
508 // (this includes metric fns)
509 for b in filtered_benchs_and_metrics {
510 try!(callback(TeWait(b.desc.clone(), b.testfn.padding())));
511 let (test, result, stdout) = run_test(opts, false, b);
512 try!(callback(TeResult(test, result, stdout)));
513 }
514 Ok(())
515}
516
517fn filter_tests(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> Vec<TestDescAndFn> {
518 let mut filtered = tests;
519
520 // Remove tests that don't match the test filter
521 filtered = match opts.filter {
522 None => filtered,
523 Some(ref filter) => {
524 filtered.into_iter()
525 .filter(|test| test.desc.name.contains(&filter[..]))
526 .collect()
527 }
528 };
529
530 // Maybe pull out the ignored test and unignore them
531 filtered = if !opts.run_ignored {
532 filtered
533 } else {
534 fn filter(test: TestDescAndFn) -> Option<TestDescAndFn> {
535 if test.desc.ignore {
536 let TestDescAndFn {desc, testfn} = test;
537 Some(TestDescAndFn {
538 desc: TestDesc { ignore: false, ..desc },
539 testfn: testfn,
540 })
541 } else {
542 None
543 }
544 }
545 filtered.into_iter().filter_map(filter).collect()
546 };
547
548 // Sort the tests alphabetically
549 filtered.sort_by(|t1, t2| t1.desc.name.cmp(&t2.desc.name));
550
551 filtered
552}
553
554fn run_test(_opts: &TestOpts,
555 force_ignore: bool,
556 test: TestDescAndFn) -> MonitorMsg
557{
558
559 let TestDescAndFn {desc, testfn} = test;
560
561 if force_ignore || desc.ignore {
562 return (desc, TrIgnored, Vec::new());
563 }
564
565 match testfn {
566 DynBenchFn(bencher) => {
567 let bs = ::bench::benchmark(|harness| bencher.run(harness));
568 (desc, TrBench(bs), Vec::new())
569 }
570 StaticBenchFn(benchfn) => {
571 let bs = ::bench::benchmark(|harness| benchfn(harness));
572 (desc, TrBench(bs), Vec::new())
573 }
574 }
575}
576
577
578// Benchmarking
579
580// FIXME: We don't have black_box in stable rust
581
582/// NOTE: We don't have a proper black box in stable Rust. This is
583/// a workaround implementation, that may have a too big performance overhead,
584/// depending on operation, or it may fail to properly avoid having code
585/// optimized out. It is good enough that it is used by default.
586///
587/// A function that is opaque to the optimizer, to allow benchmarks to
588/// pretend to use outputs to assist in avoiding dead-code
589/// elimination.
590pub fn black_box<T>(dummy: T) -> T {
591 unsafe {
592 let ret = ptr::read_volatile(&dummy);
593 forget(dummy);
594 ret
595 }
596}
597
598
599impl Bencher {
600 /// Callback for benchmark functions to run in their body.
601 pub fn iter<T, F>(&mut self, mut inner: F)
602 where F: FnMut() -> T
603 {
604 let start = Instant::now();
605 let k = self.iterations;
606 for _ in 0..k {
607 black_box(inner());
608 }
609 self.dur = start.elapsed();
610 }
611
612 pub fn ns_elapsed(&mut self) -> u64 {
613 self.dur.as_secs() * 1_000_000_000 + (self.dur.subsec_nanos() as u64)
614 }
615
616 pub fn ns_per_iter(&mut self) -> u64 {
617 if self.iterations == 0 {
618 0
619 } else {
620 self.ns_elapsed() / cmp::max(self.iterations, 1)
621 }
622 }
623
624 pub fn bench_n<F>(&mut self, n: u64, f: F)
625 where F: FnOnce(&mut Bencher)
626 {
627 self.iterations = n;
628 f(self);
629 }
630
631 // This is a more statistics-driven benchmark algorithm
632 pub fn auto_bench<F>(&mut self, mut f: F) -> stats::Summary
633 where F: FnMut(&mut Bencher)
634 {
635 // Initial bench run to get ballpark figure.
636 let mut n = 1;
637 self.bench_n(n, |x| f(x));
638
639 // Try to estimate iter count for 1ms falling back to 1m
640 // iterations if first run took < 1ns.
641 if self.ns_per_iter() == 0 {
642 n = 1_000_000;
643 } else {
644 n = 1_000_000 / cmp::max(self.ns_per_iter(), 1);
645 }
646 // if the first run took more than 1ms we don't want to just
647 // be left doing 0 iterations on every loop. The unfortunate
648 // side effect of not being able to do as many runs is
649 // automatically handled by the statistical analysis below
650 // (i.e. larger error bars).
651 if n == 0 {
652 n = 1;
653 }
654
655 let mut total_run = Duration::new(0, 0);
656 let samples: &mut [f64] = &mut [0.0_f64; 50];
657 loop {
658 let loop_start = Instant::now();
659
660 for p in &mut *samples {
661 self.bench_n(n, |x| f(x));
662 *p = self.ns_per_iter() as f64;
663 }
664
665 stats::winsorize(samples, 5.0);
666 let summ = stats::Summary::new(samples);
667
668 for p in &mut *samples {
669 self.bench_n(5 * n, |x| f(x));
670 *p = self.ns_per_iter() as f64;
671 }
672
673 stats::winsorize(samples, 5.0);
674 let summ5 = stats::Summary::new(samples);
675 let loop_run = loop_start.elapsed();
676
677 // If we've run for 100ms and seem to have converged to a
678 // stable median.
679 if loop_run > Duration::from_millis(100) && summ.median_abs_dev_pct < 1.0 &&
680 summ.median - summ5.median < summ5.median_abs_dev {
681 return summ5;
682 }
683
684 total_run += loop_run;
685 // Longest we ever run for is 3s.
686 if total_run > Duration::from_secs(3) {
687 return summ5;
688 }
689
690 // If we overflow here just return the results so far. We check a
691 // multiplier of 10 because we're about to multiply by 2 and the
692 // next iteration of the loop will also multiply by 5 (to calculate
693 // the summ5 result)
694 n = match n.checked_mul(10) {
695 Some(_) => n * 2,
696 None => return summ5,
697 };
698 }
699 }
700}
701
702pub mod bench {
703 use std::cmp;
704 use std::time::Duration;
705 use super::{Bencher, BenchSamples};
706
707 pub fn benchmark<F>(f: F) -> BenchSamples
708 where F: FnMut(&mut Bencher)
709 {
710 let mut bs = Bencher {
711 iterations: 0,
712 dur: Duration::new(0, 0),
713 bytes: 0,
714 };
715
716 let ns_iter_summ = bs.auto_bench(f);
717
718 let ns_iter = cmp::max(ns_iter_summ.median as u64, 1);
719 let mb_s = bs.bytes * 1000 / ns_iter;
720
721 BenchSamples {
722 ns_iter_summ: ns_iter_summ,
723 mb_s: mb_s as usize,
724 }
725 }
726
727 pub fn run_once<F>(f: F)
728 where F: FnOnce(&mut Bencher)
729 {
730 let mut bs = Bencher {
731 iterations: 0,
732 dur: Duration::new(0, 0),
733 bytes: 0,
734 };
735 bs.bench_n(1, f);
736 }
737}
738