From bffd476128807a9532c85d3fccef3f5805e97491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Thu, 11 Nov 2021 14:10:58 +0100 Subject: [PATCH] Add support for one run with --runs=1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the --runs=N option added in d78c33b (Added options to specify the max/exact numbers of runs., 2018-09-09) to support --runs=1 instead of dying with a usage error. This is useful to do ad-hoc testing of your commands while they might still have syntax errors, or just for doing one run where you don't care about the stddev. Before: $ /usr/bin/hyperfine -s basic -r 1 -L n 5,10 'sleep 0.{n}' Error: Number of runs below two After (-s basic) also works: $ hyperfine -r 1 -L n 5,10 'sleep 0.{n}' Benchmark 1: sleep 0.5 Time (abs ≡): 500.6 ms [User: 0.6 ms, System: 0.0 ms] Benchmark 2: sleep 0.10 Time (abs ≡): 100.8 ms [User: 0.7 ms, System: 0.0 ms] Summary 'sleep 0.10' ran 4.97 ± 0.00 times faster than 'sleep 0.5' This likewise combines correctly with -m and -M, probably not very useful, but if you're tweaking an existing command-line: $ hyperfine -m 1 -M 1 -L n 5,10 'sleep 0.{n}' Benchmark 1: sleep 0.5 Time (abs ≡): 500.6 ms [User: 0.5 ms, System: 0.0 ms] Benchmark 2: sleep 0.10 Time (abs ≡): 100.6 ms [User: 0.6 ms, System: 0.0 ms] Summary 'sleep 0.10' ran 4.98 ± 0.00 times faster than 'sleep 0.5' The "± 0.00" output in "faster than" should probably be adjusted too, or we could keep it for consistency. I didn't implement that because this is the first time I do anything in Rust, and I ran out of template to copy when wanting to quickly implement this in write_benchmark_comparison() in main.rs. --- src/benchmark.rs | 16 ++++++++++++++-- src/error.rs | 2 -- src/main.rs | 8 -------- tests/integration_tests.rs | 15 +++++++++++++-- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/benchmark.rs b/src/benchmark.rs index 5a05269..7f0d1e9 100644 --- a/src/benchmark.rs +++ b/src/benchmark.rs @@ -391,7 +391,10 @@ pub fn run_benchmark( // Compute statistical quantities let t_num = times_real.len(); let t_mean = mean(×_real); - let t_stddev = standard_deviation(×_real, Some(t_mean)); + let mut t_stddev = 0.0; + if times_real.len() != 1 { + t_stddev = standard_deviation(×_real, Some(t_mean)); + } let t_median = median(×_real); let t_min = min(×_real); let t_max = max(×_real); @@ -409,7 +412,16 @@ pub fn run_benchmark( let user_str = format_duration(user_mean, Some(time_unit)); let system_str = format_duration(system_mean, Some(time_unit)); - if options.output_style != OutputStyleOption::Disabled { + if options.output_style != OutputStyleOption::Disabled && times_real.len() == 1 { + println!( + " Time ({} ≡): {:>8} {:>8} [User: {}, System: {}]", + "abs".green().bold(), + mean_str.green().bold(), + " ".to_string(), // alignment + user_str.blue(), + system_str.blue() + ); + } else if options.output_style != OutputStyleOption::Disabled { println!( " Time ({} ± {}): {:>8} ± {:>8} [User: {}, System: {}]", "mean".green().bold(), diff --git a/src/error.rs b/src/error.rs index 298b310..8e892db 100644 --- a/src/error.rs +++ b/src/error.rs @@ -57,7 +57,6 @@ impl Error for ParameterScanError {} #[derive(Debug)] pub enum OptionsError<'a> { - RunsBelowTwo, EmptyRunsRange, TooManyCommandNames(usize), UnexpectedCommandNameCount(usize, usize), @@ -70,7 +69,6 @@ impl<'a> fmt::Display for OptionsError<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { OptionsError::EmptyRunsRange => write!(f, "Empty runs range"), - OptionsError::RunsBelowTwo => write!(f, "Number of runs below two"), OptionsError::TooManyCommandNames(n) => { write!(f, "Too many --command-name options: expected {} at most", n) } diff --git a/src/main.rs b/src/main.rs index b0521fb..63c87bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -171,17 +171,9 @@ fn build_hyperfine_options<'a>( } 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); diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 8328f06..ae502fc 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -23,12 +23,12 @@ fn hyperfine_runs_successfully() { } #[test] -fn at_least_two_runs_are_required() { +fn one_run_is_supported() { hyperfine() .arg("--runs=1") .arg("echo dummy benchmark") .assert() - .failure(); + .success(); } struct ExecutionOrderTest { @@ -109,6 +109,17 @@ impl Default for ExecutionOrderTest { } } +#[test] +fn benchmarks_are_executed_sequentially_one() { + ExecutionOrderTest::new() + .arg("--runs=1") + .command("command 1") + .command("command 2") + .expect_output("command 1") + .expect_output("command 2") + .run(); +} + #[test] fn benchmarks_are_executed_sequentially() { ExecutionOrderTest::new()