You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
739 lines
20 KiB
739 lines
20 KiB
// Copyright 2012-2016 The Rust Project Developers. See the COPYRIGHT
|
|
// file at the top-level directory of this distribution and at
|
|
// http://rust-lang.org/COPYRIGHT.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
// option. This file may not be copied, modified, or distributed
|
|
// except according to those terms.
|
|
|
|
//! Simplified stable-compatible benchmark runner.
|
|
//!
|
|
//! Almost all user code will only be interested in `Bencher` and the
|
|
//! macros that are used to describe benchmarker functions and
|
|
//! the benchmark runner.
|
|
//!
|
|
//! NOTE: There's no proper `black_box` yet in this stable port of the
|
|
//! benchmark runner, only a workaround implementation. It may not work
|
|
//! exactly like the upstream `test::black_box`.
|
|
//!
|
|
//! One way to use this crate is to use it as dev-dependency and setup
|
|
//! cargo to compile a file in `benches/` that runs without the testing harness.
|
|
//!
|
|
//! In Cargo.toml:
|
|
//!
|
|
//! ```ignore
|
|
//! [[bench]]
|
|
//! name = "example"
|
|
//! harness = false
|
|
//! ```
|
|
//!
|
|
//! In benches/example.rs:
|
|
//!
|
|
//! ```
|
|
//! #[macro_use]
|
|
//! extern crate bencher;
|
|
//!
|
|
//! use bencher::Bencher;
|
|
//!
|
|
//! fn a(bench: &mut Bencher) {
|
|
//! bench.iter(|| {
|
|
//! (0..1000).fold(0, |x, y| x + y)
|
|
//! })
|
|
//! }
|
|
//!
|
|
//! fn b(bench: &mut Bencher) {
|
|
//! const N: usize = 1024;
|
|
//! bench.iter(|| {
|
|
//! vec![0u8; N]
|
|
//! });
|
|
//!
|
|
//! bench.bytes = N as u64;
|
|
//! }
|
|
//!
|
|
//! benchmark_group!(benches, a, b);
|
|
//! benchmark_main!(benches);
|
|
//!
|
|
//! # #[cfg(never)]
|
|
//! # fn main() { }
|
|
//! ```
|
|
//!
|
|
//! Use `cargo bench` as usual. A command line argument can be used to filter
|
|
//! which benchmarks to run.
|
|
|
|
pub use self::TestFn::*;
|
|
use self::TestResult::*;
|
|
use self::TestEvent::*;
|
|
use self::NamePadding::*;
|
|
use self::OutputLocation::*;
|
|
|
|
use std::borrow::Cow;
|
|
use std::cmp;
|
|
use std::fmt;
|
|
use std::fs::File;
|
|
use std::io::prelude::*;
|
|
use std::io;
|
|
use std::iter::repeat;
|
|
use std::mem::forget;
|
|
use std::path::PathBuf;
|
|
use std::ptr;
|
|
use std::time::{Instant, Duration};
|
|
|
|
pub mod stats;
|
|
mod macros;
|
|
|
|
// The name of a test. By convention this follows the rules for rust
|
|
// paths; i.e. it should be a series of identifiers separated by double
|
|
// colons. This way if some test runner wants to arrange the tests
|
|
// hierarchically it may.
|
|
|
|
pub type TestName = Cow<'static, str>;
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
enum NamePadding {
|
|
PadOnRight,
|
|
}
|
|
|
|
impl TestDesc {
|
|
fn padded_name(&self, column_count: usize, align: NamePadding) -> String {
|
|
let mut name = self.name.to_string();
|
|
let fill = column_count.saturating_sub(name.len());
|
|
let pad = repeat(" ").take(fill).collect::<String>();
|
|
match align {
|
|
PadOnRight => {
|
|
name.push_str(&pad);
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Represents a benchmark function.
|
|
pub trait TDynBenchFn: Send {
|
|
fn run(&self, harness: &mut Bencher);
|
|
}
|
|
|
|
// A function that runs a test. If the function returns successfully,
|
|
// the test succeeds; if the function panics then the test fails. We
|
|
// may need to come up with a more clever definition of test in order
|
|
// to support isolation of tests into threads.
|
|
pub enum TestFn {
|
|
StaticBenchFn(fn(&mut Bencher)),
|
|
DynBenchFn(Box<TDynBenchFn + 'static>),
|
|
}
|
|
|
|
impl TestFn {
|
|
fn padding(&self) -> NamePadding {
|
|
match *self {
|
|
StaticBenchFn(..) |
|
|
DynBenchFn(..) => PadOnRight,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for TestFn {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.write_str(match *self {
|
|
StaticBenchFn(..) => "StaticBenchFn(..)",
|
|
DynBenchFn(..) => "DynBenchFn(..)",
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Manager of the benchmarking runs.
|
|
///
|
|
/// This is fed into functions marked with `#[bench]` to allow for
|
|
/// set-up & tear-down before running a piece of code repeatedly via a
|
|
/// call to `iter`.
|
|
#[derive(Copy, Clone)]
|
|
pub struct Bencher {
|
|
iterations: u64,
|
|
dur: Duration,
|
|
pub bytes: u64,
|
|
}
|
|
|
|
// The definition of a single test. A test runner will run a list of
|
|
// these.
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
pub struct TestDesc {
|
|
pub name: TestName,
|
|
pub ignore: bool,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct TestPaths {
|
|
pub file: PathBuf, // e.g., compile-test/foo/bar/baz.rs
|
|
pub base: PathBuf, // e.g., compile-test, auxiliary
|
|
pub relative_dir: PathBuf, // e.g., foo/bar
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct TestDescAndFn {
|
|
pub desc: TestDesc,
|
|
pub testfn: TestFn,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct TestOpts {
|
|
pub filter: Option<String>,
|
|
pub run_ignored: bool,
|
|
pub logfile: Option<PathBuf>,
|
|
pub quiet: bool,
|
|
pub test_threads: Option<usize>,
|
|
}
|
|
|
|
#[derive(Clone, PartialEq)]
|
|
pub struct BenchSamples {
|
|
ns_iter_summ: stats::Summary,
|
|
mb_s: usize,
|
|
}
|
|
|
|
#[derive(Clone, PartialEq)]
|
|
enum TestResult {
|
|
TrIgnored,
|
|
TrBench(BenchSamples),
|
|
}
|
|
|
|
unsafe impl Send for TestResult {}
|
|
|
|
enum OutputLocation<T> {
|
|
Raw(T),
|
|
}
|
|
|
|
struct ConsoleTestState<T> {
|
|
log_out: Option<File>,
|
|
out: OutputLocation<T>,
|
|
quiet: bool,
|
|
total: usize,
|
|
passed: usize,
|
|
failed: usize,
|
|
ignored: usize,
|
|
measured: usize,
|
|
failures: Vec<(TestDesc, Vec<u8>)>,
|
|
max_name_len: usize, // number of columns to fill when aligning names
|
|
}
|
|
|
|
impl ConsoleTestState<()> {
|
|
pub fn new(opts: &TestOpts) -> io::Result<ConsoleTestState<io::Stdout>> {
|
|
let log_out = match opts.logfile {
|
|
Some(ref path) => Some(try!(File::create(path))),
|
|
None => None,
|
|
};
|
|
let out = Raw(io::stdout());
|
|
|
|
Ok(ConsoleTestState {
|
|
out: out,
|
|
log_out: log_out,
|
|
quiet: opts.quiet,
|
|
total: 0,
|
|
passed: 0,
|
|
failed: 0,
|
|
ignored: 0,
|
|
measured: 0,
|
|
failures: Vec::new(),
|
|
max_name_len: 0,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<T: Write> ConsoleTestState<T> {
|
|
pub fn write_ignored(&mut self) -> io::Result<()> {
|
|
self.write_short_result("ignored", "i")
|
|
}
|
|
|
|
pub fn write_bench(&mut self) -> io::Result<()> {
|
|
self.write_pretty("bench")
|
|
}
|
|
|
|
pub fn write_short_result(&mut self, verbose: &str, quiet: &str)
|
|
-> io::Result<()> {
|
|
if self.quiet {
|
|
self.write_pretty(quiet)
|
|
} else {
|
|
try!(self.write_pretty(verbose));
|
|
self.write_plain("\n")
|
|
}
|
|
}
|
|
|
|
pub fn write_pretty(&mut self, word: &str) -> io::Result<()> {
|
|
match self.out {
|
|
Raw(ref mut stdout) => {
|
|
try!(stdout.write_all(word.as_bytes()));
|
|
stdout.flush()
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn write_plain(&mut self, s: &str) -> io::Result<()> {
|
|
match self.out {
|
|
Raw(ref mut stdout) => {
|
|
try!(stdout.write_all(s.as_bytes()));
|
|
stdout.flush()
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn write_run_start(&mut self, len: usize) -> io::Result<()> {
|
|
self.total = len;
|
|
let noun = if len != 1 {
|
|
"tests"
|
|
} else {
|
|
"test"
|
|
};
|
|
self.write_plain(&format!("\nrunning {} {}\n", len, noun))
|
|
}
|
|
|
|
pub fn write_test_start(&mut self, test: &TestDesc, align: NamePadding) -> io::Result<()> {
|
|
if self.quiet && align != PadOnRight {
|
|
Ok(())
|
|
} else {
|
|
let name = test.padded_name(self.max_name_len, align);
|
|
self.write_plain(&format!("test {} ... ", name))
|
|
}
|
|
}
|
|
|
|
pub fn write_result(&mut self, result: &TestResult) -> io::Result<()> {
|
|
match *result {
|
|
TrIgnored => self.write_ignored(),
|
|
TrBench(ref bs) => {
|
|
try!(self.write_bench());
|
|
self.write_plain(&format!(": {}\n", fmt_bench_samples(bs)))
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn write_log(&mut self, test: &TestDesc, result: &TestResult) -> io::Result<()> {
|
|
match self.log_out {
|
|
None => Ok(()),
|
|
Some(ref mut o) => {
|
|
let s = format!("{} {}\n",
|
|
match *result {
|
|
TrIgnored => "ignored".to_owned(),
|
|
TrBench(ref bs) => fmt_bench_samples(bs),
|
|
},
|
|
test.name);
|
|
o.write_all(s.as_bytes())
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn write_failures(&mut self) -> io::Result<()> {
|
|
try!(self.write_plain("\nfailures:\n"));
|
|
let mut failures = Vec::new();
|
|
let mut fail_out = String::new();
|
|
for &(ref f, ref stdout) in &self.failures {
|
|
failures.push(f.name.to_string());
|
|
if !stdout.is_empty() {
|
|
fail_out.push_str(&format!("---- {} stdout ----\n\t", f.name));
|
|
let output = String::from_utf8_lossy(stdout);
|
|
fail_out.push_str(&output);
|
|
fail_out.push_str("\n");
|
|
}
|
|
}
|
|
if !fail_out.is_empty() {
|
|
try!(self.write_plain("\n"));
|
|
try!(self.write_plain(&fail_out));
|
|
}
|
|
|
|
try!(self.write_plain("\nfailures:\n"));
|
|
failures.sort();
|
|
for name in &failures {
|
|
try!(self.write_plain(&format!(" {}\n", name)));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn write_run_finish(&mut self) -> io::Result<bool> {
|
|
assert_eq!(self.passed + self.failed + self.ignored + self.measured, self.total);
|
|
|
|
let success = self.failed == 0;
|
|
if !success {
|
|
try!(self.write_failures());
|
|
}
|
|
|
|
try!(self.write_plain("\ntest result: "));
|
|
if success {
|
|
// There's no parallelism at this point so it's safe to use color
|
|
try!(self.write_pretty("ok"));
|
|
} else {
|
|
try!(self.write_pretty("FAILED"));
|
|
}
|
|
let s = format!(". {} passed; {} failed; {} ignored; {} measured\n\n",
|
|
self.passed,
|
|
self.failed,
|
|
self.ignored,
|
|
self.measured);
|
|
try!(self.write_plain(&s));
|
|
Ok(success)
|
|
}
|
|
}
|
|
|
|
// Format a number with thousands separators
|
|
fn fmt_thousands_sep(mut n: usize, sep: char) -> String {
|
|
use std::fmt::Write;
|
|
let mut output = String::new();
|
|
let mut trailing = false;
|
|
for &pow in &[9, 6, 3, 0] {
|
|
let base = 10_usize.pow(pow);
|
|
if pow == 0 || trailing || n / base != 0 {
|
|
if !trailing {
|
|
output.write_fmt(format_args!("{}", n / base)).unwrap();
|
|
} else {
|
|
output.write_fmt(format_args!("{:03}", n / base)).unwrap();
|
|
}
|
|
if pow != 0 {
|
|
output.push(sep);
|
|
}
|
|
trailing = true;
|
|
}
|
|
n %= base;
|
|
}
|
|
|
|
output
|
|
}
|
|
|
|
pub fn fmt_bench_samples(bs: &BenchSamples) -> String {
|
|
use std::fmt::Write;
|
|
let mut output = String::new();
|
|
|
|
let median = bs.ns_iter_summ.median as usize;
|
|
let deviation = (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as usize;
|
|
|
|
output.write_fmt(format_args!("{:>11} ns/iter (+/- {})",
|
|
fmt_thousands_sep(median, ','),
|
|
fmt_thousands_sep(deviation, ',')))
|
|
.unwrap();
|
|
if bs.mb_s != 0 {
|
|
output.write_fmt(format_args!(" = {} MB/s", bs.mb_s)).unwrap();
|
|
}
|
|
output
|
|
}
|
|
|
|
// A simple console test runner
|
|
pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Result<bool> {
|
|
|
|
fn callback<T: Write>(event: &TestEvent, st: &mut ConsoleTestState<T>) -> io::Result<()> {
|
|
match (*event).clone() {
|
|
TeFiltered(ref filtered_tests) => st.write_run_start(filtered_tests.len()),
|
|
TeWait(ref test, padding) => st.write_test_start(test, padding),
|
|
TeResult(test, result, _) => {
|
|
try!(st.write_log(&test, &result));
|
|
try!(st.write_result(&result));
|
|
match result {
|
|
TrIgnored => st.ignored += 1,
|
|
TrBench(_) => {
|
|
st.measured += 1
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut st = try!(ConsoleTestState::new(opts));
|
|
fn len_if_padded(t: &TestDescAndFn) -> usize {
|
|
match t.testfn.padding() {
|
|
PadOnRight => t.desc.name.len(),
|
|
}
|
|
}
|
|
if let Some(t) = tests.iter().max_by_key(|t| len_if_padded(*t)) {
|
|
let n = &t.desc.name;
|
|
st.max_name_len = n.len();
|
|
}
|
|
try!(run_tests(opts, tests, |x| callback(&x, &mut st)));
|
|
st.write_run_finish()
|
|
}
|
|
|
|
#[test]
|
|
fn should_sort_failures_before_printing_them() {
|
|
let test_a = TestDesc {
|
|
name: Cow::from("a"),
|
|
ignore: false,
|
|
};
|
|
|
|
let test_b = TestDesc {
|
|
name: Cow::from("b"),
|
|
ignore: false,
|
|
};
|
|
|
|
let mut st = ConsoleTestState {
|
|
log_out: None,
|
|
out: Raw(Vec::new()),
|
|
quiet: false,
|
|
total: 0,
|
|
passed: 0,
|
|
failed: 0,
|
|
ignored: 0,
|
|
measured: 0,
|
|
max_name_len: 10,
|
|
failures: vec![(test_b, Vec::new()), (test_a, Vec::new())],
|
|
};
|
|
|
|
st.write_failures().unwrap();
|
|
let s = match st.out {
|
|
Raw(ref m) => String::from_utf8_lossy(&m[..]),
|
|
};
|
|
|
|
let apos = s.find("a").unwrap();
|
|
let bpos = s.find("b").unwrap();
|
|
assert!(apos < bpos);
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
enum TestEvent {
|
|
TeFiltered(Vec<TestDesc>),
|
|
TeWait(TestDesc, NamePadding),
|
|
TeResult(TestDesc, TestResult, Vec<u8>),
|
|
}
|
|
|
|
type MonitorMsg = (TestDesc, TestResult, Vec<u8>);
|
|
|
|
|
|
fn run_tests<F>(opts: &TestOpts, tests: Vec<TestDescAndFn>, mut callback: F) -> io::Result<()>
|
|
where F: FnMut(TestEvent) -> io::Result<()>
|
|
{
|
|
|
|
let filtered_tests = filter_tests(opts, tests);
|
|
|
|
let filtered_descs = filtered_tests.iter()
|
|
.map(|t| t.desc.clone())
|
|
.collect();
|
|
|
|
try!(callback(TeFiltered(filtered_descs)));
|
|
|
|
let filtered_benchs_and_metrics = filtered_tests;
|
|
|
|
// All benchmarks run at the end, in serial.
|
|
// (this includes metric fns)
|
|
for b in filtered_benchs_and_metrics {
|
|
try!(callback(TeWait(b.desc.clone(), b.testfn.padding())));
|
|
let (test, result, stdout) = run_test(opts, false, b);
|
|
try!(callback(TeResult(test, result, stdout)));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn filter_tests(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> Vec<TestDescAndFn> {
|
|
let mut filtered = tests;
|
|
|
|
// Remove tests that don't match the test filter
|
|
filtered = match opts.filter {
|
|
None => filtered,
|
|
Some(ref filter) => {
|
|
filtered.into_iter()
|
|
.filter(|test| test.desc.name.contains(&filter[..]))
|
|
.collect()
|
|
}
|
|
};
|
|
|
|
// Maybe pull out the ignored test and unignore them
|
|
filtered = if !opts.run_ignored {
|
|
filtered
|
|
} else {
|
|
fn filter(test: TestDescAndFn) -> Option<TestDescAndFn> {
|
|
if test.desc.ignore {
|
|
let TestDescAndFn {desc, testfn} = test;
|
|
Some(TestDescAndFn {
|
|
desc: TestDesc { ignore: false, ..desc },
|
|
testfn: testfn,
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
filtered.into_iter().filter_map(filter).collect()
|
|
};
|
|
|
|
// Sort the tests alphabetically
|
|
filtered.sort_by(|t1, t2| t1.desc.name.cmp(&t2.desc.name));
|
|
|
|
filtered
|
|
}
|
|
|
|
fn run_test(_opts: &TestOpts,
|
|
force_ignore: bool,
|
|
test: TestDescAndFn) -> MonitorMsg
|
|
{
|
|
|
|
let TestDescAndFn {desc, testfn} = test;
|
|
|
|
if force_ignore || desc.ignore {
|
|
return (desc, TrIgnored, Vec::new());
|
|
}
|
|
|
|
match testfn {
|
|
DynBenchFn(bencher) => {
|
|
let bs = ::bench::benchmark(|harness| bencher.run(harness));
|
|
(desc, TrBench(bs), Vec::new())
|
|
}
|
|
StaticBenchFn(benchfn) => {
|
|
let bs = ::bench::benchmark(|harness| benchfn(harness));
|
|
(desc, TrBench(bs), Vec::new())
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Benchmarking
|
|
|
|
// FIXME: We don't have black_box in stable rust
|
|
|
|
/// NOTE: We don't have a proper black box in stable Rust. This is
|
|
/// a workaround implementation, that may have a too big performance overhead,
|
|
/// depending on operation, or it may fail to properly avoid having code
|
|
/// optimized out. It is good enough that it is used by default.
|
|
///
|
|
/// A function that is opaque to the optimizer, to allow benchmarks to
|
|
/// pretend to use outputs to assist in avoiding dead-code
|
|
/// elimination.
|
|
pub fn black_box<T>(dummy: T) -> T {
|
|
unsafe {
|
|
let ret = ptr::read_volatile(&dummy);
|
|
forget(dummy);
|
|
ret
|
|
}
|
|
}
|
|
|
|
|
|
impl Bencher {
|
|
/// Callback for benchmark functions to run in their body.
|
|
pub fn iter<T, F>(&mut self, mut inner: F)
|
|
where F: FnMut() -> T
|
|
{
|
|
let start = Instant::now();
|
|
let k = self.iterations;
|
|
for _ in 0..k {
|
|
black_box(inner());
|
|
}
|
|
self.dur = start.elapsed();
|
|
}
|
|
|
|
pub fn ns_elapsed(&mut self) -> u64 {
|
|
self.dur.as_secs() * 1_000_000_000 + (self.dur.subsec_nanos() as u64)
|
|
}
|
|
|
|
pub fn ns_per_iter(&mut self) -> u64 {
|
|
if self.iterations == 0 {
|
|
0
|
|
} else {
|
|
self.ns_elapsed() / cmp::max(self.iterations, 1)
|
|
}
|
|
}
|
|
|
|
pub fn bench_n<F>(&mut self, n: u64, f: F)
|
|
where F: FnOnce(&mut Bencher)
|
|
{
|
|
self.iterations = n;
|
|
f(self);
|
|
}
|
|
|
|
// This is a more statistics-driven benchmark algorithm
|
|
pub fn auto_bench<F>(&mut self, mut f: F) -> stats::Summary
|
|
where F: FnMut(&mut Bencher)
|
|
{
|
|
// Initial bench run to get ballpark figure.
|
|
let mut n = 1;
|
|
self.bench_n(n, |x| f(x));
|
|
|
|
// Try to estimate iter count for 1ms falling back to 1m
|
|
// iterations if first run took < 1ns.
|
|
if self.ns_per_iter() == 0 {
|
|
n = 1_000_000;
|
|
} else {
|
|
n = 1_000_000 / cmp::max(self.ns_per_iter(), 1);
|
|
}
|
|
// if the first run took more than 1ms we don't want to just
|
|
// be left doing 0 iterations on every loop. The unfortunate
|
|
// side effect of not being able to do as many runs is
|
|
// automatically handled by the statistical analysis below
|
|
// (i.e. larger error bars).
|
|
if n == 0 {
|
|
n = 1;
|
|
}
|
|
|
|
let mut total_run = Duration::new(0, 0);
|
|
let samples: &mut [f64] = &mut [0.0_f64; 50];
|
|
loop {
|
|
let loop_start = Instant::now();
|
|
|
|
for p in &mut *samples {
|
|
self.bench_n(n, |x| f(x));
|
|
*p = self.ns_per_iter() as f64;
|
|
}
|
|
|
|
stats::winsorize(samples, 5.0);
|
|
let summ = stats::Summary::new(samples);
|
|
|
|
for p in &mut *samples {
|
|
self.bench_n(5 * n, |x| f(x));
|
|
*p = self.ns_per_iter() as f64;
|
|
}
|
|
|
|
stats::winsorize(samples, 5.0);
|
|
let summ5 = stats::Summary::new(samples);
|
|
let loop_run = loop_start.elapsed();
|
|
|
|
// If we've run for 100ms and seem to have converged to a
|
|
// stable median.
|
|
if loop_run > Duration::from_millis(100) && summ.median_abs_dev_pct < 1.0 &&
|
|
summ.median - summ5.median < summ5.median_abs_dev {
|
|
return summ5;
|
|
}
|
|
|
|
total_run += loop_run;
|
|
// Longest we ever run for is 3s.
|
|
if total_run > Duration::from_secs(3) {
|
|
return summ5;
|
|
}
|
|
|
|
// If we overflow here just return the results so far. We check a
|
|
// multiplier of 10 because we're about to multiply by 2 and the
|
|
// next iteration of the loop will also multiply by 5 (to calculate
|
|
// the summ5 result)
|
|
n = match n.checked_mul(10) {
|
|
Some(_) => n * 2,
|
|
None => return summ5,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
pub mod bench {
|
|
use std::cmp;
|
|
use std::time::Duration;
|
|
use super::{Bencher, BenchSamples};
|
|
|
|
pub fn benchmark<F>(f: F) -> BenchSamples
|
|
where F: FnMut(&mut Bencher)
|
|
{
|
|
let mut bs = Bencher {
|
|
iterations: 0,
|
|
dur: Duration::new(0, 0),
|
|
bytes: 0,
|
|
};
|
|
|
|
let ns_iter_summ = bs.auto_bench(f);
|
|
|
|
let ns_iter = cmp::max(ns_iter_summ.median as u64, 1);
|
|
let mb_s = bs.bytes * 1000 / ns_iter;
|
|
|
|
BenchSamples {
|
|
ns_iter_summ: ns_iter_summ,
|
|
mb_s: mb_s as usize,
|
|
}
|
|
}
|
|
|
|
pub fn run_once<F>(f: F)
|
|
where F: FnOnce(&mut Bencher)
|
|
{
|
|
let mut bs = Bencher {
|
|
iterations: 0,
|
|
dur: Duration::new(0, 0),
|
|
bytes: 0,
|
|
};
|
|
bs.bench_n(1, f);
|
|
}
|
|
}
|
|
|