Files
hyperfine/src/internal.rs
T

255 lines
7.5 KiB
Rust
Raw Normal View History

use colored::*;
2018-01-14 20:14:10 +01:00
use indicatif::{ProgressBar, ProgressStyle};
2021-08-24 21:24:34 +02:00
use crate::benchmark_result::BenchmarkResult;
use crate::options::OutputStyleOption;
use crate::units::{Scalar, Second};
use std::cmp::Ordering;
use std::iter::Iterator;
2018-01-14 20:14:10 +01:00
/// Threshold for warning about fast execution time
pub const MIN_EXECUTION_TIME: Second = 5e-3;
2020-01-04 11:06:34 +01:00
#[cfg(not(windows))]
const TICK_SETTINGS: (&str, u64) = ("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ ", 80);
#[cfg(windows)]
const TICK_SETTINGS: (&str, u64) = (r"+-x| ", 200);
2018-01-14 20:14:10 +01:00
/// Return a pre-configured progress bar
2019-07-13 10:38:51 +02:00
pub fn get_progress_bar(length: u64, msg: &str, option: OutputStyleOption) -> ProgressBar {
let progressbar_style = match option {
OutputStyleOption::Basic | OutputStyleOption::Color => ProgressStyle::default_bar(),
2018-02-13 21:00:22 -05:00
_ => ProgressStyle::default_spinner()
2020-01-04 11:06:34 +01:00
.tick_chars(TICK_SETTINGS.0)
2018-09-28 21:19:54 +02:00
.template(" {spinner} {msg:<30} {wide_bar} ETA {eta_precise}"),
};
2018-01-14 20:14:10 +01:00
2019-07-13 10:38:51 +02:00
let progress_bar = match option {
OutputStyleOption::Basic | OutputStyleOption::Color => ProgressBar::hidden(),
2018-02-13 21:00:22 -05:00
_ => ProgressBar::new(length),
};
2021-06-11 17:44:11 -04:00
progress_bar.set_style(progressbar_style);
2020-01-04 11:06:34 +01:00
progress_bar.enable_steady_tick(TICK_SETTINGS.1);
2021-05-09 12:10:08 +02:00
progress_bar.set_message(msg.to_owned());
2018-01-14 20:14:10 +01:00
2018-01-18 20:16:36 +01:00
progress_bar
2018-01-14 20:14:10 +01:00
}
2018-01-14 21:43:29 +01:00
/// A max function for f64's without NaNs
pub fn max(vals: &[f64]) -> f64 {
2018-05-22 22:57:51 +02:00
*vals
.iter()
2018-01-14 21:43:29 +01:00
.max_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap()
}
/// A min function for f64's without NaNs
pub fn min(vals: &[f64]) -> f64 {
2018-05-22 22:57:51 +02:00
*vals
.iter()
2018-01-14 21:43:29 +01:00
.min_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap()
}
2018-01-18 18:58:51 +01:00
2020-10-16 10:47:26 +02:00
#[derive(Debug)]
2019-06-08 11:47:50 +02:00
pub struct BenchmarkResultWithRelativeSpeed<'a> {
2019-06-08 12:04:26 +02:00
pub result: &'a BenchmarkResult,
pub relative_speed: Scalar,
pub relative_speed_stddev: Scalar,
pub is_fastest: bool,
2019-06-08 11:47:50 +02:00
}
fn compare_mean_time(l: &BenchmarkResult, r: &BenchmarkResult) -> Ordering {
l.mean.partial_cmp(&r.mean).unwrap_or(Ordering::Equal)
}
2021-08-22 15:30:13 +02:00
pub fn compute_relative_speed(
results: &[BenchmarkResult],
2021-06-11 17:44:11 -04:00
) -> Option<Vec<BenchmarkResultWithRelativeSpeed>> {
2019-06-08 11:47:50 +02:00
let fastest: &BenchmarkResult = results
.iter()
.min_by(|&l, &r| compare_mean_time(l, r))
.expect("at least one benchmark result");
2020-10-16 10:47:26 +02:00
if fastest.mean == 0.0 {
return None;
}
2019-06-08 11:47:50 +02:00
2020-10-16 10:47:26 +02:00
Some(
results
.iter()
.map(|result| {
let ratio = result.mean / fastest.mean;
// https://en.wikipedia.org/wiki/Propagation_of_uncertainty#Example_formulas
// Covariance asssumed to be 0, i.e. variables are assumed to be independent
let ratio_stddev = ratio
* ((result.stddev / result.mean).powi(2)
+ (fastest.stddev / fastest.mean).powi(2))
2019-06-08 12:04:26 +02:00
.sqrt();
2019-06-08 11:47:50 +02:00
2020-10-16 10:47:26 +02:00
BenchmarkResultWithRelativeSpeed {
result,
relative_speed: ratio,
relative_speed_stddev: ratio_stddev,
is_fastest: result == fastest,
}
})
.collect(),
)
2019-06-08 11:47:50 +02:00
}
2019-07-13 10:29:05 +02:00
pub fn write_benchmark_comparison(results: &[BenchmarkResult]) {
if results.len() < 2 {
return;
}
2021-08-22 15:59:56 +02:00
if let Some(mut annotated_results) = compute_relative_speed(results) {
2020-10-16 10:47:26 +02:00
annotated_results.sort_by(|l, r| compare_mean_time(l.result, r.result));
2020-10-16 10:47:26 +02:00
let fastest = &annotated_results[0];
let others = &annotated_results[1..];
2020-10-16 10:47:26 +02:00
println!("{}", "Summary".bold());
println!(" '{}' ran", fastest.result.command.cyan());
2020-10-16 10:47:26 +02:00
for item in others {
println!(
"{} ± {} times faster than '{}'",
format!("{:8.2}", item.relative_speed).bold().green(),
format!("{:.2}", item.relative_speed_stddev).green(),
&item.result.command.magenta()
);
}
} else {
eprintln!(
"{}: The benchmark comparison could not be computed as some benchmark times are zero. \
This could be caused by background interference during the initial calibration phase \
of hyperfine, in combination with very fast commands (faster than a few milliseconds). \
Try to re-run the benchmark on a quiet system. If it does not help, you command is \
most likely too fast to be accurately benchmarked by hyperfine.",
"Note".bold().red()
);
}
}
2018-01-18 18:58:51 +01:00
#[test]
fn test_max() {
2021-07-27 09:10:27 +02:00
let assert_float_eq = |a, b| {
assert!((a - b) < f64::EPSILON);
};
assert_float_eq(1.0, max(&[1.0]));
assert_float_eq(-1.0, max(&[-1.0]));
assert_float_eq(-1.0, max(&[-2.0, -1.0]));
assert_float_eq(1.0, max(&[-1.0, 1.0]));
assert_float_eq(1.0, max(&[-1.0, 1.0, 0.0]));
2018-01-18 18:58:51 +01:00
}
2019-06-08 11:47:50 +02:00
2020-10-16 10:47:26 +02:00
#[cfg(test)]
fn create_result(name: &str, mean: Scalar) -> BenchmarkResult {
use std::collections::BTreeMap;
2019-06-08 11:47:50 +02:00
2020-10-16 10:47:26 +02:00
BenchmarkResult {
2019-06-08 12:04:26 +02:00
command: name.into(),
2019-07-13 10:06:25 +02:00
mean,
2019-06-08 12:04:26 +02:00
stddev: 1.0,
median: mean,
2019-06-08 12:04:26 +02:00
user: mean,
system: 0.0,
min: mean,
max: mean,
times: None,
2021-02-28 10:22:21 +01:00
exit_codes: Vec::new(),
parameters: BTreeMap::new(),
2020-10-16 10:47:26 +02:00
}
}
#[test]
fn test_compute_relative_speed() {
use approx::assert_relative_eq;
2019-06-08 11:47:50 +02:00
let results = vec![
create_result("cmd1", 3.0),
create_result("cmd2", 2.0),
create_result("cmd3", 5.0),
];
2020-10-16 10:47:26 +02:00
let annotated_results = compute_relative_speed(&results).unwrap();
2019-06-08 11:47:50 +02:00
assert_relative_eq!(1.5, annotated_results[0].relative_speed);
assert_relative_eq!(1.0, annotated_results[1].relative_speed);
assert_relative_eq!(2.5, annotated_results[2].relative_speed);
}
2019-10-20 13:18:00 +02:00
2020-10-16 10:47:26 +02:00
#[test]
fn test_compute_relative_speed_for_zero_times() {
let results = vec![create_result("cmd1", 1.0), create_result("cmd2", 0.0)];
let annotated_results = compute_relative_speed(&results);
assert!(annotated_results.is_none());
}
2021-06-11 17:44:11 -04:00
pub fn tokenize(values: &str) -> Vec<String> {
2019-10-20 13:18:00 +02:00
let mut tokens = vec![];
let mut buf = String::new();
let mut iter = values.chars();
while let Some(c) = iter.next() {
match c {
'\\' => match iter.next() {
Some(c2 @ ',') | Some(c2 @ '\\') => {
buf.push(c2);
}
Some(c2) => {
buf.push('\\');
buf.push(c2);
}
None => buf.push('\\'),
},
',' => {
tokens.push(buf);
buf = String::new();
}
_ => {
buf.push(c);
}
};
}
tokens.push(buf);
tokens
}
#[test]
fn test_tokenize_single_value() {
2019-10-20 13:20:08 +02:00
assert_eq!(tokenize(r""), vec![""]);
2019-10-20 13:18:00 +02:00
assert_eq!(tokenize(r"foo"), vec!["foo"]);
assert_eq!(tokenize(r" "), vec![" "]);
assert_eq!(tokenize(r"hello\, world!"), vec!["hello, world!"]);
assert_eq!(tokenize(r"\,"), vec![","]);
assert_eq!(tokenize(r"\,\,\,"), vec![",,,"]);
assert_eq!(tokenize(r"\n"), vec![r"\n"]);
assert_eq!(tokenize(r"\\"), vec![r"\"]);
assert_eq!(tokenize(r"\\\,"), vec![r"\,"]);
}
#[test]
fn test_tokenize_multiple_values() {
assert_eq!(tokenize(r"foo,bar,baz"), vec!["foo", "bar", "baz"]);
assert_eq!(tokenize(r"hello world,foo"), vec!["hello world", "foo"]);
assert_eq!(tokenize(r"hello\,world!,baz"), vec!["hello,world!", "baz"]);
}
#[test]
fn test_tokenize_empty_values() {
assert_eq!(tokenize(r"foo,,bar"), vec!["foo", "", "bar"]);
assert_eq!(tokenize(r",bar"), vec!["", "bar"]);
assert_eq!(tokenize(r"bar,"), vec!["bar", ""]);
assert_eq!(tokenize(r",,"), vec!["", "", ""]);
}