diff --git a/src/hyperfine/app.rs b/src/hyperfine/app.rs index 8b94365..f7aa44a 100644 --- a/src/hyperfine/app.rs +++ b/src/hyperfine/app.rs @@ -54,6 +54,22 @@ fn build_app() -> App<'static, 'static> { .value_name("NUM") .help("Perform at least NUM runs for each command (default: 10)."), ) + .arg( + Arg::with_name("max-runs") + .long("max-runs") + .short("M") + .takes_value(true) + .value_name("NUM") + .help("Perform at most NUM runs for each command."), + ) + .arg( + Arg::with_name("runs") + .long("runs") + .short("r") + .takes_value(true) + .value_name("NUM") + .help("Perform exactly NUM runs for each command."), + ) .arg( Arg::with_name("prepare") .long("prepare") diff --git a/src/hyperfine/benchmark.rs b/src/hyperfine/benchmark.rs index c690f43..332b7ad 100644 --- a/src/hyperfine/benchmark.rs +++ b/src/hyperfine/benchmark.rs @@ -1,3 +1,4 @@ +use std::cmp; use std::io; use std::process::Stdio; @@ -187,7 +188,7 @@ pub fn run_benchmark( // Set up progress bar (and spinner for initial measurement) let progress_bar = get_progress_bar( - options.min_runs, + options.runs.min, "Initial time measurement", &options.output_style, ); @@ -215,11 +216,10 @@ pub fn run_benchmark( / (res.time_real + prepare_res.time_real + shell_spawning_time.time_real)) as u64; - let count = if runs_in_min_time >= options.min_runs { - runs_in_min_time - } else { - options.min_runs - }; + let count = cmp::min( + cmp::max(runs_in_min_time, options.runs.min), + options.runs.max + ); let count_remaining = count - 1; diff --git a/src/hyperfine/error.rs b/src/hyperfine/error.rs index b66221f..033cc4f 100644 --- a/src/hyperfine/error.rs +++ b/src/hyperfine/error.rs @@ -9,16 +9,6 @@ pub enum ParameterScanError { TooLarge, } -impl ParameterScanError { - fn __description(&self) -> &str { - match *self { - ParameterScanError::ParseIntError(ref e) => e.description(), - ParameterScanError::EmptyRange => "Empty parameter range", - ParameterScanError::TooLarge => "Parameter range is too large", - } - } -} - impl From for ParameterScanError { fn from(e: num::ParseIntError) -> ParameterScanError { ParameterScanError::ParseIntError(e) @@ -27,12 +17,37 @@ impl From for ParameterScanError { impl fmt::Display for ParameterScanError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.__description()) + write!(f, "{}", self.description()) } } impl Error for ParameterScanError { fn description(&self) -> &str { - self.__description() + match *self { + ParameterScanError::ParseIntError(ref e) => e.description(), + ParameterScanError::EmptyRange => "Empty parameter range", + ParameterScanError::TooLarge => "Parameter range is too large", + } + } +} + +#[derive(Debug)] +pub enum OptionsError { + RunsBelowTwo, + EmptyRunsRange, +} + +impl fmt::Display for OptionsError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.description()) + } +} + +impl Error for OptionsError { + fn description(&self) -> &str { + match *self { + OptionsError::EmptyRunsRange => "Empty runs range", + OptionsError::RunsBelowTwo => "Number of runs below two", + } } } diff --git a/src/hyperfine/types.rs b/src/hyperfine/types.rs index 23baf22..690507c 100644 --- a/src/hyperfine/types.rs +++ b/src/hyperfine/types.rs @@ -73,13 +73,31 @@ pub enum OutputStyleOption { NoColor, } +/// Number of runs for a benchmark +pub struct Runs { + /// Minimum number of benchmark runs + pub min: u64, + + /// Maximum number of benchmark runs + pub max: u64, +} + +impl Default for Runs { + fn default() -> Runs { + Runs { + min: 10, + max: ::std::u64::MAX, + } + } +} + /// A set of options for hyperfine pub struct HyperfineOptions { /// Number of warmup runs pub warmup_count: u64, - /// Minimum number of benchmark runs - pub min_runs: u64, + /// Number of benchmark runs + pub runs: Runs, /// Minimum benchmarking time pub min_time_sec: Second, @@ -101,7 +119,7 @@ impl Default for HyperfineOptions { fn default() -> HyperfineOptions { HyperfineOptions { warmup_count: 0, - min_runs: 10, + runs: Runs::default(), min_time_sec: 3.0, failure_action: CmdFailureAction::RaiseError, preparation_command: None, diff --git a/src/main.rs b/src/main.rs index f254a66..189fe6e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,7 +42,7 @@ mod hyperfine; use hyperfine::app::get_arg_matches; use hyperfine::benchmark::{mean_shell_spawning_time, run_benchmark}; -use hyperfine::error::ParameterScanError; +use hyperfine::error::{ParameterScanError, OptionsError}; use hyperfine::export::{ExportManager, ExportType}; use hyperfine::internal::write_benchmark_comparison; use hyperfine::types::{ @@ -95,7 +95,10 @@ fn main() { let export_manager = build_export_manager(&matches); let commands = build_commands(&matches); - let res = run(&commands, &options); + let res = match options { + Ok(opts) => run(&commands, &opts), + Err(e) => error(e.description()), + }; match res { Ok(timing_results) => { @@ -113,7 +116,7 @@ fn main() { } /// Build the HyperfineOptions that correspond to the given ArgMatches -fn build_hyperfine_options(matches: &ArgMatches) -> HyperfineOptions { +fn build_hyperfine_options(matches: &ArgMatches) -> Result { let mut options = HyperfineOptions::default(); let str_to_u64 = |n| u64::from_str_radix(n, 10).ok(); @@ -122,11 +125,41 @@ fn build_hyperfine_options(matches: &ArgMatches) -> HyperfineOptions { .and_then(&str_to_u64) .unwrap_or(0); - if let Some(min_runs) = matches.value_of("min-runs").and_then(&str_to_u64) { - // we need at least two runs to compute a variance - options.min_runs = cmp::max(2, min_runs); + let mut min_runs = matches.value_of("min-runs").and_then(&str_to_u64); + let mut max_runs = matches.value_of("max-runs").and_then(&str_to_u64); + + if let Some(runs) = matches.value_of("runs").and_then(&str_to_u64) { + min_runs = Some(runs); + max_runs = Some(runs); } + match (min_runs, max_runs) { + (Some(min), _) if min < 2 => { + // We need at least two runs to compute a variance. + return Err(OptionsError::RunsBelowTwo); + } + (Some(min), None) => { + options.runs.min = min; + } + (_, Some(max)) if max < 2 => { + // We need at least two runs to compute a variance. + return Err(OptionsError::RunsBelowTwo); + } + (None, Some(max)) => { + // Since the minimum was not explicit we lower it if max is below the default min. + options.runs.min = cmp::min(options.runs.min, max); + options.runs.max = max; + } + (Some(min), Some(max)) if min > max => { + return Err(OptionsError::EmptyRunsRange); + } + (Some(min), Some(max)) => { + options.runs.min = min; + options.runs.max = max; + } + (None, None) => {} + }; + options.preparation_command = matches.value_of("prepare").map(String::from); options.show_output = matches.is_present("show-output"); @@ -155,7 +188,7 @@ fn build_hyperfine_options(matches: &ArgMatches) -> HyperfineOptions { options.failure_action = CmdFailureAction::Ignore; } - options + Ok(options) } /// Build the ExportManager that will export the results specified