blob: da9b4dabd2f160b3af1e00dc279a283b9e9b1a08 [file] [log] [blame]
//! A statistics-driven micro-benchmarking library written in Rust.
//!
//! This crate is a microbenchmarking library which aims to provide strong
//! statistical confidence in detecting and estimating the size of performance
//! improvements and regressions, whle also being easy to use.
//!
//! See
//! [the user guide](https://bheisler.github.io/criterion.rs/book/index.html)
//! for examples as well as details on the measurement and analysis process,
//! and the output.
//!
//! ## Features:
//! * Benchmark Rust code as well as external programs
//! * Collects detailed statistics, providing strong confidence that changes
//! to performance are real, not measurement noise
//! * Produces detailed charts, providing thorough understanding of your code's
//! performance behavior.
#![deny(missing_docs)]
#![cfg_attr(feature = "real_blackbox", feature(test))]
#![cfg_attr(not(feature = "html_reports"), allow(dead_code))]
#![cfg_attr(
feature = "cargo-clippy",
allow(
clippy::used_underscore_binding,
clippy::just_underscores_and_digits,
clippy::transmute_ptr_to_ptr
)
)]
#[cfg(test)]
#[macro_use]
extern crate approx;
#[cfg(test)]
#[macro_use]
extern crate quickcheck;
#[cfg(test)]
extern crate rand;
#[macro_use]
extern crate clap;
#[macro_use]
extern crate lazy_static;
extern crate atty;
extern crate cast;
extern crate csv;
extern crate itertools;
extern crate num_traits;
extern crate rand_core;
extern crate rand_os;
extern crate rand_xoshiro;
extern crate rayon;
extern crate serde;
extern crate serde_json;
extern crate walkdir;
#[cfg(feature = "html_reports")]
extern crate criterion_plot;
#[cfg(feature = "html_reports")]
extern crate tinytemplate;
#[cfg(feature = "real_blackbox")]
extern crate test;
#[macro_use]
extern crate serde_derive;
// Needs to be declared before other modules
// in order to be usable there.
#[macro_use]
mod macros_private;
#[macro_use]
mod analysis;
mod benchmark;
mod csv_report;
mod error;
mod estimate;
mod format;
mod fs;
mod macros;
mod program;
mod report;
mod routine;
mod stats;
#[cfg(feature = "html_reports")]
mod kde;
#[cfg(feature = "html_reports")]
mod plot;
#[cfg(feature = "html_reports")]
mod html;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::default::Default;
use std::fmt;
use std::iter::IntoIterator;
use std::process::Command;
use std::time::{Duration, Instant};
use benchmark::BenchmarkConfig;
use benchmark::NamedRoutine;
use csv_report::FileCsvReport;
use estimate::{Distributions, Estimates, Statistic};
use plotting::Plotting;
use report::{CliReport, Report, ReportContext, Reports};
use routine::Function;
#[cfg(feature = "html_reports")]
use html::Html;
pub use benchmark::{Benchmark, BenchmarkDefinition, ParameterizedBenchmark};
lazy_static! {
static ref DEBUG_ENABLED: bool = { std::env::vars().any(|(key, _)| key == "CRITERION_DEBUG") };
}
fn debug_enabled() -> bool {
*DEBUG_ENABLED
}
// Fake function which shows a deprecation warning when compiled without the html_reports feature.
#[cfg(not(feature = "html_reports"))]
#[cfg_attr(not(feature = "html_reports"), doc(hidden))]
pub fn deprecation_warning() {
#[deprecated(
since = "0.2.6",
note = "The html_reports cargo feature is deprecated. As of 0.3.0, HTML reports will no longer be optional."
)]
fn deprecation_warning_inner() {}
deprecation_warning_inner()
}
/// A function that is opaque to the optimizer, used to prevent the compiler from
/// optimizing away computations in a benchmark.
///
/// This variant is backed by the (unstable) test::black_box function.
#[cfg(feature = "real_blackbox")]
pub fn black_box<T>(dummy: T) -> T {
test::black_box(dummy)
}
/// A function that is opaque to the optimizer, used to prevent the compiler from
/// optimizing away computations in a benchmark.
///
/// This variant is stable-compatible, but it may cause some performance overhead
/// or fail to prevent code from being eliminated.
#[cfg(not(feature = "real_blackbox"))]
pub fn black_box<T>(dummy: T) -> T {
unsafe {
let ret = std::ptr::read_volatile(&dummy);
std::mem::forget(dummy);
ret
}
}
/// Representing a function to benchmark together with a name of that function.
/// Used together with `bench_functions` to represent one out of multiple functions
/// under benchmark.
pub struct Fun<I: fmt::Debug> {
f: NamedRoutine<I>,
}
impl<I> Fun<I>
where
I: fmt::Debug + 'static,
{
/// Create a new `Fun` given a name and a closure
pub fn new<F>(name: &str, f: F) -> Fun<I>
where
F: FnMut(&mut Bencher, &I) + 'static,
{
let routine = NamedRoutine {
id: name.to_owned(),
f: Box::new(RefCell::new(Function::new(f))),
};
Fun { f: routine }
}
}
/// Argument to [`Bencher::iter_batched`](struct.Bencher.html#method.iter_batched) and
/// [`Bencher::iter_batched_ref`](struct.Bencher.html#method.iter_batched_ref) which controls the
/// batch size.
///
/// Generally speaking, almost all benchmarks should use `SmallInput`. If the input or the result
/// of the benchmark routine is large enough that `SmallInput` causes out-of-memory errors,
/// `LargeInput` can be used to reduce memory usage at the cost of increasing the measurement
/// overhead. If the input or the result is extremely large (or if it holds some
/// limited external resource like a file handle), `PerIteration` will set the number of iterations
/// per batch to exactly one. `PerIteration` can increase the measurement overhead substantially
/// and should be avoided wherever possible.
///
/// Each value lists an estimate of the measurement overhead. This is intended as a rough guide
/// to assist in choosing an option, it should not be relied upon. In particular, it is not valid
/// to subtract the listed overhead from the measurement and assume that the result represents the
/// true runtime of a function. The actual measurement overhead for your specific benchmark depends
/// on the details of the function you're benchmarking and the hardware and operating
/// system running the benchmark.
///
/// With that said, if the runtime of your function is small relative to the measurement overhead
/// it will be difficult to take accurate measurements. In this situation, the best option is to use
/// [`Bencher::iter`](struct.Bencher.html#method.iter_batched_ref) which has next-to-zero measurement
/// overhead.
#[derive(Debug, Eq, PartialEq, Copy, Hash, Clone)]
pub enum BatchSize {
/// `SmallInput` indicates that the input to the benchmark routine (the value returned from
/// the setup routine) is small enough that millions of values can be safely held in memory.
/// Always prefer `SmallInput` unless the benchmark is using too much memory.
///
/// In testing, the maximum measurement overhead from benchmarking with `SmallInput` is on the
/// order of 500 picoseconds. This is presented as a rough guide; your results may vary.
SmallInput,
/// `LargeInput` indicates that the input to the benchmark routine or the value returned from
/// that routine is large. This will reduce the memory usage but increase the measurement
/// overhead.
///
/// In testing, the maximum measurement overhead from benchmarking with `LargeInput` is on the
/// order of 750 picoseconds. This is presented as a rough guide; your results may vary.
LargeInput,
/// `PerIteration` indicates that the input to the benchmark routine or the value returned from
/// that routine is extremely large or holds some limited resource, such that holding many values
/// in memory at once is infeasible. This provides the worst measurement overhead, but the
/// lowest memory usage.
///
/// In testing, the maximum measurement overhead from benchmarking with `PerIteration` is on the
/// order of 350 nanoseconds or 350,000 picoseconds. This is presented as a rough guide; your
/// results may vary.
PerIteration,
/// `NumBatches` will attempt to divide the iterations up into a given number of batches.
/// A larger number of batches (and thus smaller batches) will reduce memory usage but increase
/// measurement overhead. This allows the user to choose their own tradeoff between memory usage
/// and measurement overhead, but care must be taken in tuning the number of batches. Most
/// benchmarks should use `SmallInput` or `LargeInput` instead.
NumBatches(u64),
/// `NumIterations` fixes the batch size to a constant number, specified by the user. This
/// allows the user to choose their own tradeoff between overhead and memory usage, but care must
/// be taken in tuning the batch size. In general, the measurement overhead of NumIterations
/// will be larger than that of `NumBatches`. Most benchmarks should use `SmallInput` or
/// `LargeInput` instead.
NumIterations(u64),
#[doc(hidden)]
__NonExhaustive,
}
impl BatchSize {
/// Convert to a number of iterations per batch.
///
/// We try to do a constant number of batches regardless of the number of iterations in this
/// sample. If the measurement overhead is roughly constant regardless of the number of
/// iterations the analysis of the results later will have an easier time separating the
/// measurement overhead from the benchmark time.
fn iters_per_batch(self, iters: u64) -> u64 {
match self {
BatchSize::SmallInput => (iters + 10 - 1) / 10,
BatchSize::LargeInput => (iters + 1000 - 1) / 1000,
BatchSize::PerIteration => 1,
BatchSize::NumBatches(batches) => (iters + batches - 1) / batches,
BatchSize::NumIterations(size) => size,
BatchSize::__NonExhaustive => panic!("__NonExhaustive is not a valid BatchSize."),
}
}
}
/// Timer struct to iterate a benchmarked function and measure the runtime.
///
/// This struct provides different timing loops as methods. Each timing loop provides a different
/// way to time a routine and each has advantages and disadvantages.
///
/// * If your routine requires no per-iteration setup and returns a value with an expensive `drop`
/// method, use `iter_with_large_drop`.
/// * If your routine requires some per-iteration setup that shouldn't be timed, use `iter_batched`
/// or `iter_batched_ref`. See [`BatchSize`](enum.BatchSize.html) for a discussion of batch sizes.
/// If the setup value implements `Drop` and you don't want to include the `drop` time in the
/// measurement, use `iter_batched_ref`, otherwise use `iter_batched`. These methods are also
/// suitable for benchmarking routines which return a value with an expensive `drop` method,
/// but are more complex than `iter_with_large_drop`.
/// * Otherwise, use `iter`.
#[derive(Clone, Copy)]
pub struct Bencher {
iterated: bool,
iters: u64,
elapsed: Duration,
}
impl Bencher {
/// Times a `routine` by executing it many times and timing the total elapsed time.
///
/// Prefer this timing loop when `routine` returns a value that doesn't have a destructor.
///
/// # Timing model
///
/// Note that the `Bencher` also times the time required to destroy the output of `routine()`.
/// Therefore prefer this timing loop when the runtime of `mem::drop(O)` is negligible compared
/// to the runtime of the `routine`.
///
/// ```text
/// elapsed = Instant::now + iters * (routine + mem::drop(O) + Range::next)
/// ```
///
/// # Example
///
/// ```rust
/// #[macro_use] extern crate criterion;
///
/// use criterion::*;
///
/// // The function to benchmark
/// fn foo() {
/// // ...
/// }
///
/// fn bench(c: &mut Criterion) {
/// c.bench_function("iter", move |b| {
/// b.iter(|| foo())
/// });
/// }
///
/// criterion_group!(benches, bench);
/// criterion_main!(benches);
/// ```
///
#[inline(never)]
pub fn iter<O, R>(&mut self, mut routine: R)
where
R: FnMut() -> O,
{
self.iterated = true;
let start = Instant::now();
for _ in 0..self.iters {
black_box(routine());
}
self.elapsed = start.elapsed();
}
#[doc(hidden)]
pub fn iter_with_setup<I, O, S, R>(&mut self, setup: S, routine: R)
where
S: FnMut() -> I,
R: FnMut(I) -> O,
{
self.iter_batched(setup, routine, BatchSize::PerIteration);
}
/// Times a `routine` by collecting its output on each iteration. This avoids timing the
/// destructor of the value returned by `routine`.
///
/// WARNING: This requires `O(iters * mem::size_of::<O>())` of memory, and `iters` is not under the
/// control of the caller. If this causes out-of-memory errors, use `iter_batched` instead.
///
/// # Timing model
///
/// ``` text
/// elapsed = Instant::now + iters * (routine) + Iterator::collect::<Vec<_>>
/// ```
///
/// # Example
///
/// ```rust
/// #[macro_use] extern crate criterion;
///
/// use criterion::*;
///
/// fn create_vector() -> Vec<u64> {
/// # vec![]
/// // ...
/// }
///
/// fn bench(c: &mut Criterion) {
/// c.bench_function("with_drop", move |b| {
/// // This will avoid timing the Vec::drop.
/// b.iter_with_large_drop(|| create_vector())
/// });
/// }
///
/// criterion_group!(benches, bench);
/// criterion_main!(benches);
/// ```
///
#[doc(hidden)]
pub fn iter_with_large_drop<O, R>(&mut self, mut routine: R)
where
R: FnMut() -> O,
{
self.iter_batched(|| (), |_| routine(), BatchSize::SmallInput);
}
#[doc(hidden)]
pub fn iter_with_large_setup<I, O, S, R>(&mut self, setup: S, routine: R)
where
S: FnMut() -> I,
R: FnMut(I) -> O,
{
self.iter_batched(setup, routine, BatchSize::NumBatches(1));
}
/// Times a `routine` that requires some input by generating a batch of input, then timing the
/// iteration of the benchmark over the input. See [`BatchSize`](struct.BatchSize.html) for
/// details on choosing the batch size. Use this when the routine must consume its input.
///
/// For example, use this loop to benchmark sorting algorithms, because they require unsorted
/// data on each iteration.
///
/// # Timing model
///
/// ```text
/// elapsed = (Instant::now * num_batches) + (iters * (routine + O::drop)) + Vec::extend
/// ```
///
/// # Example
///
/// ```rust
/// #[macro_use] extern crate criterion;
///
/// use criterion::*;
///
/// fn create_scrambled_data() -> Vec<u64> {
/// # vec![]
/// // ...
/// }
///
/// // The sorting algorithm to test
/// fn sort(data: &mut [u64]) {
/// // ...
/// }
///
/// fn bench(c: &mut Criterion) {
/// let data = create_scrambled_data();
///
/// c.bench_function("with_setup", move |b| {
/// // This will avoid timing the to_vec call.
/// b.iter_batched(|| data.clone(), |mut data| sort(&mut data), BatchSize::SmallInput)
/// });
/// }
///
/// criterion_group!(benches, bench);
/// criterion_main!(benches);
/// ```
///
#[inline(never)]
pub fn iter_batched<I, O, S, R>(&mut self, mut setup: S, mut routine: R, size: BatchSize)
where
S: FnMut() -> I,
R: FnMut(I) -> O,
{
self.iterated = true;
let batch_size = size.iters_per_batch(self.iters);
assert!(batch_size != 0, "Batch size must not be zero.");
self.elapsed = Duration::from_secs(0);
if batch_size == 1 {
for _ in 0..self.iters {
let mut input = black_box(setup());
let start = Instant::now();
let output = routine(input);
self.elapsed += start.elapsed();
drop(black_box(output));
}
} else {
let mut iteration_counter = 0;
while iteration_counter < self.iters {
let batch_size = ::std::cmp::min(batch_size, self.iters - iteration_counter);
let inputs = black_box((0..batch_size).map(|_| setup()).collect::<Vec<_>>());
let mut outputs = Vec::with_capacity(batch_size as usize);
let start = Instant::now();
outputs.extend(inputs.into_iter().map(&mut routine));
self.elapsed += start.elapsed();
black_box(outputs);
iteration_counter += batch_size;
}
}
}
/// Times a `routine` that requires some input by generating a batch of input, then timing the
/// iteration of the benchmark over the input. See [`BatchSize`](struct.BatchSize.html) for
/// details on choosing the batch size. Use this when the routine should accept the input by
/// mutable reference.
///
/// For example, use this loop to benchmark sorting algorithms, because they require unsorted
/// data on each iteration.
///
/// # Timing model
///
/// ```text
/// elapsed = (Instant::now * num_batches) + (iters * routine) + Vec::extend
/// ```
///
/// # Example
///
/// ```rust
/// #[macro_use] extern crate criterion;
///
/// use criterion::*;
///
/// fn create_scrambled_data() -> Vec<u64> {
/// # vec![]
/// // ...
/// }
///
/// // The sorting algorithm to test
/// fn sort(data: &mut [u64]) {
/// // ...
/// }
///
/// fn bench(c: &mut Criterion) {
/// let data = create_scrambled_data();
///
/// c.bench_function("with_setup", move |b| {
/// // This will avoid timing the to_vec call.
/// b.iter_batched(|| data.clone(), |mut data| sort(&mut data), BatchSize::SmallInput)
/// });
/// }
///
/// criterion_group!(benches, bench);
/// criterion_main!(benches);
/// ```
///
#[inline(never)]
pub fn iter_batched_ref<I, O, S, R>(&mut self, mut setup: S, mut routine: R, size: BatchSize)
where
S: FnMut() -> I,
R: FnMut(&mut I) -> O,
{
self.iterated = true;
let batch_size = size.iters_per_batch(self.iters);
assert!(batch_size != 0, "Batch size must not be zero.");
self.elapsed = Duration::from_secs(0);
if batch_size == 1 {
for _ in 0..self.iters {
let mut input = black_box(setup());
let start = Instant::now();
let output = routine(&mut input);
self.elapsed += start.elapsed();
drop(black_box(output));
drop(black_box(input));
}
} else {
let mut iteration_counter = 0;
while iteration_counter < self.iters {
let batch_size = ::std::cmp::min(batch_size, self.iters - iteration_counter);
let mut inputs = black_box((0..batch_size).map(|_| setup()).collect::<Vec<_>>());
let mut outputs = Vec::with_capacity(batch_size as usize);
let start = Instant::now();
outputs.extend(inputs.iter_mut().map(&mut routine));
self.elapsed += start.elapsed();
black_box(outputs);
iteration_counter += batch_size;
}
}
}
// Benchmarks must actually call one of the iter methods. This causes benchmarks to fail loudly
// if they don't.
fn assert_iterated(&mut self) {
if !self.iterated {
panic!("Benchmark function must call Bencher::iter or related method.");
}
self.iterated = false;
}
}
/// Baseline describes how the baseline_directory is handled.
pub enum Baseline {
/// Compare ensures a previous saved version of the baseline
/// exists and runs comparison against that.
Compare,
/// Save writes the benchmark results to the baseline directory,
/// overwriting any results that were previously there.
Save,
}
/// The benchmark manager
///
/// `Criterion` lets you configure and execute benchmarks
///
/// Each benchmark consists of four phases:
///
/// - **Warm-up**: The routine is repeatedly executed, to let the CPU/OS/JIT/interpreter adapt to
/// the new load
/// - **Measurement**: The routine is repeatedly executed, and timing information is collected into
/// a sample
/// - **Analysis**: The sample is analyzed and distiled into meaningful statistics that get
/// reported to stdout, stored in files, and plotted
/// - **Comparison**: The current sample is compared with the sample obtained in the previous
/// benchmark.
pub struct Criterion {
config: BenchmarkConfig,
plotting: Plotting,
filter: Option<String>,
report: Box<Report>,
output_directory: String,
baseline_directory: String,
baseline: Baseline,
profile_time: Option<Duration>,
test_mode: bool,
list_mode: bool,
}
impl Default for Criterion {
/// Creates a benchmark manager with the following default settings:
///
/// - Sample size: 100 measurements
/// - Warm-up time: 3 s
/// - Measurement time: 5 s
/// - Bootstrap size: 100 000 resamples
/// - Noise threshold: 0.01 (1%)
/// - Confidence level: 0.95
/// - Significance level: 0.05
/// - Plotting: enabled (if gnuplot is available)
/// - No filter
fn default() -> Criterion {
#[allow(unused_mut, unused_assignments)]
let mut plotting = Plotting::Unset;
let mut reports: Vec<Box<Report>> = vec![];
reports.push(Box::new(CliReport::new(false, false, false)));
reports.push(Box::new(FileCsvReport));
let output_directory =
match std::env::vars().find(|&(ref key, _)| key == "CARGO_TARGET_DIR") {
Some((_, value)) => format!("{}/criterion", value),
None => "target/criterion".to_owned(),
};
Criterion {
config: BenchmarkConfig {
confidence_level: 0.95,
measurement_time: Duration::new(5, 0),
noise_threshold: 0.01,
nresamples: 100_000,
sample_size: 100,
significance_level: 0.05,
warm_up_time: Duration::new(3, 0),
},
plotting,
filter: None,
report: Box::new(Reports::new(reports)),
baseline_directory: "base".to_owned(),
baseline: Baseline::Save,
profile_time: None,
test_mode: false,
list_mode: false,
output_directory,
}
}
}
impl Criterion {
/// Changes the default size of the sample for benchmarks run with this runner.
///
/// A bigger sample should yield more accurate results if paired with a sufficiently large
/// measurement time.
///
/// Sample size must be at least 2.
///
/// # Panics
///
/// Panics if set to zero or one
pub fn sample_size(mut self, n: usize) -> Criterion {
assert!(n >= 2);
if n < 10 {
println!("Warning: Sample sizes < 10 will be disallowed in Criterion.rs 0.3.0.");
}
self.config.sample_size = n;
self
}
/// Changes the default warm up time for benchmarks run with this runner.
///
/// # Panics
///
/// Panics if the input duration is zero
pub fn warm_up_time(mut self, dur: Duration) -> Criterion {
assert!(dur.to_nanos() > 0);
self.config.warm_up_time = dur;
self
}
/// Changes the default measurement time for benchmarks run with this runner.
///
/// With a longer time, the measurement will become more resilient to transitory peak loads
/// caused by external programs
///
/// **Note**: If the measurement time is too "low", Criterion will automatically increase it
///
/// # Panics
///
/// Panics if the input duration in zero
pub fn measurement_time(mut self, dur: Duration) -> Criterion {
assert!(dur.to_nanos() > 0);
self.config.measurement_time = dur;
self
}
/// Changes the default number of resamples for benchmarks run with this runner.
///
/// Number of resamples to use for the
/// [bootstrap](http://en.wikipedia.org/wiki/Bootstrapping_(statistics)#Case_resampling)
///
/// A larger number of resamples reduces the random sampling errors, which are inherent to the
/// bootstrap method, but also increases the analysis time
///
/// # Panics
///
/// Panics if the number of resamples is set to zero
pub fn nresamples(mut self, n: usize) -> Criterion {
assert!(n > 0);
self.config.nresamples = n;
self
}
/// Changes the default noise threshold for benchmarks run with this runner.
///
/// This threshold is used to decide if an increase of `X%` in the execution time is considered
/// significant or should be flagged as noise
///
/// *Note:* A value of `0.02` is equivalent to `2%`
///
/// # Panics
///
/// Panics is the threshold is set to a negative value
pub fn noise_threshold(mut self, threshold: f64) -> Criterion {
assert!(threshold >= 0.0);
self.config.noise_threshold = threshold;
self
}
/// Changes the default confidence level for benchmarks run with this runner
///
/// The confidence level is used to calculate the
/// [confidence intervals](https://en.wikipedia.org/wiki/Confidence_interval) of the estimated
/// statistics
///
/// # Panics
///
/// Panics if the confidence level is set to a value outside the `(0, 1)` range
pub fn confidence_level(mut self, cl: f64) -> Criterion {
assert!(cl > 0.0 && cl < 1.0);
self.config.confidence_level = cl;
self
}
/// Changes the default [significance level](https://en.wikipedia.org/wiki/Statistical_significance)
/// for benchmarks run with this runner
///
/// The significance level is used for
/// [hypothesis testing](https://en.wikipedia.org/wiki/Statistical_hypothesis_testing)
///
/// # Panics
///
/// Panics if the significance level is set to a value outside the `(0, 1)` range
pub fn significance_level(mut self, sl: f64) -> Criterion {
assert!(sl > 0.0 && sl < 1.0);
self.config.significance_level = sl;
self
}
/// Enables plotting
#[cfg(feature = "html_reports")]
pub fn with_plots(mut self) -> Criterion {
use criterion_plot::VersionError;
self.plotting = match criterion_plot::version() {
Ok(_) => {
let mut reports: Vec<Box<Report>> = vec![];
reports.push(Box::new(CliReport::new(false, false, false)));
reports.push(Box::new(FileCsvReport));
reports.push(Box::new(Html::new()));
self.report = Box::new(Reports::new(reports));
Plotting::Enabled
}
Err(e) => {
match e {
VersionError::Exec(_) => println!("Gnuplot not found, disabling plotting"),
e => println!("Gnuplot not found or not usable, disabling plotting\n{}", e),
}
Plotting::NotAvailable
}
};
self
}
/// Enables plotting
#[cfg(not(feature = "html_reports"))]
pub fn with_plots(self) -> Criterion {
self
}
/// Disables plotting
pub fn without_plots(mut self) -> Criterion {
self.plotting = Plotting::Disabled;
self
}
/// Return true if generation of the plots is possible.
#[cfg(feature = "html_reports")]
pub fn can_plot(&self) -> bool {
match self.plotting {
Plotting::NotAvailable => false,
Plotting::Enabled => true,
_ => criterion_plot::version().is_ok(),
}
}
/// Return true if generation of the plots is possible.
#[cfg(not(feature = "html_reports"))]
pub fn can_plot(&self) -> bool {
false
}
/// Names an explicit baseline and enables overwriting the previous results.
pub fn save_baseline(mut self, baseline: String) -> Criterion {
self.baseline_directory = baseline;
self.baseline = Baseline::Save;
self
}
/// Names an explicit baseline and disables overwriting the previous results.
pub fn retain_baseline(mut self, baseline: String) -> Criterion {
self.baseline_directory = baseline;
self.baseline = Baseline::Compare;
self
}
/// Filters the benchmarks. Only benchmarks with names that contain the
/// given string will be executed.
pub fn with_filter<S: Into<String>>(mut self, filter: S) -> Criterion {
self.filter = Some(filter.into());
self
}
/// Set the output directory (currently for testing only)
#[doc(hidden)]
pub fn output_directory(mut self, path: &std::path::Path) -> Criterion {
self.output_directory = path.to_string_lossy().into_owned();
self
}
/// Generate the final summary at the end of a run.
#[doc(hidden)]
pub fn final_summary(&self) {
if self.profile_time.is_some() || self.test_mode {
return;
}
let report_context = ReportContext {
output_directory: self.output_directory.clone(),
plotting: self.plotting,
plot_config: PlotConfiguration::default(),
test_mode: self.test_mode,
};
self.report.final_summary(&report_context);
}
/// Configure this criterion struct based on the command-line arguments to
/// this process.
pub fn configure_from_args(mut self) -> Criterion {
use clap::{App, Arg};
let matches = App::new("Criterion Benchmark")
.arg(Arg::with_name("FILTER")
.help("Skip benchmarks whose names do not contain FILTER.")
.index(1))
.arg(Arg::with_name("color")
.short("c")
.long("color")
.alias("colour")
.takes_value(true)
.possible_values(&["auto", "always", "never"])
.default_value("auto")
.help("Configure coloring of output. always = always colorize output, never = never colorize output, auto = colorize output if output is a tty and compiled for unix."))
.arg(Arg::with_name("verbose")
.short("v")
.long("verbose")
.help("Print additional statistical information."))
.arg(Arg::with_name("noplot")
.short("n")
.long("noplot")
.help("Disable plot and HTML generation."))
.arg(Arg::with_name("save-baseline")
.short("s")
.long("save-baseline")
.default_value("base")
.help("Save results under a named baseline."))
.arg(Arg::with_name("baseline")
.short("b")
.long("baseline")
.takes_value(true)
.conflicts_with("save-baseline")
.help("Compare to a named baseline."))
.arg(Arg::with_name("list")
.long("list")
.help("List all benchmarks"))
.arg(Arg::with_name("measure-only")
.long("measure-only")
.hidden(true)
.help("Only perform measurements; do no analysis or storage of results. This is useful eg. when profiling the benchmarks, to reduce clutter in the profiling data."))
.arg(Arg::with_name("profile-time")
.long("profile-time")
.takes_value(true)
.help("Iterate each benchmark for approximately the given number of seconds, doing no analysis and without storing the results. Useful for running the benchmarks in a profiler."))
.arg(Arg::with_name("test")
.long("test")
.help("Run the benchmarks once, to verify that they execute successfully, but do not measure or report the results."))
//Ignored but always passed to benchmark executables
.arg(Arg::with_name("bench")
.hidden(true)
.long("bench"))
.arg(Arg::with_name("version")
.hidden(true)
.short("V")
.long("version"))
.after_help("
This executable is a Criterion.rs benchmark.
See https://github.com/bheisler/criterion.rs for more details.
To enable debug output, define the environment variable CRITERION_DEBUG.
Criterion.rs will output more debug information and will save the gnuplot
scripts alongside the generated plots.
")
.get_matches();
if let Some(filter) = matches.value_of("FILTER") {
self = self.with_filter(filter);
}
let verbose = matches.is_present("verbose");
let stdout_isatty = atty::is(atty::Stream::Stdout);
let mut enable_text_overwrite = stdout_isatty && !verbose && !debug_enabled();
let enable_text_coloring;
match matches.value_of("color") {
Some("always") => {
enable_text_coloring = true;
}
Some("never") => {
enable_text_coloring = false;
enable_text_overwrite = false;
}
_ => enable_text_coloring = stdout_isatty,
}
if matches.is_present("noplot") || matches.is_present("test") {
self = self.without_plots();
} else {
self = self.with_plots();
}
if let Some(dir) = matches.value_of("save-baseline") {
self.baseline = Baseline::Save;
self.baseline_directory = dir.to_owned()
}
if let Some(dir) = matches.value_of("baseline") {
self.baseline = Baseline::Compare;
self.baseline_directory = dir.to_owned();
}
let mut reports: Vec<Box<Report>> = vec![];
reports.push(Box::new(CliReport::new(
enable_text_overwrite,
enable_text_coloring,
verbose,
)));
reports.push(Box::new(FileCsvReport));
// TODO: Remove this in 0.3.0
if matches.is_present("measure-only") {
println!("Warning: The '--measure-only' argument is deprecated and will be removed in Criterion.rs 0.3.0. Use '--profile-time' instead.");
self.profile_time = Some(Duration::from_secs(5));
}
if matches.is_present("profile-time") {
let num_seconds = value_t!(matches.value_of("profile-time"), u64).unwrap_or_else(|e| {
println!("{}", e);
std::process::exit(1)
});
if num_seconds < 1 {
println!("Profile time must be at least one second.");
std::process::exit(1);
}
self.profile_time = Some(Duration::from_secs(num_seconds));
}
self.test_mode = matches.is_present("test");
if matches.is_present("list") {
self.test_mode = true;
self.list_mode = true;
}
#[cfg(feature = "html_reports")]
{
if self.profile_time.is_none() {
reports.push(Box::new(Html::new()));
}
}
self.report = Box::new(Reports::new(reports));
self
}
fn filter_matches(&self, id: &str) -> bool {
match self.filter {
Some(ref string) => id.contains(string),
None => true,
}
}
/// Benchmarks a function
///
/// # Example
///
/// ```rust
/// # #[macro_use] extern crate criterion;
/// # use self::criterion::*;
///
/// fn bench(c: &mut Criterion) {
/// // Setup (construct data, allocate memory, etc)
/// c.bench_function(
/// "function_name",
/// |b| b.iter(|| {
/// // Code to benchmark goes here
/// }),
/// );
/// }
///
/// criterion_group!(benches, bench);
/// criterion_main!(benches);
/// ```
pub fn bench_function<F>(&mut self, id: &str, f: F) -> &mut Criterion
where
F: FnMut(&mut Bencher) + 'static,
{
self.bench(id, Benchmark::new(id, f))
}
/// Benchmarks multiple functions
///
/// All functions get the same input and are compared with the other implementations.
/// Works similar to `bench_function`, but with multiple functions.
///
/// # Example
///
/// ``` rust
/// # #[macro_use] extern crate criterion;
/// # use self::criterion::*;
/// # fn seq_fib(i: &u32) {}
/// # fn par_fib(i: &u32) {}
///
/// fn bench_seq_fib(b: &mut Bencher, i: &u32) {
/// b.iter(|| {
/// seq_fib(i);
/// });
/// }
///
/// fn bench_par_fib(b: &mut Bencher, i: &u32) {
/// b.iter(|| {
/// par_fib(i);
/// });
/// }
///
/// fn bench(c: &mut Criterion) {
/// let sequential_fib = Fun::new("Sequential", bench_seq_fib);
/// let parallel_fib = Fun::new("Parallel", bench_par_fib);
/// let funs = vec![sequential_fib, parallel_fib];
///
/// c.bench_functions("Fibonacci", funs, 14);
/// }
///
/// criterion_group!(benches, bench);
/// criterion_main!(benches);
/// ```
pub fn bench_functions<I>(&mut self, id: &str, funs: Vec<Fun<I>>, input: I) -> &mut Criterion
where
I: fmt::Debug + 'static,
{
let benchmark = ParameterizedBenchmark::with_functions(
funs.into_iter().map(|fun| fun.f).collect(),
vec![input],
);
self.bench(id, benchmark)
}
/// Benchmarks a function under various inputs
///
/// This is a convenience method to execute several related benchmarks. Each benchmark will
/// receive the id: `${id}/${input}`.
///
/// # Example
///
/// ```rust
/// # #[macro_use] extern crate criterion;
/// # use self::criterion::*;
///
/// fn bench(c: &mut Criterion) {
/// c.bench_function_over_inputs("from_elem",
/// |b: &mut Bencher, size: &usize| {
/// b.iter(|| vec![0u8; *size]);
/// },
/// vec![1024, 2048, 4096]
/// );
/// }
///
/// criterion_group!(benches, bench);
/// criterion_main!(benches);
/// ```
pub fn bench_function_over_inputs<I, F>(&mut self, id: &str, f: F, inputs: I) -> &mut Criterion
where
I: IntoIterator,
I::Item: fmt::Debug + 'static,
F: FnMut(&mut Bencher, &I::Item) + 'static,
{
self.bench(id, ParameterizedBenchmark::new(id, f, inputs))
}
/// Benchmarks an external program
///
/// The external program must:
///
/// * Read the number of iterations from stdin
/// * Execute the routine to benchmark that many times
/// * Print the elapsed time (in nanoseconds) to stdout
///
/// ```rust,no_run
/// # use std::io::{self, BufRead};
/// # use std::time::Instant;
/// # use std::time::Duration;
/// # trait DurationExt { fn to_nanos(&self) -> u64 { 0 } }
/// # impl DurationExt for Duration {}
/// // Example of an external program that implements this protocol
///
/// fn main() {
/// let stdin = io::stdin();
/// let ref mut stdin = stdin.lock();
///
/// // For each line in stdin
/// for line in stdin.lines() {
/// // Parse line as the number of iterations
/// let iters: u64 = line.unwrap().trim().parse().unwrap();
///
/// // Setup
///
/// // Benchmark
/// let start = Instant::now();
/// // Execute the routine "iters" times
/// for _ in 0..iters {
/// // Code to benchmark goes here
/// }
/// let elapsed = start.elapsed();
///
/// // Teardown
///
/// // Report elapsed time in nanoseconds to stdout
/// println!("{}", elapsed.to_nanos());
/// }
/// }
/// ```
#[deprecated(
since = "0.2.6",
note = "External program benchmarks were rarely used and are awkward to maintain, so they are scheduled for deletion in 0.3.0"
)]
#[allow(deprecated)]
pub fn bench_program(&mut self, id: &str, program: Command) -> &mut Criterion {
self.bench(id, Benchmark::new_external(id, program))
}
/// Benchmarks an external program under various inputs
///
/// This is a convenience method to execute several related benchmarks. Each benchmark will
/// receive the id: `${id}/${input}`.
#[deprecated(
since = "0.2.6",
note = "External program benchmarks were rarely used and are awkward to maintain, so they are scheduled for deletion in 0.3.0"
)]
#[allow(deprecated)]
pub fn bench_program_over_inputs<I, F>(
&mut self,
id: &str,
mut program: F,
inputs: I,
) -> &mut Criterion
where
F: FnMut() -> Command + 'static,
I: IntoIterator,
I::Item: fmt::Debug + 'static,
{
self.bench(
id,
ParameterizedBenchmark::new_external(
id,
move |i| {
let mut command = program();
command.arg(format!("{:?}", i));
command
},
inputs,
),
)
}
/// Executes the given benchmark. Use this variant to execute benchmarks
/// with complex configuration. This can be used to compare multiple
/// functions, execute benchmarks with custom configuration settings and
/// more. See the Benchmark and ParameterizedBenchmark structs for more
/// information.
///
/// ```rust
/// # #[macro_use] extern crate criterion;
/// # use criterion::*;
/// # fn routine_1() {}
/// # fn routine_2() {}
///
/// fn bench(c: &mut Criterion) {
/// // Setup (construct data, allocate memory, etc)
/// c.bench(
/// "routines",
/// Benchmark::new("routine_1", |b| b.iter(|| routine_1()))
/// .with_function("routine_2", |b| b.iter(|| routine_2()))
/// .sample_size(50)
/// );
/// }
///
/// criterion_group!(benches, bench);
/// criterion_main!(benches);
/// ```
pub fn bench<B: BenchmarkDefinition>(
&mut self,
group_id: &str,
benchmark: B,
) -> &mut Criterion {
benchmark.run(group_id, self);
self
}
}
mod plotting {
#[derive(Debug, Clone, Copy)]
pub enum Plotting {
Unset,
Disabled,
Enabled,
NotAvailable,
}
impl Plotting {
pub fn is_enabled(self) -> bool {
match self {
Plotting::Enabled => true,
_ => false,
}
}
}
}
trait DurationExt {
fn to_nanos(&self) -> u64;
}
const NANOS_PER_SEC: u64 = 1_000_000_000;
impl DurationExt for Duration {
fn to_nanos(&self) -> u64 {
self.as_secs() * NANOS_PER_SEC + u64::from(self.subsec_nanos())
}
}
#[derive(Clone, Copy, PartialEq, Deserialize, Serialize, Debug)]
struct ConfidenceInterval {
confidence_level: f64,
lower_bound: f64,
upper_bound: f64,
}
#[derive(Clone, Copy, PartialEq, Deserialize, Serialize, Debug)]
struct Estimate {
/// The confidence interval for this estimate
confidence_interval: ConfidenceInterval,
///
point_estimate: f64,
/// The standard error of this estimate
standard_error: f64,
}
fn build_estimates(
distributions: &Distributions,
points: &BTreeMap<Statistic, f64>,
cl: f64,
) -> Estimates {
distributions
.iter()
.map(|(&statistic, distribution)| {
let point_estimate = points[&statistic];
let (lb, ub) = distribution.confidence_interval(cl);
(
statistic,
Estimate {
confidence_interval: ConfidenceInterval {
confidence_level: cl,
lower_bound: lb,
upper_bound: ub,
},
point_estimate,
standard_error: distribution.std_dev(None),
},
)
})
.collect()
}
/// Enum representing different ways of measuring the throughput of benchmarked code.
/// If the throughput setting is configured for a benchmark then the estimated throughput will
/// be reported as well as the time per iteration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Throughput {
/// Measure throughput in terms of bytes/second. The value should be the number of bytes
/// processed by one iteration of the benchmarked code. Typically, this would be the length of
/// an input string or `&[u8]`.
Bytes(u32),
/// Measure throughput in terms of elements/second. The value should be the number of elements
/// processed by one iteration of the benchmarked code. Typically, this would be the size of a
/// collection, but could also be the number of lines of input text or the number of values to
/// parse.
Elements(u32),
}
/// Axis scaling type
#[derive(Debug, Clone, Copy)]
pub enum AxisScale {
/// Axes scale linearly
Linear,
/// Axes scale logarithmically
Logarithmic,
}
/// Contains the configuration options for the plots generated by a particular benchmark
/// or benchmark group.
///
/// ```rust
/// use self::criterion::{Bencher, Criterion, Benchmark, PlotConfiguration, AxisScale};
///
/// let plot_config = PlotConfiguration::default()
/// .summary_scale(AxisScale::Logarithmic);
///
/// Benchmark::new("test", |b| b.iter(|| 10))
/// .plot_config(plot_config);
/// ```
#[derive(Debug, Clone)]
pub struct PlotConfiguration {
summary_scale: AxisScale,
}
impl Default for PlotConfiguration {
fn default() -> PlotConfiguration {
PlotConfiguration {
summary_scale: AxisScale::Linear,
}
}
}
impl PlotConfiguration {
/// Set the axis scale (linear or logarithmic) for the summary plots. Typically, you would
/// set this to logarithmic if benchmarking over a range of inputs which scale exponentially.
/// Defaults to linear.
pub fn summary_scale(mut self, new_scale: AxisScale) -> PlotConfiguration {
self.summary_scale = new_scale;
self
}
}