Switch to unit-safe quantities

This commit is contained in:
David Peter authored and David Peter committed 2026-10-05 15:58:56 +02:00
1 parent 914d5cbc59
commit f9aa299c0f
15 files changed
+399 -287

No files matched your search

Generated
+10 -2
View File
@@ -392,6 +392,8 @@ dependencies = [
"serde",
"serde_json",
"shell-words",
"static_assertions",
"statistical",
"tempfile",
"thiserror",
"uom",
@@ -818,8 +820,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]]
name = "strip-ansi-escapes"
version = "0.2.1"
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "statistical"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025"
dependencies = [
+1 -1
View File
@@ -28,7 +28,7 @@ rand = "0.10"
shell-words = "1.1"
thiserror = "2.0"
anyhow = "1.0"
uom = { version = "0.36.0", default-features = false, features = ["std", "si", "f64"] }
static_assertions = "1.1.0"
[target.'cfg(not(windows))'.dependencies]
libc = "0.2"
+1 -1
View File
@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use serde::Serialize;
use crate::{benchmark::measurement::Measurements, util::units::Second};
use crate::benchmark::{measurement::Measurements, quantity::Second};
/// Parameter value and whether it was used in the command line template
#[derive(Debug, Default, Clone, Serialize, PartialEq)]
+25 -16
View File
@@ -2,6 +2,9 @@
use std::os::windows::process::CommandExt;
use std::process::ExitStatus;
use crate::benchmark::quantity::mean;
use crate::benchmark::quantity::Byte;
use crate::benchmark::quantity::Second;
use crate::command::Command;
use crate::options::{
CmdFailureAction, CommandInputPolicy, CommandOutputPolicy, Options, OutputStyleOption, Shell,
@@ -161,8 +164,8 @@ impl Executor for RawExecutor<'_> {
Ok(())
}
fn time_overhead(&self) -> Time {
Time::zero()
fn time_overhead(&self) -> Second {
Second::zero()
}
}
@@ -212,11 +215,20 @@ impl Executor for ShellExecutor<'_> {
)?;
// Subtract shell spawning time
fn ensure_non_negative(time: Second) -> Second {
if time < Second::zero() {
Second::zero()
} else {
time
}
}
if let Some(spawning_time) = self.shell_spawning_time {
result.time_wall_clock =
(result.time_wall_clock - spawning_time.time_wall_clock).max(0.0);
result.time_user = (result.time_user - spawning_time.time_user).max(0.0);
result.time_system = (result.time_system - spawning_time.time_system).max(0.0);
ensure_non_negative(result.time_wall_clock - spawning_time.time_wall_clock);
result.time_user = ensure_non_negative(result.time_user - spawning_time.time_user);
result.time_system =
ensure_non_negative(result.time_system - spawning_time.time_system);
}
Ok((
@@ -243,7 +255,7 @@ impl Executor for ShellExecutor<'_> {
None
};
let mut times_wall_clock: Vec<Second> = vec![];
let mut times_wall_clock: Vec<Second> = vec![]; // TODO
let mut times_user: Vec<Second> = vec![];
let mut times_system: Vec<Second> = vec![];
@@ -289,7 +301,7 @@ impl Executor for ShellExecutor<'_> {
time_wall_clock: mean(&times_wall_clock),
time_user: mean(&times_user),
time_system: mean(&times_system),
memory_usage_byte: 0,
memory_usage_byte: Byte::new(0),
});
Ok(())
@@ -312,7 +324,7 @@ impl MockExecutor {
fn extract_time<S: AsRef<str>>(sleep_command: S) -> Time {
assert!(sleep_command.as_ref().starts_with("sleep "));
Time::new::<second>(
Second::new(
sleep_command
.as_ref()
.trim_start_matches("sleep ")
@@ -345,9 +357,9 @@ impl Executor for MockExecutor {
Ok((
TimingResult {
time_wall_clock: Self::extract_time(command.get_command_line()),
time_user: 0.0,
time_system: 0.0,
memory_usage_byte: 0,
time_user: Second::zero(),
time_system: Second::zero(),
memory_usage_byte: Byte::new(0),
},
status,
))
@@ -359,7 +371,7 @@ impl Executor for MockExecutor {
fn time_overhead(&self) -> Time {
match &self.shell {
None => Time::zero(),
None => Second::zero(),
Some(shell) => Self::extract_time(shell),
}
}
@@ -367,8 +379,5 @@ impl Executor for MockExecutor {
#[test]
fn test_mock_executor_extract_time() {
assert_eq!(
MockExecutor::extract_time("sleep 0.1"),
Time::new::<second>(0.1)
);
assert_eq!(MockExecutor::extract_time("sleep 0.1"), Second::new(0.1));
}
+14 -14
View File
@@ -1,12 +1,7 @@
use serde::Serialize;
use statistical::{mean, median, standard_deviation};
use crate::util::units::Second;
use crate::{
outlier_detection::modified_zscores,
util::min_max::{max, min},
};
use crate::benchmark::quantity::{max, mean, median, min, standard_deviation, Byte, Second};
use crate::outlier_detection::modified_zscores;
/// Performance metric measurements and exit code for a single run
#[derive(Debug, Clone, Serialize, PartialEq)]
@@ -20,8 +15,8 @@ pub struct Measurement {
/// Time spent in kernel mode
pub system_time: Second,
/// Maximum memory usage of the process, in bytes
pub memory_usage_byte: u64,
/// Maximum memory usage of the process
pub memory_usage_byte: Byte,
/// Exit codes of the process
pub exit_code: Option<i32>,
@@ -65,11 +60,10 @@ impl Measurements {
pub fn stddev(&self) -> Option<Second> {
let times = self.wall_clock_times();
let t_mean = mean(&times);
if times.len() > 1 {
Some(standard_deviation(&times, Some(t_mean)))
} else {
if times.len() < 2 {
None
} else {
Some(standard_deviation(&times))
}
}
@@ -111,7 +105,13 @@ impl Measurements {
}
pub fn modified_zscores(&self) -> Vec<f64> {
modified_zscores(&self.wall_clock_times())
modified_zscores(
&self
.wall_clock_times()
.iter()
.map(|t| t.value_in::<Second>()) // TODO
.collect::<Vec<_>>(),
)
}
pub fn peak_memory_usage_mean(&self) -> Information {
+5 -5
View File
@@ -12,6 +12,7 @@ use std::time::Instant;
use crate::benchmark::benchmark_result::Parameter;
use crate::benchmark::executor::BenchmarkIteration;
use crate::benchmark::measurement::{Measurement, Measurements};
use crate::benchmark::quantity::{MilliSecond, Second};
use crate::command::Command;
use crate::options::{
CmdFailureAction, CommandOutputPolicy, ExecutorKind, Options, OutputStyleOption,
@@ -26,7 +27,6 @@ use crate::output::warnings::{OutlierWarningOptions, Warnings};
use crate::parameter::ParameterNameAndValue;
use crate::quantity::{byte, const_time_from_seconds, ratio, second, FormatQuantity, Time, Zero};
use crate::util::exit_code::extract_exit_code;
use crate::util::units::Second;
use benchmark_result::BenchmarkResult;
use measurement::{Measurement, Measurements};
@@ -36,7 +36,7 @@ use colored::*;
use self::executor::Executor;
/// Threshold for warning about fast execution time
pub const MIN_EXECUTION_TIME: Time = const_time_from_seconds(5e-3);
pub const MIN_EXECUTION_TIME: MilliSecond = MilliSecond::new(5.);
pub struct Benchmark<'a> {
number: usize,
@@ -265,7 +265,7 @@ impl<'a> Benchmark<'a> {
};
let preparation_result = run_preparation_command()?;
let preparation_overhead = preparation_result.map_or(0.0, |res| {
let preparation_overhead = preparation_result.map_or(Second::zero(), |res| {
res.time_wall_clock + self.executor.time_overhead()
});
@@ -294,7 +294,7 @@ impl<'a> Benchmark<'a> {
}
let conclusion_result = run_conclusion_command()?;
let conclusion_overhead = conclusion_result.map_or(0.0, |res| {
let conclusion_overhead = conclusion_result.map_or(Second::zero(), |res| {
res.time_wall_clock + self.executor.time_overhead()
});
@@ -434,7 +434,7 @@ impl<'a> Benchmark<'a> {
&& measurements
.wall_clock_times()
.iter()
.any(|&t| t < MIN_EXECUTION_TIME)
.any(|&t| t < MIN_EXECUTION_TIME.convert_to::<Second>())
{
warnings.push(Warnings::FastExecutionTime);
}
+184 -82
View File
@@ -1,103 +1,205 @@
use std::fmt::{self, Display, Formatter};
use std::{
fmt::{self, Display, Formatter},
ops::{Add, Div, Sub},
};
use serde::Serialize;
#[derive(Debug, Clone, Serialize, PartialEq)]
enum MetricPrefix {
Nano,
Micro,
Milli,
None,
pub trait IsQuantity {
type Value;
const IS_TIME: bool;
const METRIC_PREFIX_TIME: i32;
const IS_INFORMATION: bool;
const BINARY_PREFIX_INFORMATION: i32;
fn unsafe_from(value: Self::Value) -> Self;
}
impl Display for MetricPrefix {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
MetricPrefix::Nano => write!(f, "n"),
MetricPrefix::Micro => write!(f, "µ"),
MetricPrefix::Milli => write!(f, "m"),
MetricPrefix::None => Ok(()),
#[derive(Debug, Clone, Copy, Default, Serialize, PartialEq, Eq, PartialOrd)] // TODO: check partialord
pub struct Quantity<
Value,
const IS_TIME: bool,
const METRIC_PREFIX_TIME: i32,
const IS_INFORMATION: bool,
const BINARY_PREFIX_INFORMATION: i32,
>(Value);
impl<
Value,
const IS_TIME: bool,
const METRIC_PREFIX_TIME: i32,
const IS_INFORMATION: bool,
const BINARY_PREFIX_INFORMATION: i32,
> IsQuantity
for Quantity<Value, IS_TIME, METRIC_PREFIX_TIME, IS_INFORMATION, BINARY_PREFIX_INFORMATION>
{
type Value = Value;
const IS_TIME: bool = IS_TIME;
const METRIC_PREFIX_TIME: i32 = METRIC_PREFIX_TIME;
const IS_INFORMATION: bool = IS_INFORMATION;
const BINARY_PREFIX_INFORMATION: i32 = BINARY_PREFIX_INFORMATION;
fn unsafe_from(value: Self::Value) -> Self {
Self(value)
}
}
type Time<const PREFIX: i32> = Quantity<f64, true, PREFIX, false, 0>;
type Information<const PREFIX: i32> = Quantity<u64, false, 0, true, PREFIX>;
pub type Second = Time<0>;
pub type MilliSecond = Time<-3>;
pub type MicroSecond = Time<-6>;
pub type Byte = Information<0>;
pub type KibiByte = Information<10>;
pub type MebiByte = Information<20>;
pub type GibiByte = Information<30>;
pub type TebiByte = Information<40>;
impl<
Value: Copy + Default,
const IS_TIME: bool,
const METRIC_PREFIX_TIME: i32,
const IS_INFORMATION: bool,
const BINARY_PREFIX_INFORMATION: i32,
> Quantity<Value, IS_TIME, METRIC_PREFIX_TIME, IS_INFORMATION, BINARY_PREFIX_INFORMATION>
{
pub const fn new(value: Value) -> Self {
Self(value)
}
pub fn zero() -> Self {
Self::new(Value::default())
}
pub fn value_in<TargetUnit: IsQuantity>(&self) -> Value {
const {
assert!(IS_TIME == TargetUnit::IS_TIME);
assert!(METRIC_PREFIX_TIME == TargetUnit::METRIC_PREFIX_TIME);
assert!(IS_INFORMATION == TargetUnit::IS_INFORMATION);
assert!(BINARY_PREFIX_INFORMATION == TargetUnit::BINARY_PREFIX_INFORMATION);
}
self.0
}
}
#[derive(Debug, Clone, Serialize, PartialEq)]
enum BinaryPrefix {
None,
Kibi,
Mebi,
Gibi,
Tebi,
impl<const METRIC_PREFIX_TIME: i32> Time<METRIC_PREFIX_TIME> {
pub fn convert_to<TargetUnit: IsQuantity<Value = f64>>(&self) -> TargetUnit {
let factor = 10_f64.powi(METRIC_PREFIX_TIME - TargetUnit::METRIC_PREFIX_TIME);
TargetUnit::unsafe_from(self.0 * factor)
}
}
impl Display for BinaryPrefix {
impl<const METRIC_PREFIX_TIME: i32> Add<Time<METRIC_PREFIX_TIME>> for Time<METRIC_PREFIX_TIME> {
type Output = Time<METRIC_PREFIX_TIME>;
fn add(self, rhs: Time<METRIC_PREFIX_TIME>) -> Self::Output {
Time::new(self.0 + rhs.0)
}
}
impl<const METRIC_PREFIX_TIME: i32> Sub<Time<METRIC_PREFIX_TIME>> for Time<METRIC_PREFIX_TIME> {
type Output = Time<METRIC_PREFIX_TIME>;
fn sub(self, rhs: Time<METRIC_PREFIX_TIME>) -> Self::Output {
Time::new(self.0 - rhs.0)
}
}
impl<const METRIC_PREFIX_TIME: i32> Div<Time<METRIC_PREFIX_TIME>> for Time<METRIC_PREFIX_TIME> {
type Output = f64;
fn div(self, rhs: Time<METRIC_PREFIX_TIME>) -> Self::Output {
self.0 / rhs.0
}
}
impl<const METRIC_PREFIX_TIME: i32> Display for Time<METRIC_PREFIX_TIME> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
BinaryPrefix::None => Ok(()),
BinaryPrefix::Kibi => write!(f, "Ki"),
BinaryPrefix::Mebi => write!(f, "Mi"),
BinaryPrefix::Gibi => write!(f, "Gi"),
BinaryPrefix::Tebi => write!(f, "Ti"),
let prefix = match METRIC_PREFIX_TIME {
-6 => "µ",
-3 => "m",
0 => "s",
_ => unreachable!(),
};
write!(f, "{} {}s", self.0, prefix)
}
}
impl<const BINARY_PREFIX_INFORMATION: i32> Display for Information<BINARY_PREFIX_INFORMATION> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let prefix = match BINARY_PREFIX_INFORMATION {
0 => "",
10 => "Ki",
20 => "Mi",
30 => "Gi",
40 => "Ti",
_ => unreachable!(),
};
write!(f, "{} {}B", self.0, prefix)
}
}
macro_rules! quantity_fn {
($name:ident, $unwrapped_values:ident, $body:expr) => {
pub fn $name<
const IS_TIME: bool,
const METRIC_PREFIX_TIME: i32,
const IS_INFORMATION: bool,
const BINARY_PREFIX_INFORMATION: i32,
>(
values: &[Quantity<
f64,
IS_TIME,
METRIC_PREFIX_TIME,
IS_INFORMATION,
BINARY_PREFIX_INFORMATION,
>],
) -> Quantity<f64, IS_TIME, METRIC_PREFIX_TIME, IS_INFORMATION, BINARY_PREFIX_INFORMATION> {
let $unwrapped_values: Vec<f64> = values.iter().map(|v| v.0).collect();
let result_value = $body;
Quantity::new(result_value)
}
}
};
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub enum Unit {
Second(MetricPrefix),
Byte(BinaryPrefix),
}
pub const SECOND: Unit = Unit::Second(MetricPrefix::None);
pub const MILLISECOND: Unit = Unit::Second(MetricPrefix::Milli);
pub const MICROSECOND: Unit = Unit::Second(MetricPrefix::Micro);
pub const BYTE: Unit = Unit::Byte(BinaryPrefix::None);
impl Display for Unit {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Unit::Second(prefix) => write!(f, "{prefix}s",),
Unit::Byte(prefix) => write!(f, "{prefix}B"),
}
}
}
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct Quantity<Value> {
value: Value,
unit: Unit,
}
impl<Value> Quantity<Value> {
pub fn new(value: Value, unit: Unit) -> Self {
Self { value, unit }
}
}
impl<Value: Display> Display for Quantity<Value> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{} {}", self.value, self.unit)
}
}
pub fn mean(values: &[Quantity<f64>]) -> Quantity<f64> {
statistical::mean(values).unwrap()
}
pub type Time = Quantity<f64>;
pub type Memory = Quantity<u64>;
quantity_fn!(mean, values, statistical::mean(&values));
quantity_fn!(median, values, statistical::median(&values));
quantity_fn!(min, values, crate::util::min_max::min(&values));
quantity_fn!(max, values, crate::util::min_max::max(&values));
quantity_fn!(standard_deviation, values, {
let mean_value = statistical::mean(&values);
statistical::standard_deviation(&values, Some(mean_value))
});
#[test]
fn basic() {
let time = Time {
value: 123.4,
unit: Unit::Second(MetricPrefix::Milli),
};
fn test_quantity() {
let time = MilliSecond::new(123.4);
assert_eq!(time.value_in::<MilliSecond>(), 123.4);
let time_s = time.convert_to::<Second>();
approx::assert_relative_eq!(time_s.value_in::<Second>(), 0.1234);
let time_us = time.convert_to::<MicroSecond>();
approx::assert_relative_eq!(time_us.value_in::<MicroSecond>(), 123400.0);
}
#[test]
fn test_format() {
let time = MilliSecond::new(123.4);
assert_eq!(time.to_string(), "123.4 ms");
let peak_memory_usage = Memory {
value: 8,
unit: Unit::Byte(BinaryPrefix::Kibi),
};
let peak_memory_usage = KibiByte::new(8);
assert_eq!(peak_memory_usage.to_string(), "8 KiB");
}
#[test]
fn test_mean() {
let values = vec![MilliSecond::new(123.4), MilliSecond::new(234.5)];
let result = mean(&values);
assert_eq!(result.to_string(), "178.95 ms");
}
+14 -13
View File
@@ -1,10 +1,7 @@
use std::cmp::Ordering;
use super::benchmark_result::BenchmarkResult;
use crate::{
options::SortOrder,
quantity::{self, Ratio, Time, Zero},
};
use crate::{benchmark::quantity::Second, options::SortOrder};
#[derive(Debug)]
pub struct BenchmarkResultWithRelativeSpeed<'a> {
@@ -55,8 +52,8 @@ fn compute_relative_speeds<'a>(
let is_reference = std::ptr::eq(result, reference);
let relative_ordering = compare_mean_time(result, reference);
if result.mean_wall_clock_time() == Time::zero()
|| reference.mean_wall_clock_time() == Time::zero()
if result.mean_wall_clock_time() == Second::zero()
|| reference.mean_wall_clock_time() == Second::zero()
{
return BenchmarkResultWithRelativeSpeed {
result,
@@ -117,7 +114,8 @@ pub fn compute_with_check_from_reference<'a>(
reference: &'a BenchmarkResult,
sort_order: SortOrder,
) -> Option<Vec<BenchmarkResultWithRelativeSpeed<'a>>> {
if fastest_of(results).mean_wall_clock_time() == 0.0 || reference.mean_wall_clock_time() == 0.0
if fastest_of(results).mean_wall_clock_time() == Second::zero()
|| reference.mean_wall_clock_time() == Second::zero()
{
return None;
}
@@ -131,7 +129,7 @@ pub fn compute_with_check(
) -> Option<Vec<BenchmarkResultWithRelativeSpeed<'_>>> {
let fastest = fastest_of(results);
if fastest.mean_wall_clock_time() == 0.0 {
if fastest.mean_wall_clock_time() == Second::zero() {
return None;
}
@@ -152,16 +150,19 @@ pub fn compute<'a>(
fn create_result(name: &str, mean: f64) -> BenchmarkResult {
use std::collections::BTreeMap;
use crate::benchmark::measurement::{Measurement, Measurements};
use crate::benchmark::{
measurement::{Measurement, Measurements},
quantity::Byte,
};
BenchmarkResult {
command: name.into(),
measurements: Measurements {
measurements: vec![Measurement {
wall_clock_time: mean,
user_time: mean,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(mean),
user_time: Second::new(mean),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
}],
},
+28 -37
View File
@@ -4,6 +4,7 @@ use csv::WriterBuilder;
use super::Exporter;
use crate::benchmark::benchmark_result::BenchmarkResult;
use crate::benchmark::quantity::Second;
use crate::options::SortOrder;
use crate::quantity::{FormatQuantity, TimeUnit};
@@ -45,17 +46,14 @@ impl Exporter for CsvExporter {
let mut fields = vec![Cow::Borrowed(res.command.as_bytes())];
for f in &[
res.mean_wall_clock_time(),
res.measurements.stddev().unwrap_or(0.0),
res.measurements.stddev().unwrap_or_default(),
res.measurements.median(),
res.measurements.user_mean(),
res.measurements.system_mean(),
res.measurements.min(),
res.measurements.max(),
] {
fields.push(Cow::Owned(
f.format_with_precision(CSV_UNIT, CSV_PRECISION)
.into_bytes(),
))
fields.push(Cow::Owned(f.value_in::<Second>().to_string().into_bytes()))
}
for v in res.parameters.values() {
fields.push(Cow::Borrowed(v.value.as_bytes()))
@@ -67,20 +65,13 @@ impl Exporter for CsvExporter {
}
}
#[cfg(test)]
use crate::benchmark::benchmark_result::Parameter;
#[cfg(test)]
use crate::benchmark::measurement::{Measurement, Measurements};
#[test]
fn test_csv() {
use crate::benchmark::benchmark_result::Parameter;
use crate::benchmark::measurement::{Measurement, Measurements};
use crate::quantity::{byte, second, Information, Time, Zero};
use crate::benchmark::quantity::{Byte, Second};
use std::collections::BTreeMap;
use std::process::ExitStatus;
let exporter = CsvExporter::default();
@@ -89,24 +80,24 @@ fn test_csv() {
command: String::from("command_a"),
measurements: Measurements::new(vec![
Measurement {
wall_clock_time: 7.0,
user_time: 7.0,
system_time: 0.0,
memory_usage_byte: 1024,
wall_clock_time: Second::new(7.0),
user_time: Second::new(7.0),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 8.0,
user_time: 8.0,
system_time: 0.0,
memory_usage_byte: 1024,
wall_clock_time: Second::new(8.0),
user_time: Second::new(8.0),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 12.0,
user_time: 12.0,
system_time: 0.0,
memory_usage_byte: 1024,
wall_clock_time: Second::new(12.0),
user_time: Second::new(12.0),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
]),
@@ -133,24 +124,24 @@ fn test_csv() {
command: String::from("command_b"),
measurements: Measurements::new(vec![
Measurement {
wall_clock_time: 17.0,
user_time: 17.0,
system_time: 0.0,
memory_usage_byte: 1024,
wall_clock_time: Second::new(17.0),
user_time: Second::new(17.0),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 18.0,
user_time: 18.0,
system_time: 0.0,
memory_usage_byte: 1024,
wall_clock_time: Second::new(18.0),
user_time: Second::new(18.0),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 19.0,
user_time: 19.0,
system_time: 0.0,
memory_usage_byte: 1024,
wall_clock_time: Second::new(19.0),
user_time: Second::new(19.0),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
]),
+97 -96
View File
@@ -1,6 +1,7 @@
use super::Exporter;
use crate::benchmark::benchmark_result::BenchmarkResult;
use crate::benchmark::measurement::{Measurement, Measurements};
use crate::benchmark::quantity::{Byte, Second};
use crate::export::asciidoc::AsciidocExporter;
use crate::export::orgmode::OrgmodeExporter;
use crate::quantity::{byte, second, Information, Time, TimeUnit, Zero};
@@ -30,24 +31,24 @@ fn test_markup_export_auto_ms() {
command: String::from("sleep 0.1"),
measurements: Measurements::new(vec![
Measurement {
wall_clock_time: 0.09,
user_time: 0.09,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(0.09),
user_time: Second::new(0.09),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 0.10,
user_time: 0.10,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(0.10),
user_time: Second::new(0.10),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 0.14,
user_time: 0.14,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(0.14),
user_time: Second::new(0.14),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
]),
@@ -57,24 +58,24 @@ fn test_markup_export_auto_ms() {
command: String::from("sleep 2"),
measurements: Measurements::new(vec![
Measurement {
wall_clock_time: 2.0,
user_time: 2.0,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(2.0),
user_time: Second::new(2.0),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 3.0,
user_time: 3.0,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(3.0),
user_time: Second::new(3.0),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 4.0,
user_time: 4.0,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(4.0),
user_time: Second::new(4.0),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
]),
@@ -129,24 +130,24 @@ fn test_markup_export_auto_s() {
command: String::from("sleep 2"),
measurements: Measurements::new(vec![
Measurement {
wall_clock_time: 2.1,
user_time: 2.1,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(2.1),
user_time: Second::new(2.1),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 2.2,
user_time: 2.2,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(2.2),
user_time: Second::new(2.2),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 2.3,
user_time: 2.3,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(2.3),
user_time: Second::new(2.3),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
]),
@@ -156,24 +157,24 @@ fn test_markup_export_auto_s() {
command: String::from("sleep 0.1"),
measurements: Measurements::new(vec![
Measurement {
wall_clock_time: 0.1,
user_time: 0.1,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(0.1),
user_time: Second::new(0.1),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 0.2,
user_time: 0.2,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(0.2),
user_time: Second::new(0.2),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 0.3,
user_time: 0.3,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(0.3),
user_time: Second::new(0.3),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
]),
@@ -228,24 +229,24 @@ fn test_markup_export_manual_ms() {
command: String::from("sleep 2"),
measurements: Measurements::new(vec![
Measurement {
wall_clock_time: 2.1,
user_time: 2.1,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(2.1),
user_time: Second::new(2.1),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 2.2,
user_time: 2.2,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(2.2),
user_time: Second::new(2.2),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 2.3,
user_time: 2.3,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(2.3),
user_time: Second::new(2.3),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
]),
@@ -255,24 +256,24 @@ fn test_markup_export_manual_ms() {
command: String::from("sleep 0.1"),
measurements: Measurements::new(vec![
Measurement {
wall_clock_time: 0.1,
user_time: 0.1,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(0.1),
user_time: Second::new(0.1),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 0.2,
user_time: 0.2,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(0.2),
user_time: Second::new(0.2),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 0.3,
user_time: 0.3,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(0.3),
user_time: Second::new(0.3),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
]),
@@ -326,24 +327,24 @@ fn test_markup_export_manual_s() {
command: String::from("sleep 2"),
measurements: Measurements::new(vec![
Measurement {
wall_clock_time: 2.01,
user_time: 2.01,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(2.01),
user_time: Second::new(2.01),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 2.02,
user_time: 2.02,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(2.02),
user_time: Second::new(2.02),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 2.03,
user_time: 2.03,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(2.03),
user_time: Second::new(2.03),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
]),
@@ -353,24 +354,24 @@ fn test_markup_export_manual_s() {
command: String::from("sleep 0.1"),
measurements: Measurements::new(vec![
Measurement {
wall_clock_time: 0.11,
user_time: 0.11,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(0.11),
user_time: Second::new(0.11),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 0.12,
user_time: 0.12,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(0.12),
user_time: Second::new(0.12),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
Measurement {
wall_clock_time: 0.13,
user_time: 0.13,
system_time: 0.,
memory_usage_byte: 1024,
wall_clock_time: Second::new(0.13),
user_time: Second::new(0.13),
system_time: Second::zero(),
memory_usage_byte: Byte::new(1024),
exit_code: Some(0),
},
]),
+4 -3
View File
@@ -7,9 +7,10 @@ use std::{cmp, env, fmt, io};
use anyhow::{bail, ensure};
use clap::ArgMatches;
use crate::benchmark::quantity::Second;
use crate::command::Commands;
use crate::error::OptionsError;
use crate::quantity::{second, Time, TimeUnit};
use crate::util::units::Unit;
use anyhow::Result;
@@ -256,7 +257,7 @@ impl Default for Options {
Options {
run_bounds: RunBounds::default(),
warmup_count: 0,
min_benchmarking_time: Time::new::<second>(3.0),
min_benchmarking_time: Second::new(3.0),
command_failure_action: CmdFailureAction::RaiseError,
reference_command: None,
reference_name: None,
@@ -442,7 +443,7 @@ impl Options {
};
if let Some(time) = matches.get_one::<String>("min-benchmarking-time") {
options.min_benchmarking_time = Time::new::<second>(
options.min_benchmarking_time = Second::new(
time.parse::<f64>()
.map_err(|e| OptionsError::FloatParsingError("min-benchmarking-time", e))?,
);
+3 -3
View File
@@ -1,7 +1,8 @@
use std::fmt;
use crate::benchmark::quantity::Second;
use crate::benchmark::MIN_EXECUTION_TIME;
use crate::quantity::{FormatQuantity, Time, TimeUnit};
use crate::output::format::format_duration;
pub struct OutlierWarningOptions {
pub warmup_in_use: bool,
@@ -21,11 +22,10 @@ impl fmt::Display for Warnings {
match *self {
Warnings::FastExecutionTime => write!(
f,
"Command took less than {} ms to complete. Note that the results might be \
"Command took less than {MIN_EXECUTION_TIME} to complete. Note that the results might be \
inaccurate because hyperfine can not calibrate the shell startup time much \
more precise than this limit. You can try to use the `-N`/`--shell=none` \
option to disable the shell completely.",
MIN_EXECUTION_TIME.format_with_precision(TimeUnit::MilliSecond, 0)
),
Warnings::NonZeroExitCode => write!(f, "Ignoring non-zero exit code."),
Warnings::SlowInitialRun(time_first_run, ref options) => write!(
+2 -2
View File
@@ -16,7 +16,7 @@ use std::os::fd::AsFd;
#[cfg(target_os = "windows")]
use windows_sys::Win32::System::Threading::CREATE_SUSPENDED;
use crate::benchmark::measurement::Measurement;
use crate::benchmark::quantity::{Byte, Second};
use wall_clock_timer::WallClockTimer;
use std::io::Read;
@@ -30,7 +30,7 @@ pub struct TimerResult {
pub time_wall_clock: Second,
pub time_user: Second,
pub time_system: Second,
pub memory_usage_byte: u64,
pub memory_usage_byte: Byte,
/// The exit status of the process
pub status: ExitStatus,
}
+8 -8
View File
@@ -8,22 +8,22 @@ use std::process::{Child, ExitStatus};
use anyhow::Result;
use crate::util::units::Second;
use crate::benchmark::quantity::{Byte, Second};
#[derive(Debug, Copy, Clone)]
struct ResourceUsage {
/// Total amount of time spent executing in user mode
pub user_usec: Second,
pub user_usec: f64,
/// Total amount of time spent executing in kernel mode
pub system_usec: Second,
pub system_usec: f64,
/// Maximum amount of memory used by the process, in bytes
pub memory_usage_byte: i64,
}
#[allow(clippy::useless_conversion)]
fn timeval_to_seconds(tv: libc::timeval) -> Second {
fn timeval_to_seconds(tv: libc::timeval) -> f64 {
const MICROSEC_PER_SEC: i64 = 1000 * 1000;
(i64::from(tv.tv_sec) * MICROSEC_PER_SEC + i64::from(tv.tv_usec)) as f64 * 1e-6
}
@@ -68,13 +68,13 @@ impl CPUTimer {
Self {}
}
pub fn stop(&self, child: Child) -> Result<(ExitStatus, Second, Second, u64)> {
pub fn stop(&self, child: Child) -> Result<(ExitStatus, Second, Second, Byte)> {
let (status, usage) = wait4(child)?;
Ok((
status,
usage.user_usec,
usage.system_usec,
u64::try_from(usage.memory_usage_byte).unwrap_or(0),
Second::new(usage.user_usec),
Second::new(usage.system_usec),
Byte::new(u64::try_from(usage.memory_usage_byte).unwrap_or(0)),
))
}
}
+3 -4
View File
@@ -1,6 +1,6 @@
use std::time::Instant;
use crate::quantity::{nanosecond, second, Time};
use crate::benchmark::quantity::Second;
pub struct WallClockTimer {
start: Instant,
@@ -15,8 +15,7 @@ impl WallClockTimer {
pub fn stop(&self) -> Time {
let duration = self.start.elapsed();
Time::new::<second>(duration.as_secs() as f64)
+ Time::new::<nanosecond>(duration.subsec_nanos() as f64)
// TODO
Second::new(duration.as_secs() as f64 + f64::from(duration.subsec_nanos()) * 1e-9)
}
}