#![deny(missing_docs)]
#![cfg_attr(feature = "real_blackbox", feature(test))]
#[macro_use]
extern crate log;
extern crate failure;
extern crate itertools;
extern crate serde;
extern crate serde_json;
extern crate criterion_plot as simplot;
extern crate criterion_stats as stats;
extern crate simplelog;
extern crate isatty;
extern crate clap;
#[cfg(feature = "real_blackbox")] extern crate test;
#[macro_use]
extern crate failure_derive;
#[macro_use]
extern crate serde_derive;
#[macro_use]
mod macros_private;
mod analysis;
mod error;
mod estimate;
mod format;
mod fs;
mod kde;
mod plot;
mod program;
mod report;
mod routine;
mod macros;
use std::default::Default;
use std::iter::IntoIterator;
use std::process::Command;
use std::time::{Duration, Instant};
use std::{fmt, mem};
use std::fs::File;
use std::io::Read;
use std::path::Path;
use estimate::{Distributions, Estimates};
use report::{Report, CliReport};
fn debug_enabled() -> bool {
std::env::vars().any(|(key, _)| key == "CRITERION_DEBUG")
}
pub fn init_logging() {
use simplelog::*;
let filter = if debug_enabled() { LogLevelFilter::max() } else { LogLevelFilter::Warn };
SimpleLogger::init(
filter,
Config::default(),
).unwrap();
}
#[cfg(feature = "real_blackbox")]
pub fn black_box<T>(dummy: T) -> T {
test::black_box(dummy)
}
#[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
}
}
pub struct Fun<I: fmt::Display> {
n: String,
f: Box<FnMut(&mut Bencher, &I)>,
}
impl<I> Fun<I> where I: fmt::Display {
pub fn new<F>(name: &str, f: F) -> Fun<I>
where F: FnMut(&mut Bencher, &I) + 'static
{
Fun {
n: name.to_owned(),
f: Box::new(f),
}
}
}
#[derive(Clone, Copy)]
pub struct Bencher {
iters: u64,
elapsed: Duration,
}
impl Bencher {
pub fn iter<O, R>(&mut self, mut routine: R) where
R: FnMut() -> O,
{
let start = Instant::now();
for _ in 0..self.iters {
black_box(routine());
}
self.elapsed = start.elapsed();
}
pub fn iter_with_setup<I, O, S, R>(&mut self, mut setup: S, mut routine: R)
where S: FnMut() -> I,
R: FnMut(I) -> O
{
self.elapsed = Duration::from_secs(0);
for _ in 0..self.iters {
let input = setup();
let start = Instant::now();
let output = black_box(routine(black_box(input)));
let elapsed = start.elapsed();
mem::drop(output);
self.elapsed += elapsed;
}
}
pub fn iter_with_large_drop<O, R>(&mut self, mut routine: R)
where R: FnMut() -> O
{
let mut outputs = Vec::with_capacity(self.iters as usize);
let start = Instant::now();
for _ in 0..self.iters {
outputs.push(black_box(routine()));
}
self.elapsed = start.elapsed();
mem::drop(outputs);
}
pub fn iter_with_large_setup<I, S, R>(&mut self, mut setup: S, mut routine: R)
where S: FnMut() -> I,
R: FnMut(I)
{
let inputs = (0..self.iters).map(|_| setup()).collect::<Vec<_>>();
let start = Instant::now();
for input in inputs {
routine(black_box(input));
}
self.elapsed = start.elapsed();
}
}
pub struct Criterion {
confidence_level: f64,
measurement_time: Duration,
noise_threshold: f64,
nresamples: usize,
plotting: Plotting,
sample_size: usize,
significance_level: f64,
warm_up_time: Duration,
filter: Option<String>,
report: Box<Report>,
}
impl Default for Criterion {
fn default() -> Criterion {
let plotting = if simplot::version().is_ok() {
Plotting::Enabled
} else {
println!("Gnuplot not found, disabling plotting");
Plotting::NotAvailable
};
Criterion {
confidence_level: 0.95,
measurement_time: Duration::new(5, 0),
noise_threshold: 0.01,
nresamples: 100_000,
sample_size: 100,
plotting: plotting,
significance_level: 0.05,
warm_up_time: Duration::new(3, 0),
filter: None,
report: Box::new(CliReport::new(false, false, false)),
}
}
}
impl Criterion {
pub fn sample_size(&mut self, n: usize) -> &mut Criterion {
assert!(n > 0);
self.sample_size = n;
self
}
pub fn warm_up_time(&mut self, dur: Duration) -> &mut Criterion {
assert!(dur.to_nanos() > 0);
self.warm_up_time = dur;
self
}
pub fn measurement_time(&mut self, dur: Duration) -> &mut Criterion {
assert!(dur.to_nanos() > 0);
self.measurement_time = dur;
self
}
pub fn nresamples(&mut self, n: usize) -> &mut Criterion {
assert!(n > 0);
self.nresamples = n;
self
}
pub fn noise_threshold(&mut self, threshold: f64) -> &mut Criterion {
assert!(threshold >= 0.0);
self.noise_threshold = threshold;
self
}
pub fn confidence_level(&mut self, cl: f64) -> &mut Criterion {
assert!(cl > 0.0 && cl < 1.0);
self.confidence_level = cl;
self
}
pub fn significance_level(&mut self, sl: f64) -> &mut Criterion {
assert!(sl > 0.0 && sl < 1.0);
self.significance_level = sl;
self
}
pub fn with_plots(&mut self) -> &mut Criterion {
match self.plotting {
Plotting::NotAvailable => {},
_ => self.plotting = Plotting::Enabled,
}
self
}
pub fn without_plots(&mut self) -> &mut Criterion {
match self.plotting {
Plotting::NotAvailable => {},
_ => self.plotting = Plotting::Disabled,
}
self
}
pub fn can_plot(&self) -> bool {
match self.plotting {
Plotting::NotAvailable => false,
_ => true,
}
}
pub fn with_filter<S: Into<String>>(&mut self, filter: S) -> &mut Criterion {
self.filter = Some(filter.into());
self
}
pub fn configure_from_args(&mut self) {
use clap::{Arg, App};
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("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/japaric/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.with_filter(filter);
}
let verbose = matches.is_present("verbose");
let mut enable_text_overwrite =
isatty::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 = cfg!(unix) && isatty::stdout_isatty()
}
}
self.report = Box::new(CliReport::new(
enable_text_overwrite, enable_text_coloring, verbose))
}
fn filter_matches(&self, id: &str) -> bool {
match &self.filter {
&Some(ref string) => id.contains(string),
&None => true,
}
}
pub fn bench_function<F>(&mut self, id: &str, f: F) -> &mut Criterion where
F: FnMut(&mut Bencher),
{
if self.filter_matches(id) {
analysis::function(id, f, self);
}
self
}
pub fn bench_functions<I>(&mut self,
id: &str,
funs: Vec<Fun<I>>,
input: &I) -> &mut Criterion
where I: fmt::Display
{
if self.filter_matches(id) {
analysis::functions(id, funs, input, self);
}
self
}
pub fn bench_function_over_inputs<I, F>(
&mut self,
id: &str,
f: F,
inputs: I,
) -> &mut Criterion where
I: IntoIterator,
I::Item: fmt::Display,
F: FnMut(&mut Bencher, &I::Item),
{
if self.filter_matches(id) {
analysis::function_over_inputs(id, f, inputs, self);
}
self
}
pub fn bench_program(&mut self, id: &str, mut program: Command) -> &mut Criterion {
if self.filter_matches(id) {
analysis::program(id, &mut program, self);
}
self
}
pub fn bench_program_over_inputs<I, F>(
&mut self,
id: &str,
program: F,
inputs: I,
) -> &mut Criterion where
F: FnMut() -> Command,
I: IntoIterator,
I::Item: fmt::Display,
{
if self.filter_matches(id) {
analysis::program_over_inputs(id, program, inputs, self);
}
self
}
pub fn summarize(&mut self, id: &str) -> &mut Criterion {
analysis::summarize(id, self);
self
}
}
enum Plotting {
Disabled,
Enabled,
NotAvailable,
}
impl Plotting {
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())
}
}
#[doc(hidden)]
#[derive(Clone, Copy, PartialEq, Deserialize, Serialize)]
pub struct ConfidenceInterval {
confidence_level: f64,
lower_bound: f64,
upper_bound: f64,
}
#[doc(hidden)]
#[derive(Clone, Copy, PartialEq, Deserialize, Serialize)]
pub struct Estimate {
pub confidence_interval: ConfidenceInterval,
pub point_estimate: f64,
pub standard_error: f64,
}
impl Estimate {
fn new(distributions: &Distributions, points: &[f64], cl: f64) -> Estimates {
distributions.iter().zip(points.iter()).map(|((&statistic, distribution), &point)| {
let (lb, ub) = distribution.confidence_interval(cl);
(statistic, Estimate {
confidence_interval: ConfidenceInterval {
confidence_level: cl,
lower_bound: lb,
upper_bound: ub,
},
point_estimate: point,
standard_error: distribution.std_dev(None),
})
}).collect()
}
fn load(path: &Path) -> Option<Estimates> {
let mut string = String::new();
match File::open(path) {
Err(_) => None,
Ok(mut f) => match f.read_to_string(&mut string) {
Err(_) => None,
Ok(_) => match serde_json::from_str(&string) {
Err(_) => None,
Ok(estimates) => Some(estimates),
},
}
}
}
}