bencher/
lib.rs

1// 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