#![deny(missing_docs)]
#![feature(test)]
#[macro_use]
extern crate log;
extern crate itertools;
extern crate rustc_serialize;
extern crate criterion_plot as simplot;
extern crate criterion_stats as stats;
extern crate test;
mod analysis;
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 rustc_serialize::json;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use estimate::{Distributions, Estimates};
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 {
test::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 = test::black_box(routine(test::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(test::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(test::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>,
}
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,
}
}
}
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) {
if let Some(arg) = ::std::env::args().skip(1).find(|arg| *arg != "--bench") {
self.with_filter(arg);
}
}
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, RustcDecodable, RustcEncodable)]
pub struct ConfidenceInterval {
confidence_level: f64,
lower_bound: f64,
upper_bound: f64,
}
#[doc(hidden)]
#[derive(Clone, Copy, PartialEq, RustcDecodable, RustcEncodable)]
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 json::decode(&string) {
Err(_) => None,
Ok(estimates) => Some(estimates),
},
}
}
}
}