mirror of
https://github.com/sharkdp/hyperfine.git
synced 2026-11-14 00:17:37 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aba7bdec07 | ||
|
|
1f5a2ff70c | ||
|
|
c0b3834277 | ||
|
|
4d073e08ae |
No files matched your search
@@ -79,6 +79,7 @@ jobs:
|
||||
matrix:
|
||||
job:
|
||||
- { target: aarch64-unknown-linux-gnu , os: ubuntu-24.04, use-cross: true }
|
||||
- { target: aarch64-unknown-linux-musl , os: ubuntu-24.04, use-cross: true }
|
||||
- { target: arm-unknown-linux-gnueabihf , os: ubuntu-24.04, use-cross: true }
|
||||
- { target: arm-unknown-linux-musleabihf, os: ubuntu-24.04, use-cross: true }
|
||||
- { target: i686-pc-windows-msvc , os: windows-2022 }
|
||||
|
||||
+1
-1
@@ -247,7 +247,7 @@ Package authors: note that Hyperfine now comes with a set of shell completion fi
|
||||
necessarily required numeric arguments.
|
||||
|
||||
``` bash
|
||||
hyperfine --parameter-list compiler "gcc,clang" \
|
||||
hyperfine --parameter-list compiler "g++,clang++" \
|
||||
"{compiler} -O2 main.cpp"
|
||||
```
|
||||
|
||||
|
||||
Generated
+295
-464
File diff suppressed because it is too large.
Load diff
+15
-14
@@ -10,22 +10,23 @@ repository = "https://github.com/sharkdp/hyperfine"
|
||||
version = "1.20.0"
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
rust-version = "1.88.0"
|
||||
rust-version = "1.97.0"
|
||||
|
||||
[features]
|
||||
# Use the nightly feature windows_process_extensions_main_thread_handle
|
||||
windows_process_extensions_main_thread_handle = []
|
||||
|
||||
[dependencies]
|
||||
colored = "2.1"
|
||||
colored = "3.1"
|
||||
# Newer versions regress ETA estimates: https://github.com/console-rs/indicatif/issues/580
|
||||
indicatif = "=0.17.4"
|
||||
statistical = "1.0"
|
||||
csv = "1.3"
|
||||
csv = "1.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
rust_decimal = "1.36"
|
||||
rand = "0.8"
|
||||
shell-words = "1.0"
|
||||
rust_decimal = "1.43"
|
||||
rand = "0.10"
|
||||
shell-words = "1.1"
|
||||
thiserror = "2.0"
|
||||
anyhow = "1.0"
|
||||
|
||||
@@ -33,7 +34,7 @@ anyhow = "1.0"
|
||||
libc = "0.2"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.59", features = [
|
||||
windows-sys = { version = "0.61", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Security",
|
||||
"Win32_System_JobObjects",
|
||||
@@ -42,10 +43,10 @@ windows-sys = { version = "0.59", features = [
|
||||
] }
|
||||
|
||||
[target.'cfg(all(windows, not(windows_process_extensions_main_thread_handle)))'.dependencies]
|
||||
once_cell = "1.19"
|
||||
once_cell = "1.21"
|
||||
|
||||
[target.'cfg(target_os="linux")'.dependencies]
|
||||
nix = { version = "0.29", features = ["zerocopy"] }
|
||||
nix = { version = "0.31", features = ["zerocopy"] }
|
||||
|
||||
[dependencies.clap]
|
||||
version = "4"
|
||||
@@ -62,18 +63,18 @@ features = [
|
||||
|
||||
[dev-dependencies]
|
||||
approx = "0.5"
|
||||
assert_cmd = "2.0"
|
||||
insta = { version = "1.41.1", features = ["yaml"] }
|
||||
assert_cmd = "2.2"
|
||||
insta = { version = "1.48", features = ["yaml"] }
|
||||
predicates = "3.1"
|
||||
tempfile = "3.23"
|
||||
tempfile = "3.27"
|
||||
|
||||
[profile.dev.package]
|
||||
insta.opt-level = 3
|
||||
similar.opt-level = 3
|
||||
|
||||
[build-dependencies]
|
||||
clap = "4.5.48"
|
||||
clap_complete = "4.2.1"
|
||||
clap = "4.6"
|
||||
clap_complete = "4.6"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
@@ -84,7 +84,7 @@ This runs `sleep 0.3`, `sleep 0.5` and `sleep 0.7`.
|
||||
For non-numeric parameters, you can also supply a list of values with the `-L`/`--parameter-list`
|
||||
option:
|
||||
```
|
||||
hyperfine -L compiler gcc,clang '{compiler} -O2 main.cpp'
|
||||
hyperfine -L compiler g++,clang++ '{compiler} -O2 main.cpp'
|
||||
```
|
||||
|
||||
### Intermediate shell
|
||||
@@ -302,7 +302,7 @@ Hyperfine can be installed from source via [cargo](https://doc.rust-lang.org/car
|
||||
cargo install --locked hyperfine
|
||||
```
|
||||
|
||||
Make sure that you use Rust 1.88 or newer.
|
||||
Make sure that you use Rust 1.97 or newer.
|
||||
|
||||
### From binaries (Linux, macOS, Windows)
|
||||
|
||||
|
||||
+2
-2
@@ -184,11 +184,11 @@ Replaces the string '{\fIVAR\fP}' in each command by the current parameter value
|
||||
.RS
|
||||
Example:
|
||||
.RS
|
||||
\fBhyperfine\fR \fB\-L\fR compiler gcc,clang '{compiler} \-O2 main.cpp'
|
||||
\fBhyperfine\fR \fB\-L\fR compiler g++,clang++ '{compiler} \-O2 main.cpp'
|
||||
.RE
|
||||
.RE
|
||||
.IP
|
||||
This performs benchmarks for 'gcc \-O2 main.cpp' and 'clang \-O2 main.cpp'.
|
||||
This performs benchmarks for 'g++ \-O2 main.cpp' and 'clang++ \-O2 main.cpp'.
|
||||
.IP
|
||||
The option can be specified multiple times to run benchmarks for all
|
||||
possible parameter combinations.
|
||||
|
||||
+19
-11
@@ -5,6 +5,7 @@ pub mod scheduler;
|
||||
pub mod timing_result;
|
||||
|
||||
use std::cmp;
|
||||
use std::io::{self, Write};
|
||||
|
||||
use crate::benchmark::executor::BenchmarkIteration;
|
||||
use crate::command::Command;
|
||||
@@ -12,6 +13,7 @@ use crate::options::{
|
||||
CmdFailureAction, CommandOutputPolicy, ExecutorKind, Options, OutputStyleOption,
|
||||
};
|
||||
use crate::outlier_detection::{modified_zscores, OUTLIER_THRESHOLD};
|
||||
use crate::output::console_writeln;
|
||||
use crate::output::format::{format_duration, format_duration_unit};
|
||||
use crate::output::progress_bar::get_progress_bar;
|
||||
use crate::output::warnings::{OutlierWarningOptions, Warnings};
|
||||
@@ -140,12 +142,13 @@ impl<'a> Benchmark<'a> {
|
||||
/// Run the benchmark for a single command
|
||||
pub fn run(&self) -> Result<BenchmarkResult> {
|
||||
if self.options.output_style != OutputStyleOption::Disabled {
|
||||
println!(
|
||||
console_writeln!(
|
||||
io::stdout(),
|
||||
"{}{}: {}",
|
||||
"Benchmark ".bold(),
|
||||
(self.number + 1).to_string().bold(),
|
||||
self.command.get_name_with_unused_parameters(),
|
||||
);
|
||||
)?;
|
||||
}
|
||||
|
||||
let mut times_real: Vec<Second> = vec![];
|
||||
@@ -358,19 +361,22 @@ impl<'a> Benchmark<'a> {
|
||||
let system_str = format_duration(system_mean, Some(time_unit));
|
||||
|
||||
if self.options.output_style != OutputStyleOption::Disabled {
|
||||
let mut stdout = io::stdout().lock();
|
||||
if times_real.len() == 1 {
|
||||
println!(
|
||||
console_writeln!(
|
||||
stdout,
|
||||
" Time ({} ≡): {:>8} {:>8} [User: {}, System: {}]",
|
||||
"abs".green().bold(),
|
||||
mean_str.green().bold(),
|
||||
" ", // alignment
|
||||
user_str.blue(),
|
||||
system_str.blue()
|
||||
);
|
||||
)?;
|
||||
} else {
|
||||
let stddev_str = format_duration(t_stddev.unwrap(), Some(time_unit));
|
||||
|
||||
println!(
|
||||
console_writeln!(
|
||||
stdout,
|
||||
" Time ({} ± {}): {:>8} ± {:>8} [User: {}, System: {}]",
|
||||
"mean".green().bold(),
|
||||
"σ".green(),
|
||||
@@ -378,16 +384,17 @@ impl<'a> Benchmark<'a> {
|
||||
stddev_str.green(),
|
||||
user_str.blue(),
|
||||
system_str.blue()
|
||||
);
|
||||
)?;
|
||||
|
||||
println!(
|
||||
console_writeln!(
|
||||
stdout,
|
||||
" Range ({} … {}): {:>8} … {:>8} {}",
|
||||
"min".cyan(),
|
||||
"max".purple(),
|
||||
min_str.cyan(),
|
||||
max_str.purple(),
|
||||
num_str.dimmed()
|
||||
);
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,15 +437,16 @@ impl<'a> Benchmark<'a> {
|
||||
}
|
||||
|
||||
if !warnings.is_empty() {
|
||||
eprintln!(" ");
|
||||
let mut stderr = io::stderr().lock();
|
||||
console_writeln!(stderr, " ")?;
|
||||
|
||||
for warning in &warnings {
|
||||
eprintln!(" {}: {}", "Warning".yellow(), warning);
|
||||
console_writeln!(stderr, " {}: {}", "Warning".yellow(), warning)?;
|
||||
}
|
||||
}
|
||||
|
||||
if self.options.output_style != OutputStyleOption::Disabled {
|
||||
println!(" ");
|
||||
console_writeln!(io::stdout(), " ")?;
|
||||
}
|
||||
|
||||
self.run_cleanup_command(self.command.get_parameters().iter().cloned(), output_policy)?;
|
||||
|
||||
+24
-15
@@ -3,10 +3,12 @@ use super::executor::{Executor, MockExecutor, RawExecutor, ShellExecutor};
|
||||
use super::{relative_speed, Benchmark};
|
||||
use colored::*;
|
||||
use std::cmp::Ordering;
|
||||
use std::io::{self, Write};
|
||||
|
||||
use crate::command::{Command, Commands};
|
||||
use crate::export::ExportManager;
|
||||
use crate::options::{ExecutorKind, Options, OutputStyleOption, SortOrder};
|
||||
use crate::output::console_writeln;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
@@ -58,13 +60,13 @@ impl<'a> Scheduler<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_relative_speed_comparison(&self) {
|
||||
pub fn print_relative_speed_comparison(&self) -> Result<()> {
|
||||
if self.options.output_style == OutputStyleOption::Disabled {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.results.len() < 2 {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let reference = self
|
||||
@@ -79,17 +81,19 @@ impl<'a> Scheduler<'a> {
|
||||
reference,
|
||||
self.options.sort_order_speed_comparison,
|
||||
) {
|
||||
let mut stdout = io::stdout().lock();
|
||||
match self.options.sort_order_speed_comparison {
|
||||
SortOrder::MeanTime => {
|
||||
println!("{}", "Summary".bold());
|
||||
console_writeln!(stdout, "{}", "Summary".bold())?;
|
||||
|
||||
let reference = annotated_results.iter().find(|r| r.is_reference).unwrap();
|
||||
let others = annotated_results.iter().filter(|r| !r.is_reference);
|
||||
|
||||
println!(
|
||||
console_writeln!(
|
||||
stdout,
|
||||
" {} ran",
|
||||
reference.result.command_with_unused_parameters.cyan()
|
||||
);
|
||||
)?;
|
||||
|
||||
for item in others {
|
||||
let stddev = if let Some(stddev) = item.relative_speed_stddev {
|
||||
@@ -114,18 +118,20 @@ impl<'a> Scheduler<'a> {
|
||||
stddev
|
||||
),
|
||||
};
|
||||
println!(
|
||||
console_writeln!(
|
||||
stdout,
|
||||
"{} {}",
|
||||
comparator,
|
||||
&item.result.command_with_unused_parameters.magenta()
|
||||
);
|
||||
item.result.command_with_unused_parameters.magenta()
|
||||
)?;
|
||||
}
|
||||
}
|
||||
SortOrder::Command => {
|
||||
println!("{}", "Relative speed comparison".bold());
|
||||
console_writeln!(stdout, "{}", "Relative speed comparison".bold())?;
|
||||
|
||||
for item in annotated_results {
|
||||
println!(
|
||||
console_writeln!(
|
||||
stdout,
|
||||
" {}{} {}",
|
||||
format!("{:10.2}", item.relative_speed).bold().green(),
|
||||
if item.is_reference {
|
||||
@@ -135,13 +141,14 @@ impl<'a> Scheduler<'a> {
|
||||
} else {
|
||||
" ".into()
|
||||
},
|
||||
&item.result.command_with_unused_parameters,
|
||||
);
|
||||
item.result.command_with_unused_parameters,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!(
|
||||
console_writeln!(
|
||||
io::stderr(),
|
||||
"{}: 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). \
|
||||
@@ -149,8 +156,10 @@ impl<'a> Scheduler<'a> {
|
||||
--shell=none/-N option. If it does not help either, you command is most likely too fast \
|
||||
to be accurately benchmarked by hyperfine.",
|
||||
"Note".bold().red()
|
||||
);
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn final_export(&self) -> Result<()> {
|
||||
|
||||
+2
-2
@@ -194,8 +194,8 @@ fn build_command() -> Command {
|
||||
.help(
|
||||
"Perform benchmark runs for each value in the comma-separated list VALUES. \
|
||||
Replaces the string '{VAR}' in each command by the current parameter value\
|
||||
.\n\nExample: hyperfine -L compiler gcc,clang '{compiler} -O2 main.cpp'\n\n\
|
||||
This performs benchmarks for 'gcc -O2 main.cpp' and 'clang -O2 main.cpp'.\n\n\
|
||||
.\n\nExample: hyperfine -L compiler g++,clang++ '{compiler} -O2 main.cpp'\n\n\
|
||||
This performs benchmarks for 'g++ -O2 main.cpp' and 'clang++ -O2 main.cpp'.\n\n\
|
||||
The option can be specified multiple times to run benchmarks for all \
|
||||
possible parameter combinations.\n"
|
||||
),
|
||||
|
||||
+4
-2
@@ -158,7 +158,9 @@ impl<'a> Commands<'a> {
|
||||
});
|
||||
let args: Vec<_> = args.map(|v| v.as_str()).collect::<Vec<_>>();
|
||||
let param_names_and_values: Vec<(&str, Vec<String>)> = args
|
||||
.chunks_exact(2)
|
||||
.as_chunks::<2>()
|
||||
.0
|
||||
.iter()
|
||||
.map(|pair| {
|
||||
let name = pair[0];
|
||||
let list_str = pair[1];
|
||||
@@ -169,7 +171,7 @@ impl<'a> Commands<'a> {
|
||||
let duplicates =
|
||||
Self::find_duplicates(param_names_and_values.iter().map(|(name, _)| *name));
|
||||
if !duplicates.is_empty() {
|
||||
bail!("Duplicate parameter names: {}", &duplicates.join(", "));
|
||||
bail!("Duplicate parameter names: {}", duplicates.join(", "));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@ use std::num::{self, ParseFloatError, ParseIntError};
|
||||
use rust_decimal::Error as DecimalError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error(transparent)]
|
||||
pub struct ConsoleOutputError(pub std::io::Error);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ParameterScanError {
|
||||
#[error("Error while parsing parameter scan arguments ({0})")]
|
||||
|
||||
+5
-3
@@ -1,5 +1,5 @@
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::io::{self, Write};
|
||||
|
||||
mod asciidoc;
|
||||
mod csv;
|
||||
@@ -18,6 +18,7 @@ use self::orgmode::OrgmodeExporter;
|
||||
|
||||
use crate::benchmark::benchmark_result::BenchmarkResult;
|
||||
use crate::options::SortOrder;
|
||||
use crate::output::console_writeln;
|
||||
use crate::util::units::Unit;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
@@ -144,8 +145,9 @@ impl ExportManager {
|
||||
}
|
||||
ExportTarget::Stdout => {
|
||||
if !intermediate {
|
||||
println!();
|
||||
println!("{}", String::from_utf8(content()?).unwrap());
|
||||
let mut stdout = io::stdout().lock();
|
||||
console_writeln!(stdout)?;
|
||||
console_writeln!(stdout, "{}", String::from_utf8(content()?).unwrap())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ impl MarkupExporter for OrgmodeExporter {
|
||||
format!(
|
||||
"| {} | {} |\n",
|
||||
cells.first().unwrap(),
|
||||
&cells[1..].join(" | ")
|
||||
cells[1..].join(" | ")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+16
-2
@@ -4,10 +4,12 @@
|
||||
)]
|
||||
|
||||
use std::env;
|
||||
use std::io::{self, Write};
|
||||
|
||||
use benchmark::scheduler::Scheduler;
|
||||
use cli::get_cli_arguments;
|
||||
use command::Commands;
|
||||
use error::ConsoleOutputError;
|
||||
use export::ExportManager;
|
||||
use options::Options;
|
||||
|
||||
@@ -44,17 +46,29 @@ fn run() -> Result<()> {
|
||||
|
||||
let mut scheduler = Scheduler::new(&commands, &options, &export_manager);
|
||||
scheduler.run_benchmarks()?;
|
||||
scheduler.print_relative_speed_comparison();
|
||||
scheduler.print_relative_speed_comparison()?;
|
||||
scheduler.final_export()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn caused_by_broken_console_pipe(error: &anyhow::Error) -> bool {
|
||||
error
|
||||
.downcast_ref::<ConsoleOutputError>()
|
||||
.is_some_and(|e| e.0.kind() == io::ErrorKind::BrokenPipe)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match run() {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
eprintln!("{} {:#}", "Error:".red(), e);
|
||||
// Exit quietly when a reader stops consuming console output early.
|
||||
if caused_by_broken_console_pipe(&e) {
|
||||
std::process::exit(0);
|
||||
}
|
||||
// The write to stderr can itself fail if stderr is closed; the
|
||||
// error message cannot be shown in that case anyway.
|
||||
let _ = writeln!(io::stderr(), "{} {:#}", "Error:".red(), e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
pub mod format;
|
||||
pub mod progress_bar;
|
||||
pub mod warnings;
|
||||
|
||||
/// Mark console write errors so broken pipes can be handled separately from file errors.
|
||||
macro_rules! console_writeln {
|
||||
($writer:expr $(, $($args:tt)*)?) => {
|
||||
writeln!($writer $(, $($args)*)?)
|
||||
.map_err($crate::error::ConsoleOutputError)
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use console_writeln;
|
||||
@@ -4,5 +4,5 @@
|
||||
/// [1] Mytkowicz, 2009. Producing Wrong Data Without Doing Anything Obviously Wrong!.
|
||||
/// Sigplan Notices - SIGPLAN. 44. 265-276. 10.1145/1508284.1508275.
|
||||
pub fn value() -> String {
|
||||
"X".repeat(rand::random::<usize>() % 4096usize)
|
||||
"X".repeat(rand::random_range(0..4096))
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
mod common;
|
||||
use common::hyperfine;
|
||||
use common::{hyperfine, hyperfine_raw_command};
|
||||
|
||||
use predicates::prelude::*;
|
||||
|
||||
@@ -33,6 +33,47 @@ fn one_run_is_supported() {
|
||||
.success();
|
||||
}
|
||||
|
||||
/// Regression test: hyperfine must not panic when writing to a closed
|
||||
/// stdout pipe (e.g. `hyperfine ... | head -n 1` or a downstream process
|
||||
/// that terminates early). A `BrokenPipe` error should result in a quiet
|
||||
/// exit with code 0 instead of a `println!` panic.
|
||||
#[test]
|
||||
fn exits_quietly_when_stdout_is_closed() {
|
||||
use std::process::Stdio;
|
||||
|
||||
for args in [
|
||||
vec!["--runs=1", "echo dummy benchmark"],
|
||||
vec!["--runs=1", "--export-json=-", "echo dummy benchmark"],
|
||||
vec!["--runs=1", "echo dummy benchmark", "echo second benchmark"],
|
||||
] {
|
||||
// A pipe whose read end has been closed before hyperfine is even
|
||||
// started: every write to it will fail with `BrokenPipe`.
|
||||
let (reader, writer) = std::io::pipe().expect("failed to create pipe");
|
||||
drop(reader);
|
||||
|
||||
let output = hyperfine_raw_command()
|
||||
.args(&args)
|
||||
.stdout(writer)
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("failed to spawn hyperfine")
|
||||
.wait_with_output()
|
||||
.expect("failed to wait for hyperfine");
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
assert!(
|
||||
!stderr.contains("panicked"),
|
||||
"hyperfine panicked while writing to a closed pipe:\n{}",
|
||||
stderr
|
||||
);
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"hyperfine did not exit cleanly; stderr:\n{}",
|
||||
stderr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_run_commands_without_a_shell() {
|
||||
hyperfine()
|
||||
|
||||
Reference in new issue
Block a user