Compare commits

..
Author SHA1 Message Date
David Peter 34aed32655 Dependency and MSRV update 2026-10-02 11:01:17 +02:00
9 changed files with 31 additions and 120 deletions

No files matched your search

-1
View File
@@ -79,7 +79,6 @@ 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
View File
@@ -89,7 +89,7 @@ hyperfine -L compiler gcc,clang '{compiler} -O2 main.cpp'
### Intermediate shell
By default, commands are executed using `sh` on Unix (resolved through `PATH`) or `cmd.exe` on Windows.
By default, commands are executed using a predefined shell (`/bin/sh` on Unix, `cmd.exe` on Windows).
If you want to use a different shell, you can use the `-S, --shell <SHELL>` option:
```sh
hyperfine --shell zsh 'for i in {1..10000}; do echo test; done'
+11 -19
View File
@@ -5,7 +5,6 @@ pub mod scheduler;
pub mod timing_result;
use std::cmp;
use std::io::{self, Write};
use crate::benchmark::executor::BenchmarkIteration;
use crate::command::Command;
@@ -13,7 +12,6 @@ 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};
@@ -142,13 +140,12 @@ impl<'a> Benchmark<'a> {
/// Run the benchmark for a single command
pub fn run(&self) -> Result<BenchmarkResult> {
if self.options.output_style != OutputStyleOption::Disabled {
console_writeln!(
io::stdout(),
println!(
"{}{}: {}",
"Benchmark ".bold(),
(self.number + 1).to_string().bold(),
self.command.get_name_with_unused_parameters(),
)?;
);
}
let mut times_real: Vec<Second> = vec![];
@@ -361,22 +358,19 @@ 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 {
console_writeln!(
stdout,
println!(
" 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));
console_writeln!(
stdout,
println!(
" Time ({} ± {}): {:>8} ± {:>8} [User: {}, System: {}]",
"mean".green().bold(),
"σ".green(),
@@ -384,17 +378,16 @@ impl<'a> Benchmark<'a> {
stddev_str.green(),
user_str.blue(),
system_str.blue()
)?;
);
console_writeln!(
stdout,
println!(
" Range ({} … {}): {:>8} … {:>8} {}",
"min".cyan(),
"max".purple(),
min_str.cyan(),
max_str.purple(),
num_str.dimmed()
)?;
);
}
}
@@ -437,16 +430,15 @@ impl<'a> Benchmark<'a> {
}
if !warnings.is_empty() {
let mut stderr = io::stderr().lock();
console_writeln!(stderr, " ")?;
eprintln!(" ");
for warning in &warnings {
console_writeln!(stderr, " {}: {}", "Warning".yellow(), warning)?;
eprintln!(" {}: {}", "Warning".yellow(), warning);
}
}
if self.options.output_style != OutputStyleOption::Disabled {
console_writeln!(io::stdout(), " ")?;
println!(" ");
}
self.run_cleanup_command(self.command.get_parameters().iter().cloned(), output_policy)?;
+13 -22
View File
@@ -3,12 +3,10 @@ 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;
@@ -60,13 +58,13 @@ impl<'a> Scheduler<'a> {
Ok(())
}
pub fn print_relative_speed_comparison(&self) -> Result<()> {
pub fn print_relative_speed_comparison(&self) {
if self.options.output_style == OutputStyleOption::Disabled {
return Ok(());
return;
}
if self.results.len() < 2 {
return Ok(());
return;
}
let reference = self
@@ -81,19 +79,17 @@ 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 => {
console_writeln!(stdout, "{}", "Summary".bold())?;
println!("{}", "Summary".bold());
let reference = annotated_results.iter().find(|r| r.is_reference).unwrap();
let others = annotated_results.iter().filter(|r| !r.is_reference);
console_writeln!(
stdout,
println!(
" {} ran",
reference.result.command_with_unused_parameters.cyan()
)?;
);
for item in others {
let stddev = if let Some(stddev) = item.relative_speed_stddev {
@@ -118,20 +114,18 @@ impl<'a> Scheduler<'a> {
stddev
),
};
console_writeln!(
stdout,
println!(
"{} {}",
comparator,
item.result.command_with_unused_parameters.magenta()
)?;
);
}
}
SortOrder::Command => {
console_writeln!(stdout, "{}", "Relative speed comparison".bold())?;
println!("{}", "Relative speed comparison".bold());
for item in annotated_results {
console_writeln!(
stdout,
println!(
" {}{} {}",
format!("{:10.2}", item.relative_speed).bold().green(),
if item.is_reference {
@@ -142,13 +136,12 @@ impl<'a> Scheduler<'a> {
" ".into()
},
item.result.command_with_unused_parameters,
)?;
);
}
}
}
} else {
console_writeln!(
io::stderr(),
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). \
@@ -156,10 +149,8 @@ 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<()> {
-4
View File
@@ -3,10 +3,6 @@ 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})")]
+3 -5
View File
@@ -1,5 +1,5 @@
use std::fs::{File, OpenOptions};
use std::io::{self, Write};
use std::io::Write;
mod asciidoc;
mod csv;
@@ -18,7 +18,6 @@ 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};
@@ -145,9 +144,8 @@ impl ExportManager {
}
ExportTarget::Stdout => {
if !intermediate {
let mut stdout = io::stdout().lock();
console_writeln!(stdout)?;
console_writeln!(stdout, "{}", String::from_utf8(content()?).unwrap())?;
println!();
println!("{}", String::from_utf8(content()?).unwrap());
}
}
}
+2 -16
View File
@@ -4,12 +4,10 @@
)]
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;
@@ -46,29 +44,17 @@ 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) => {
// 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);
eprintln!("{} {:#}", "Error:".red(), e);
std::process::exit(1);
}
}
-10
View File
@@ -1,13 +1,3 @@
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;
+1 -42
View File
@@ -1,5 +1,5 @@
mod common;
use common::{hyperfine, hyperfine_raw_command};
use common::hyperfine;
use predicates::prelude::*;
@@ -33,47 +33,6 @@ 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()