From 6b4e9eb5c21e6f5641ef080d99afec3e281128b4 Mon Sep 17 00:00:00 2001 From: David Peter Date: Mon, 5 Oct 2026 14:20:12 +0200 Subject: [PATCH] Represent memory usage as f64 as well --- Cargo.lock | 13 +------- Cargo.toml | 1 - src/benchmark/executor.rs | 4 +-- src/benchmark/measurement.rs | 24 ++++++-------- src/export/csv.rs | 12 +++---- src/export/tests.rs | 48 ++++++++++++++-------------- src/quantity/mod.rs | 62 ++++++++++++++++++++++++++---------- src/timer/unix_timer.rs | 8 +++-- 8 files changed, 93 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e223749..02c106e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -515,7 +515,7 @@ dependencies = [ "num-complex", "num-integer", "num-iter", - "num-rational 0.2.4", + "num-rational", "num-traits", ] @@ -572,16 +572,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1041,7 +1031,6 @@ version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffd36e5350a65d112584053ee91843955826bf9e56ec0d1351214e01f6d7cd9c" dependencies = [ - "num-rational 0.4.2", "num-traits", "typenum", ] diff --git a/Cargo.toml b/Cargo.toml index 1c1891c..9154fc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,6 @@ uom = { version = "0.36.0", default-features = false, features = [ "std", "si", "f64", - "u64", ] } [target.'cfg(not(windows))'.dependencies] diff --git a/src/benchmark/executor.rs b/src/benchmark/executor.rs index ddc0231..de923b3 100644 --- a/src/benchmark/executor.rs +++ b/src/benchmark/executor.rs @@ -9,7 +9,7 @@ use crate::options::{ CmdFailureAction, CommandInputPolicy, CommandOutputPolicy, Options, OutputStyleOption, Shell, }; use crate::output::progress_bar::get_progress_bar; -use crate::quantity::{byte, second, Information, Time, TimeQuantity}; +use crate::quantity::{second, Information, InformationQuantity, Time, TimeQuantity}; use crate::timer::execute_and_measure; use crate::util::randomized_environment_offset; @@ -348,7 +348,7 @@ impl Executor for MockExecutor { time_wall_clock: Self::extract_time(command.get_command_line()), time_user: Time::zero(), time_system: Time::zero(), - peak_memory_usage: Information::new::(0), + peak_memory_usage: Information::zero(), exit_status, }) } diff --git a/src/benchmark/measurement.rs b/src/benchmark/measurement.rs index 26a3555..5906a24 100644 --- a/src/benchmark/measurement.rs +++ b/src/benchmark/measurement.rs @@ -2,10 +2,9 @@ use std::process::ExitStatus; use serde::Serialize; -use crate::outlier_detection::modified_zscores; use crate::quantity::{ - max, mean, median, min, second, serialize_information, serialize_time, standard_deviation, - Information, Time, TimeQuantity, + max, mean, median, min, modified_zscores, serialize_information, serialize_time, + standard_deviation, Information, Time, }; use crate::util::exit_code::extract_exit_code; @@ -103,6 +102,11 @@ impl Measurements { max(&self.wall_clock_times()) } + /// Compute modified Z-scores for the wall clock times + pub fn modified_zscores(&self) -> Vec { + modified_zscores(&self.wall_clock_times()) + } + /// The average user time pub fn time_user_mean(&self) -> Time { mean( @@ -126,19 +130,11 @@ impl Measurements { } pub fn peak_memory_usage_mean(&self) -> Information { - self.measurements - .iter() - .map(|m| m.peak_memory_usage) - .max() // TODO: should be mean: do we need InformationF64? - .unwrap_or_default() - } - - pub fn modified_zscores(&self) -> Vec { - modified_zscores( + mean( &self - .wall_clock_times() + .measurements .iter() - .map(|t| t.value_in(second)) // TODO + .map(|m| m.peak_memory_usage) .collect::>(), ) } diff --git a/src/export/csv.rs b/src/export/csv.rs index df94758..f6fb60c 100644 --- a/src/export/csv.rs +++ b/src/export/csv.rs @@ -85,21 +85,21 @@ fn test_csv() { time_wall_clock: Time::new::(7.0), time_user: Time::new::(7.0), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(8.0), time_user: Time::new::(8.0), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(12.0), time_user: Time::new::(12.0), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, ]), @@ -129,21 +129,21 @@ fn test_csv() { time_wall_clock: Time::new::(17.0), time_user: Time::new::(17.0), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(18.0), time_user: Time::new::(18.0), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(19.0), time_user: Time::new::(19.0), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, ]), diff --git a/src/export/tests.rs b/src/export/tests.rs index 2263981..501ebfb 100644 --- a/src/export/tests.rs +++ b/src/export/tests.rs @@ -33,21 +33,21 @@ fn test_markup_export_auto_ms() { time_wall_clock: Time::new::(0.09), time_user: Time::new::(0.09), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(0.10), time_user: Time::new::(0.10), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(0.14), time_user: Time::new::(0.14), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, ]), @@ -60,21 +60,21 @@ fn test_markup_export_auto_ms() { time_wall_clock: Time::new::(2.0), time_user: Time::new::(2.0), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(3.0), time_user: Time::new::(3.0), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(4.0), time_user: Time::new::(4.0), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, ]), @@ -132,21 +132,21 @@ fn test_markup_export_auto_s() { time_wall_clock: Time::new::(2.1), time_user: Time::new::(2.1), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(2.2), time_user: Time::new::(2.2), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(2.3), time_user: Time::new::(2.3), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, ]), @@ -159,21 +159,21 @@ fn test_markup_export_auto_s() { time_wall_clock: Time::new::(0.1), time_user: Time::new::(0.1), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(0.2), time_user: Time::new::(0.2), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(0.3), time_user: Time::new::(0.3), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, ]), @@ -231,21 +231,21 @@ fn test_markup_export_manual_ms() { time_wall_clock: Time::new::(2.1), time_user: Time::new::(2.1), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(2.2), time_user: Time::new::(2.2), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(2.3), time_user: Time::new::(2.3), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, ]), @@ -258,21 +258,21 @@ fn test_markup_export_manual_ms() { time_wall_clock: Time::new::(0.1), time_user: Time::new::(0.1), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(0.2), time_user: Time::new::(0.2), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(0.3), time_user: Time::new::(0.3), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, ]), @@ -329,21 +329,21 @@ fn test_markup_export_manual_s() { time_wall_clock: Time::new::(2.01), time_user: Time::new::(2.01), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(2.02), time_user: Time::new::(2.02), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(2.03), time_user: Time::new::(2.03), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, ]), @@ -356,21 +356,21 @@ fn test_markup_export_manual_s() { time_wall_clock: Time::new::(0.11), time_user: Time::new::(0.11), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(0.12), time_user: Time::new::(0.12), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, Measurement { time_wall_clock: Time::new::(0.13), time_user: Time::new::(0.13), time_system: Time::zero(), - peak_memory_usage: Information::new::(1024), + peak_memory_usage: Information::new::(1024.), exit_status: ExitStatus::default(), }, ]), diff --git a/src/quantity/mod.rs b/src/quantity/mod.rs index b8e3f87..df6db4f 100644 --- a/src/quantity/mod.rs +++ b/src/quantity/mod.rs @@ -9,9 +9,7 @@ pub use uom::si::information::{byte, kibibyte}; pub use uom::si::ratio::ratio; pub use uom::si::time::{hour, microsecond, millisecond, minute, nanosecond, second}; -pub use si::f64::Ratio; -pub use si::f64::Time; -pub use si::u64::Information; +pub use si::f64::{Information, Ratio, Time}; pub use si::f64::{Information, Ratio, Time}; pub use si::information::{byte, gibibyte, kibibyte, mebibyte, tebibyte}; @@ -92,29 +90,29 @@ pub const fn const_time_from_seconds(value: f64) -> Time { pub trait InformationQuantity { fn zero() -> Information { - Information::new::(0) + Information::new::(0.) } - fn value_in(&self, u: U) -> u64 + fn value_in(&self, u: U) -> f64 where - U: si::information::Unit + Conversion>; + U: si::information::Unit + Conversion; fn to_string(&self, u: U) -> String where - U: si::information::Unit + Conversion>; + U: si::information::Unit + Conversion; } impl InformationQuantity for Information { - fn value_in(&self, _u: U) -> u64 + fn value_in(&self, _u: U) -> f64 where - U: si::information::Unit + Conversion>, + U: si::information::Unit + Conversion, { self.get::() } fn to_string(&self, u: U) -> String where - U: si::information::Unit + Conversion>, + U: si::information::Unit + Conversion, { format!( "{}", @@ -123,6 +121,31 @@ impl InformationQuantity for Information { } } +pub trait UnsafeRawValue { + fn unsafe_raw_value(&self) -> f64; + fn unsafe_from_raw_value(value: f64) -> Self; +} + +impl UnsafeRawValue for Time { + fn unsafe_raw_value(&self) -> f64 { + self.value_in(second) + } + + fn unsafe_from_raw_value(value: f64) -> Self { + Time::new::(value) + } +} + +impl UnsafeRawValue for Information { + fn unsafe_raw_value(&self) -> f64 { + self.value_in(byte) + } + + fn unsafe_from_raw_value(value: f64) -> Self { + Information::new::(value) + } +} + pub fn serialize_time(t: &Time, s: S) -> Result where S: Serializer, @@ -145,11 +168,11 @@ where macro_rules! quantity_fn { ($name:ident, $unwrapped_values:ident, $body:expr) => { - pub fn $name(values: &[Time]) -> Time { - let $unwrapped_values: Vec<_> = values.iter().map(|q| q.value_in(second)).collect(); + pub fn $name(values: &[Q]) -> Q { + let $unwrapped_values: Vec<_> = values.iter().map(|q| q.unsafe_raw_value()).collect(); let result_value = $body; - Time::new::(result_value) + Q::unsafe_from_raw_value(result_value) } }; } @@ -163,6 +186,11 @@ quantity_fn!(standard_deviation, values, { statistical::standard_deviation(&values, Some(mean_value)) }); +pub fn modified_zscores(values: &[Q]) -> Vec { + let values: Vec<_> = values.iter().map(|q| q.unsafe_raw_value()).collect(); + crate::outlier_detection::modified_zscores(&values) +} + #[test] fn test_time() { let time = Time::new::(123.4); @@ -177,11 +205,11 @@ fn test_time() { #[test] fn test_information() { - let information = Information::new::(8); - assert_eq!(information.value_in(byte), 8192); + let information = Information::new::(8.); + assert_eq!(information.value_in(byte), 8192.); let information_kib = information.value_in(kibibyte); - assert_eq!(information_kib, 8); + assert_eq!(information_kib, 8.); } #[test] @@ -191,7 +219,7 @@ fn test_format() { assert_eq!(time.format_auto(Some(TimeUnit::MilliSecond)), "123.4 ms"); assert_eq!(time.format_auto(Some(TimeUnit::MicroSecond)), "123400.0 µs"); - let peak_memory_usage = Information::new::(8); + let peak_memory_usage = Information::new::(8.); assert_eq!(peak_memory_usage.to_string(byte), "8192 B"); assert_eq!(peak_memory_usage.to_string(kibibyte), "8 KiB"); } diff --git a/src/timer/unix_timer.rs b/src/timer/unix_timer.rs index 1ff169d..c6d7850 100644 --- a/src/timer/unix_timer.rs +++ b/src/timer/unix_timer.rs @@ -1,6 +1,5 @@ #![cfg(not(windows))] -use std::convert::TryFrom; use std::io; use std::mem::MaybeUninit; use std::os::unix::process::ExitStatusExt; @@ -47,9 +46,12 @@ fn wait4(mut child: Child) -> io::Result<(ExitStatus, ResourceUsage)> { let memory_usage_byte = if cfg!(target_os = "macos") || cfg!(target_os = "ios") { // Linux and *BSD return the value in KibiBytes, Darwin flavors in bytes - Information::new::(u64::try_from(rusage.ru_maxrss).unwrap_or(0)) + // A f64 can represent integers up to 2^53 exactly, so we can represent + // all values up to 2^53 bytes = 8 PiB exactly. Beyond that, our precision + // is larger than 1 byte, but that's considered acceptable. + Information::new::(rusage.ru_maxrss as f64) } else { - Information::new::(u64::try_from(rusage.ru_maxrss).unwrap_or(0)) + Information::new::(rusage.ru_maxrss as f64) }; Ok((