mirror of
https://github.com/sharkdp/hyperfine.git
synced 2026-10-28 08:08:58 +00:00
Merge pull request #961 from sharkdp/codex/uom-quantities
Use uom quantities for benchmark measurements
This commit is contained in:
14 files changed
+288
-118
No files matched your search
Generated
+17
@@ -394,6 +394,7 @@ dependencies = [
|
||||
"shell-words",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"uom",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
@@ -932,6 +933,12 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.26"
|
||||
@@ -950,6 +957,16 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "uom"
|
||||
version = "0.36.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffd36e5350a65d112584053ee91843955826bf9e56ec0d1351214e01f6d7cd9c"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
|
||||
@@ -28,6 +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"] }
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
+26
-21
@@ -7,9 +7,9 @@ use crate::options::{
|
||||
CmdFailureAction, CommandInputPolicy, CommandOutputPolicy, Options, OutputStyleOption, Shell,
|
||||
};
|
||||
use crate::output::progress_bar::get_progress_bar;
|
||||
use crate::quantity::{second, Information, Time, Zero};
|
||||
use crate::timer::execute_and_measure;
|
||||
use crate::util::randomized_environment_offset;
|
||||
use crate::util::units::Second;
|
||||
|
||||
use super::measurement::{Measurement, Measurements};
|
||||
|
||||
@@ -51,7 +51,7 @@ pub trait Executor {
|
||||
/// performing a measurement. This should return the time
|
||||
/// that is being used in addition to the actual runtime
|
||||
/// of the command.
|
||||
fn time_overhead(&self) -> Second;
|
||||
fn time_overhead(&self) -> Time;
|
||||
}
|
||||
|
||||
fn run_command_and_measure_common(
|
||||
@@ -150,8 +150,8 @@ impl Executor for RawExecutor<'_> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn time_overhead(&self) -> Second {
|
||||
0.0
|
||||
fn time_overhead(&self) -> Time {
|
||||
Time::zero()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,9 +203,9 @@ impl Executor for ShellExecutor<'_> {
|
||||
// Subtract shell spawning 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);
|
||||
(result.time_wall_clock - spawning_time.time_wall_clock).max(Time::zero());
|
||||
result.time_user = (result.time_user - spawning_time.time_user).max(Time::zero());
|
||||
result.time_system = (result.time_system - spawning_time.time_system).max(Time::zero());
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
@@ -266,14 +266,14 @@ impl Executor for ShellExecutor<'_> {
|
||||
time_wall_clock: measurements.time_wall_clock_mean(),
|
||||
time_user: measurements.time_user_mean(),
|
||||
time_system: measurements.time_system_mean(),
|
||||
peak_memory_usage: 0,
|
||||
peak_memory_usage: Information::zero(),
|
||||
exit_status: ExitStatus::default(),
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn time_overhead(&self) -> Second {
|
||||
fn time_overhead(&self) -> Time {
|
||||
self.shell_spawning_time.unwrap().time_wall_clock
|
||||
}
|
||||
}
|
||||
@@ -288,13 +288,15 @@ impl MockExecutor {
|
||||
MockExecutor { shell }
|
||||
}
|
||||
|
||||
fn extract_time<S: AsRef<str>>(sleep_command: S) -> Second {
|
||||
fn extract_time<S: AsRef<str>>(sleep_command: S) -> Time {
|
||||
assert!(sleep_command.as_ref().starts_with("sleep "));
|
||||
sleep_command
|
||||
.as_ref()
|
||||
.trim_start_matches("sleep ")
|
||||
.parse::<Second>()
|
||||
.unwrap()
|
||||
Time::new::<second>(
|
||||
sleep_command
|
||||
.as_ref()
|
||||
.trim_start_matches("sleep ")
|
||||
.parse::<f64>()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,9 +322,9 @@ impl Executor for MockExecutor {
|
||||
|
||||
Ok(Measurement {
|
||||
time_wall_clock: Self::extract_time(command.get_command_line()),
|
||||
time_user: 0.0,
|
||||
time_system: 0.0,
|
||||
peak_memory_usage: 0,
|
||||
time_user: Time::zero(),
|
||||
time_system: Time::zero(),
|
||||
peak_memory_usage: Information::zero(),
|
||||
exit_status,
|
||||
})
|
||||
}
|
||||
@@ -331,9 +333,9 @@ impl Executor for MockExecutor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn time_overhead(&self) -> Second {
|
||||
fn time_overhead(&self) -> Time {
|
||||
match &self.shell {
|
||||
None => 0.0,
|
||||
None => Time::zero(),
|
||||
Some(shell) => Self::extract_time(shell),
|
||||
}
|
||||
}
|
||||
@@ -341,5 +343,8 @@ impl Executor for MockExecutor {
|
||||
|
||||
#[test]
|
||||
fn test_mock_executor_extract_time() {
|
||||
assert_eq!(MockExecutor::extract_time("sleep 0.1"), 0.1);
|
||||
assert_eq!(
|
||||
MockExecutor::extract_time("sleep 0.1"),
|
||||
Time::new::<second>(0.1)
|
||||
);
|
||||
}
|
||||
@@ -1,23 +1,23 @@
|
||||
use std::process::ExitStatus;
|
||||
|
||||
use crate::outlier_detection::modified_zscores;
|
||||
use crate::quantity::{second, Information, Time};
|
||||
use crate::util::statistics::{max, mean, median, min, standard_deviation};
|
||||
use crate::util::units::Second;
|
||||
|
||||
/// Performance measurements and exit status from running a single command
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct Measurement {
|
||||
/// Wall clock time
|
||||
pub time_wall_clock: Second,
|
||||
pub time_wall_clock: Time,
|
||||
|
||||
/// Time spent in user mode
|
||||
pub time_user: Second,
|
||||
pub time_user: Time,
|
||||
|
||||
/// Time spent in kernel mode
|
||||
pub time_system: Second,
|
||||
pub time_system: Time,
|
||||
|
||||
/// Maximum amount of memory used, in bytes
|
||||
pub peak_memory_usage: u64,
|
||||
/// Maximum amount of memory used
|
||||
pub peak_memory_usage: Information,
|
||||
|
||||
/// The exit status of the process
|
||||
pub exit_status: ExitStatus,
|
||||
@@ -42,17 +42,17 @@ impl Measurements {
|
||||
self.measurements.push(measurement);
|
||||
}
|
||||
|
||||
pub fn wall_clock_times(&self) -> impl Iterator<Item = Second> + '_ {
|
||||
pub fn wall_clock_times(&self) -> impl Iterator<Item = Time> + '_ {
|
||||
self.measurements.iter().map(|m| m.time_wall_clock)
|
||||
}
|
||||
|
||||
/// The average wall clock time.
|
||||
pub fn time_wall_clock_mean(&self) -> Second {
|
||||
pub fn time_wall_clock_mean(&self) -> Time {
|
||||
mean(self.wall_clock_times())
|
||||
}
|
||||
|
||||
/// The standard deviation of wall clock times, if at least two runs were measured.
|
||||
pub fn stddev(&self) -> Option<Second> {
|
||||
pub fn stddev(&self) -> Option<Time> {
|
||||
if self.len() < 2 {
|
||||
None
|
||||
} else {
|
||||
@@ -62,32 +62,37 @@ impl Measurements {
|
||||
}
|
||||
|
||||
/// The median wall clock time.
|
||||
pub fn median(&self) -> Second {
|
||||
median(&self.wall_clock_times().collect::<Vec<_>>())
|
||||
pub fn median(&self) -> Time {
|
||||
median(self.wall_clock_times())
|
||||
}
|
||||
|
||||
/// The minimum wall clock time.
|
||||
pub fn min(&self) -> Second {
|
||||
pub fn min(&self) -> Time {
|
||||
min(self.wall_clock_times())
|
||||
}
|
||||
|
||||
/// The maximum wall clock time.
|
||||
pub fn max(&self) -> Second {
|
||||
pub fn max(&self) -> Time {
|
||||
max(self.wall_clock_times())
|
||||
}
|
||||
|
||||
/// Compute modified Z-scores for the wall clock times.
|
||||
pub fn modified_zscores(&self) -> Vec<f64> {
|
||||
modified_zscores(&self.wall_clock_times().collect::<Vec<_>>())
|
||||
modified_zscores(
|
||||
&self
|
||||
.wall_clock_times()
|
||||
.map(|t| t.get::<second>())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
|
||||
/// The average user time.
|
||||
pub fn time_user_mean(&self) -> Second {
|
||||
pub fn time_user_mean(&self) -> Time {
|
||||
mean(self.measurements.iter().map(|m| m.time_user))
|
||||
}
|
||||
|
||||
/// The average system time.
|
||||
pub fn time_system_mean(&self) -> Second {
|
||||
pub fn time_system_mean(&self) -> Time {
|
||||
mean(self.measurements.iter().map(|m| m.time_system))
|
||||
}
|
||||
}
|
||||
+32
-22
@@ -18,8 +18,8 @@ use crate::output::format::{format_duration, format_duration_unit};
|
||||
use crate::output::progress_bar::get_progress_bar;
|
||||
use crate::output::warnings::{OutlierWarningOptions, Warnings};
|
||||
use crate::parameter::ParameterNameAndValue;
|
||||
use crate::quantity::{byte, const_time_from_seconds, ratio, second, Time, Zero};
|
||||
use crate::util::exit_code::extract_exit_code;
|
||||
use crate::util::units::Second;
|
||||
use benchmark_result::BenchmarkResult;
|
||||
use measurement::{Measurement, Measurements};
|
||||
|
||||
@@ -29,7 +29,7 @@ use colored::*;
|
||||
use self::executor::Executor;
|
||||
|
||||
/// Threshold for warning about fast execution time
|
||||
pub const MIN_EXECUTION_TIME: Second = 5e-3;
|
||||
pub const MIN_EXECUTION_TIME: Time = const_time_from_seconds(5e-3);
|
||||
|
||||
pub struct Benchmark<'a> {
|
||||
number: usize,
|
||||
@@ -236,7 +236,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(Time::zero(), |res| {
|
||||
res.time_wall_clock + self.executor.time_overhead()
|
||||
});
|
||||
|
||||
@@ -250,7 +250,7 @@ impl<'a> Benchmark<'a> {
|
||||
let success = res.exit_status.success();
|
||||
|
||||
let conclusion_result = run_conclusion_command()?;
|
||||
let conclusion_overhead = conclusion_result.map_or(0.0, |res| {
|
||||
let conclusion_overhead = conclusion_result.map_or(Time::zero(), |res| {
|
||||
res.time_wall_clock + self.executor.time_overhead()
|
||||
});
|
||||
|
||||
@@ -259,7 +259,8 @@ impl<'a> Benchmark<'a> {
|
||||
/ (res.time_wall_clock
|
||||
+ self.executor.time_overhead()
|
||||
+ preparation_overhead
|
||||
+ conclusion_overhead)) as u64;
|
||||
+ conclusion_overhead))
|
||||
.get::<ratio>() as u64;
|
||||
|
||||
let count = {
|
||||
let min = cmp::max(runs_in_min_time, self.options.run_bounds.min);
|
||||
@@ -292,8 +293,10 @@ impl<'a> Benchmark<'a> {
|
||||
run_preparation_command()?;
|
||||
|
||||
let msg = {
|
||||
let mean =
|
||||
format_duration(measurements.time_wall_clock_mean(), self.options.time_unit);
|
||||
let mean = format_duration(
|
||||
measurements.time_wall_clock_mean().get::<second>(),
|
||||
self.options.time_unit,
|
||||
);
|
||||
format!("Current estimate: {}", mean.to_string().green())
|
||||
};
|
||||
|
||||
@@ -336,13 +339,14 @@ impl<'a> Benchmark<'a> {
|
||||
let system_mean = measurements.time_system_mean();
|
||||
|
||||
// Formatting and console output
|
||||
let (mean_str, time_unit) = format_duration_unit(t_mean, self.options.time_unit);
|
||||
let min_str = format_duration(t_min, Some(time_unit));
|
||||
let max_str = format_duration(t_max, Some(time_unit));
|
||||
let (mean_str, time_unit) =
|
||||
format_duration_unit(t_mean.get::<second>(), self.options.time_unit);
|
||||
let min_str = format_duration(t_min.get::<second>(), Some(time_unit));
|
||||
let max_str = format_duration(t_max.get::<second>(), Some(time_unit));
|
||||
let num_str = format!("{t_num} runs");
|
||||
|
||||
let user_str = format_duration(user_mean, Some(time_unit));
|
||||
let system_str = format_duration(system_mean, Some(time_unit));
|
||||
let user_str = format_duration(user_mean.get::<second>(), Some(time_unit));
|
||||
let system_str = format_duration(system_mean.get::<second>(), Some(time_unit));
|
||||
|
||||
if self.options.output_style != OutputStyleOption::Disabled {
|
||||
let mut stdout = io::stdout().lock();
|
||||
@@ -357,7 +361,8 @@ impl<'a> Benchmark<'a> {
|
||||
system_str.blue()
|
||||
)?;
|
||||
} else {
|
||||
let stddev_str = format_duration(t_stddev.unwrap(), Some(time_unit));
|
||||
let stddev_str =
|
||||
format_duration(t_stddev.unwrap().get::<second>(), Some(time_unit));
|
||||
|
||||
console_writeln!(
|
||||
stdout,
|
||||
@@ -440,19 +445,24 @@ impl<'a> Benchmark<'a> {
|
||||
Ok(BenchmarkResult {
|
||||
command: self.command.get_name(),
|
||||
command_with_unused_parameters: self.command.get_name_with_unused_parameters(),
|
||||
mean: t_mean,
|
||||
stddev: t_stddev,
|
||||
median: t_median,
|
||||
user: user_mean,
|
||||
system: system_mean,
|
||||
min: t_min,
|
||||
max: t_max,
|
||||
times: Some(measurements.wall_clock_times().collect()),
|
||||
mean: t_mean.get::<second>(),
|
||||
stddev: t_stddev.map(|t| t.get::<second>()),
|
||||
median: t_median.get::<second>(),
|
||||
user: user_mean.get::<second>(),
|
||||
system: system_mean.get::<second>(),
|
||||
min: t_min.get::<second>(),
|
||||
max: t_max.get::<second>(),
|
||||
times: Some(
|
||||
measurements
|
||||
.wall_clock_times()
|
||||
.map(|t| t.get::<second>())
|
||||
.collect(),
|
||||
),
|
||||
memory_usage_byte: Some(
|
||||
measurements
|
||||
.measurements
|
||||
.iter()
|
||||
.map(|m| m.peak_memory_usage)
|
||||
.map(|m| m.peak_memory_usage.get::<byte>() as u64)
|
||||
.collect(),
|
||||
),
|
||||
exit_codes: measurements
|
||||
|
||||
@@ -25,6 +25,7 @@ pub mod options;
|
||||
pub mod outlier_detection;
|
||||
pub mod output;
|
||||
pub mod parameter;
|
||||
mod quantity;
|
||||
pub mod timer;
|
||||
pub mod util;
|
||||
|
||||
|
||||
+8
-6
@@ -9,7 +9,8 @@ use clap::ArgMatches;
|
||||
|
||||
use crate::command::Commands;
|
||||
use crate::error::OptionsError;
|
||||
use crate::util::units::{Second, Unit};
|
||||
use crate::quantity::{second, Time};
|
||||
use crate::util::units::Unit;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
@@ -203,7 +204,7 @@ pub struct Options {
|
||||
pub warmup_count: u64,
|
||||
|
||||
/// Minimum benchmarking time
|
||||
pub min_benchmarking_time: Second,
|
||||
pub min_benchmarking_time: Time,
|
||||
|
||||
/// Whether or not to ignore non-zero exit codes
|
||||
pub command_failure_action: CmdFailureAction,
|
||||
@@ -253,7 +254,7 @@ impl Default for Options {
|
||||
Options {
|
||||
run_bounds: RunBounds::default(),
|
||||
warmup_count: 0,
|
||||
min_benchmarking_time: 3.0,
|
||||
min_benchmarking_time: Time::new::<second>(3.0),
|
||||
command_failure_action: CmdFailureAction::RaiseError,
|
||||
reference_command: None,
|
||||
reference_name: None,
|
||||
@@ -438,9 +439,10 @@ impl Options {
|
||||
};
|
||||
|
||||
if let Some(time) = matches.get_one::<String>("min-benchmarking-time") {
|
||||
options.min_benchmarking_time = time
|
||||
.parse::<f64>()
|
||||
.map_err(|e| OptionsError::FloatParsingError("min-benchmarking-time", e))?;
|
||||
options.min_benchmarking_time = Time::new::<second>(
|
||||
time.parse::<f64>()
|
||||
.map_err(|e| OptionsError::FloatParsingError("min-benchmarking-time", e))?,
|
||||
);
|
||||
}
|
||||
|
||||
options.command_input_policy = if let Some(path_str) = matches.get_one::<String>("input") {
|
||||
|
||||
@@ -22,13 +22,13 @@ pub fn modified_zscores(xs: &[f64]) -> Vec<f64> {
|
||||
assert!(!xs.is_empty());
|
||||
|
||||
// Compute sample median:
|
||||
let x_median = median(xs);
|
||||
let x_median = median(xs.iter().copied());
|
||||
|
||||
// Compute the absolute deviations from the median:
|
||||
let deviations: Vec<f64> = xs.iter().map(|x| (x - x_median).abs()).collect();
|
||||
|
||||
// Compute median absolute deviation:
|
||||
let mad = median(&deviations);
|
||||
let mad = median(deviations.iter().copied());
|
||||
|
||||
// Handle MAD == 0 case
|
||||
let mad = if mad > 0.0 { mad } else { f64::EPSILON };
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::fmt;
|
||||
|
||||
use crate::benchmark::MIN_EXECUTION_TIME;
|
||||
use crate::output::format::format_duration;
|
||||
use crate::util::units::Second;
|
||||
use crate::quantity::{second, Time};
|
||||
|
||||
pub struct OutlierWarningOptions {
|
||||
pub warmup_in_use: bool,
|
||||
@@ -13,7 +13,7 @@ pub struct OutlierWarningOptions {
|
||||
pub enum Warnings {
|
||||
FastExecutionTime,
|
||||
NonZeroExitCode,
|
||||
SlowInitialRun(Second, OutlierWarningOptions),
|
||||
SlowInitialRun(Time, OutlierWarningOptions),
|
||||
OutliersDetected(OutlierWarningOptions),
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ impl fmt::Display for Warnings {
|
||||
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 * 1e3
|
||||
MIN_EXECUTION_TIME.get::<second>() * 1e3
|
||||
),
|
||||
Warnings::NonZeroExitCode => write!(f, "Ignoring non-zero exit code."),
|
||||
Warnings::SlowInitialRun(time_first_run, ref options) => write!(
|
||||
@@ -34,7 +34,7 @@ impl fmt::Display for Warnings {
|
||||
"The first benchmarking run for this command was significantly slower than the \
|
||||
rest ({time}). This could be caused by (filesystem) caches that were not filled until \
|
||||
after the first run. {hints}",
|
||||
time=format_duration(time_first_run, None),
|
||||
time=format_duration(time_first_run.get::<second>(), None),
|
||||
hints=match (options.warmup_in_use, options.prepare_in_use) {
|
||||
(true, true) => "You are already using both the '--warmup' option as well \
|
||||
as the '--prepare' option. Consider re-running the benchmark on a quiet system. \
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub use uom::num_traits::Zero;
|
||||
pub use uom::si::f64::{Information, Ratio, Time};
|
||||
pub use uom::si::information::byte;
|
||||
pub use uom::si::ratio::ratio;
|
||||
pub use uom::si::time::{microsecond, nanosecond, second};
|
||||
|
||||
pub const fn const_time_from_seconds(value: f64) -> Time {
|
||||
// Quantity::new in uom is not yet const: https://docs.rs/uom/0.36.0/uom/si/struct.Quantity.html
|
||||
Time {
|
||||
dimension: PhantomData,
|
||||
units: PhantomData,
|
||||
value,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
use uom::si::{information::kibibyte, time::millisecond};
|
||||
|
||||
#[test]
|
||||
fn test_time() {
|
||||
let time = Time::new::<millisecond>(123.4);
|
||||
assert_eq!(time.get::<millisecond>(), 123.4);
|
||||
|
||||
let time_s = time.get::<second>();
|
||||
approx::assert_relative_eq!(time_s, 0.1234);
|
||||
|
||||
let time_us = time.get::<microsecond>();
|
||||
approx::assert_relative_eq!(time_us, 123400.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_information() {
|
||||
use uom::si::information::pebibyte;
|
||||
|
||||
let information = Information::new::<kibibyte>(8.);
|
||||
assert_eq!(information.get::<byte>(), 8192.);
|
||||
|
||||
let information_kib = information.get::<kibibyte>();
|
||||
assert_eq!(information_kib, 8.);
|
||||
|
||||
let largest_exactly_representable = Information::new::<byte>(9_007_199_254_740_992.);
|
||||
assert_eq!(largest_exactly_representable.get::<pebibyte>(), 8.);
|
||||
}
|
||||
+15
-13
@@ -3,16 +3,16 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::mem;
|
||||
|
||||
use crate::quantity::{byte, microsecond, Information, Time};
|
||||
use crate::timer::CPUTimes;
|
||||
use crate::util::units::Second;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct CPUInterval {
|
||||
/// Total amount of time spent executing in user mode
|
||||
pub user: Second,
|
||||
pub user: Time,
|
||||
|
||||
/// Total amount of time spent executing in kernel mode
|
||||
pub system: Second,
|
||||
pub system: Time,
|
||||
}
|
||||
|
||||
pub struct CPUTimer {
|
||||
@@ -26,13 +26,13 @@ impl CPUTimer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop(&self) -> (Second, Second, u64) {
|
||||
pub fn stop(&self) -> (Time, Time, Information) {
|
||||
let end_cpu = get_cpu_times();
|
||||
let cpu_interval = cpu_time_interval(&self.start_cpu, &end_cpu);
|
||||
(
|
||||
cpu_interval.user,
|
||||
cpu_interval.system,
|
||||
end_cpu.memory_usage_byte,
|
||||
Information::new::<byte>(end_cpu.memory_usage_byte as f64),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -70,11 +70,13 @@ fn get_cpu_times() -> CPUTimes {
|
||||
/// Compute the time intervals in between two `CPUTimes` snapshots
|
||||
fn cpu_time_interval(start: &CPUTimes, end: &CPUTimes) -> CPUInterval {
|
||||
CPUInterval {
|
||||
user: ((end.user_usec - start.user_usec) as f64) * 1e-6,
|
||||
system: ((end.system_usec - start.system_usec) as f64) * 1e-6,
|
||||
user: Time::new::<microsecond>((end.user_usec - start.user_usec) as f64),
|
||||
system: Time::new::<microsecond>((end.system_usec - start.system_usec) as f64),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::quantity::second;
|
||||
#[cfg(test)]
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
@@ -93,14 +95,14 @@ fn test_cpu_time_interval() {
|
||||
};
|
||||
|
||||
let t_zero = cpu_time_interval(&t_a, &t_a);
|
||||
assert!(t_zero.user.abs() < f64::EPSILON);
|
||||
assert!(t_zero.system.abs() < f64::EPSILON);
|
||||
assert!(t_zero.user.get::<second>().abs() < f64::EPSILON);
|
||||
assert!(t_zero.system.get::<second>().abs() < f64::EPSILON);
|
||||
|
||||
let t_ab = cpu_time_interval(&t_a, &t_b);
|
||||
assert_relative_eq!(0.007655, t_ab.user);
|
||||
assert_relative_eq!(0.015679, t_ab.system);
|
||||
assert_relative_eq!(0.007655, t_ab.user.get::<second>());
|
||||
assert_relative_eq!(0.015679, t_ab.system.get::<second>());
|
||||
|
||||
let t_ba = cpu_time_interval(&t_b, &t_a);
|
||||
assert_relative_eq!(-0.007655, t_ba.user);
|
||||
assert_relative_eq!(-0.015679, t_ba.system);
|
||||
assert_relative_eq!(-0.007655, t_ba.user.get::<second>());
|
||||
assert_relative_eq!(-0.015679, t_ba.system.get::<second>());
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::util::units::Second;
|
||||
use crate::quantity::{nanosecond, second, Time};
|
||||
|
||||
pub struct WallClockTimer {
|
||||
start: Instant,
|
||||
@@ -13,8 +13,10 @@ impl WallClockTimer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop(&self) -> Second {
|
||||
pub fn stop(&self) -> Time {
|
||||
let duration = self.start.elapsed();
|
||||
duration.as_secs() as f64 + f64::from(duration.subsec_nanos()) * 1e-9
|
||||
|
||||
Time::new::<second>(duration.as_secs() as f64)
|
||||
+ Time::new::<nanosecond>(duration.subsec_nanos() as f64)
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ use windows_sys::{
|
||||
},
|
||||
};
|
||||
|
||||
use crate::util::units::Second;
|
||||
use crate::quantity::{microsecond, Information, Time, Zero};
|
||||
|
||||
const HUNDRED_NS_PER_MS: i64 = 10;
|
||||
|
||||
@@ -86,7 +86,7 @@ impl CPUTimer {
|
||||
Self { job_object }
|
||||
}
|
||||
|
||||
pub fn stop(&self) -> (Second, Second, u64) {
|
||||
pub fn stop(&self) -> (Time, Time, Information) {
|
||||
let mut job_object_info =
|
||||
mem::MaybeUninit::<JOBOBJECT_BASIC_ACCOUNTING_INFORMATION>::uninit();
|
||||
|
||||
@@ -114,9 +114,13 @@ impl CPUTimer {
|
||||
// for all active processes associated with the job, as well as all terminated
|
||||
// processes no longer associated with the job, in 100-nanosecond ticks."
|
||||
let kernel: i64 = job_object_info.TotalKernelTime / HUNDRED_NS_PER_MS;
|
||||
(user as f64 * 1e-6, kernel as f64 * 1e-6, 0)
|
||||
(
|
||||
Time::new::<microsecond>(user as f64),
|
||||
Time::new::<microsecond>(kernel as f64),
|
||||
Information::zero(),
|
||||
)
|
||||
} else {
|
||||
(0.0, 0.0, 0)
|
||||
(Time::zero(), Time::zero(), Information::zero())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+103
-27
@@ -1,5 +1,9 @@
|
||||
//! Statistics for non-empty samples of finite measurements.
|
||||
|
||||
use std::ops::{Add, AddAssign, Div};
|
||||
|
||||
use crate::quantity::{byte, ratio, second, Information, Ratio, Time, Zero};
|
||||
|
||||
/// A min function that assumes no NaNs and at least one element
|
||||
pub fn min<Q: PartialOrd>(values: impl IntoIterator<Item = Q>) -> Q {
|
||||
values
|
||||
@@ -17,54 +21,97 @@ pub fn max<Q: PartialOrd>(values: impl IntoIterator<Item = Q>) -> Q {
|
||||
}
|
||||
|
||||
/// Arithmetic mean, summing measurements in their original order.
|
||||
pub fn mean(values: impl IntoIterator<Item = f64>) -> f64 {
|
||||
let mut sum = 0.0;
|
||||
pub fn mean<Q, P>(values: impl IntoIterator<Item = Q>) -> Q
|
||||
where
|
||||
Q: AddAssign + Zero + Div<Ratio, Output = P>,
|
||||
P: Into<Q>,
|
||||
{
|
||||
let mut sum = Q::zero();
|
||||
let mut count = 0usize;
|
||||
for value in values {
|
||||
sum += value;
|
||||
count += 1;
|
||||
}
|
||||
sum / count as f64
|
||||
|
||||
let count = Ratio::new::<ratio>(count as f64);
|
||||
(sum / count).into()
|
||||
}
|
||||
|
||||
/// Median without modifying the input sample.
|
||||
pub fn median(values: &[f64]) -> f64 {
|
||||
pub fn median<Q, P>(values: impl IntoIterator<Item = Q>) -> Q
|
||||
where
|
||||
Q: Copy + PartialOrd + Add<Output = Q> + Div<Ratio, Output = P>,
|
||||
P: Into<Q>,
|
||||
{
|
||||
let mut values = values.into_iter().collect::<Vec<_>>();
|
||||
assert!(
|
||||
!values.is_empty(),
|
||||
"median requires at least one measurement"
|
||||
);
|
||||
let mut sorted = values.to_vec();
|
||||
sorted.sort_unstable_by(|a, b| a.partial_cmp(b).expect("No NaN measurements"));
|
||||
values.sort_unstable_by(|a, b| a.partial_cmp(b).expect("No NaN measurements"));
|
||||
|
||||
let mid = sorted.len() / 2;
|
||||
if sorted.len() % 2 == 1 {
|
||||
sorted[mid]
|
||||
let len = values.len();
|
||||
if len % 2 == 0 {
|
||||
let mid = len / 2;
|
||||
let a = &values[mid - 1];
|
||||
let b = &values[mid];
|
||||
((*b + *a) / Ratio::new::<ratio>(2.)).into()
|
||||
} else {
|
||||
(sorted[mid] + sorted[mid - 1]) / 2.0
|
||||
values[len / 2]
|
||||
}
|
||||
}
|
||||
|
||||
/// This is just sad, but after several hours trying to figure out how to write
|
||||
/// a generic `standard_deviation` function using `uom` quantities, I gave up.
|
||||
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.get::<second>()
|
||||
}
|
||||
|
||||
fn unsafe_from_raw_value(value: f64) -> Self {
|
||||
Time::new::<second>(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl UnsafeRawValue for Information {
|
||||
fn unsafe_raw_value(&self) -> f64 {
|
||||
self.get::<byte>()
|
||||
}
|
||||
|
||||
fn unsafe_from_raw_value(value: f64) -> Self {
|
||||
Information::new::<byte>(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sample standard deviation, using an already computed mean.
|
||||
pub fn standard_deviation(values: &[f64], mean: f64) -> f64 {
|
||||
pub fn standard_deviation<Q: UnsafeRawValue>(values: &[Q], mean: Q) -> Q {
|
||||
let mean = mean.unsafe_raw_value();
|
||||
assert!(
|
||||
values.len() > 1,
|
||||
"standard deviation requires at least two measurements"
|
||||
);
|
||||
let sum = values
|
||||
.iter()
|
||||
.map(|value| (value - mean) * (value - mean))
|
||||
.map(|value| (value.unsafe_raw_value() - mean) * (value.unsafe_raw_value() - mean))
|
||||
.fold(0.0, |sum, deviation| sum + deviation);
|
||||
assert!(sum >= 0.0, "invalid sum of squared deviations");
|
||||
|
||||
// Preserve the operation order of statistical 1.0: dividing by n - 1
|
||||
// before taking the square root also preserves exported floating-point values.
|
||||
(sum / (values.len() as f64 - 1.0)).sqrt()
|
||||
Q::unsafe_from_raw_value((sum / (values.len() as f64 - 1.0)).sqrt())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use approx::assert_relative_eq;
|
||||
use uom::si::information::kibibyte;
|
||||
use uom::si::time::{microsecond, millisecond};
|
||||
|
||||
#[test]
|
||||
fn test_min() {
|
||||
@@ -74,8 +121,12 @@ mod tests {
|
||||
assert_eq!(-1.0, min([-1.0, 1.0]));
|
||||
assert_eq!(-1.0, min([1.0, -1.0, 0.0]));
|
||||
|
||||
let values = vec![1024.0, 2.0, 3.0];
|
||||
assert_eq!(*min(&values), 2.0);
|
||||
let values = vec![
|
||||
Information::new::<kibibyte>(1.0),
|
||||
Information::new::<byte>(2.0),
|
||||
Information::new::<byte>(3.0),
|
||||
];
|
||||
assert_eq!(min(&values).get::<byte>(), 2.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -86,8 +137,12 @@ mod tests {
|
||||
assert_eq!(1.0, max([-1.0, 1.0]));
|
||||
assert_eq!(1.0, max([-1.0, 1.0, 0.0]));
|
||||
|
||||
let values = vec![1.0, 2048.0, 3.0];
|
||||
assert_eq!(*max(&values), 2048.0);
|
||||
let values = vec![
|
||||
Information::new::<byte>(1.0),
|
||||
Information::new::<kibibyte>(2.0),
|
||||
Information::new::<byte>(3.0),
|
||||
];
|
||||
assert_eq!(max(&values).get::<kibibyte>(), 2.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -95,25 +150,46 @@ mod tests {
|
||||
assert_eq!(1.0, mean([1.0]));
|
||||
assert_relative_eq!(2.0, mean([1.0, 3.0]));
|
||||
|
||||
let values = [0.1, 0.2, 0.6];
|
||||
assert_relative_eq!(mean(values), 0.3);
|
||||
let values = [
|
||||
Time::new::<millisecond>(100.0),
|
||||
Time::new::<millisecond>(200.0),
|
||||
Time::new::<microsecond>(600_000.0),
|
||||
];
|
||||
let result = mean(values);
|
||||
assert_relative_eq!(result.get::<millisecond>(), 300.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_median() {
|
||||
assert_eq!(1.0, median(&[1.0]));
|
||||
assert_relative_eq!(2.0, median(&[1.0, 3.0]));
|
||||
assert_eq!(1.0, median([1.0]));
|
||||
assert_relative_eq!(2.0, median([1.0, 3.0]));
|
||||
|
||||
let values = [0.1, 0.2, 0.6];
|
||||
assert_relative_eq!(median(&values), 0.2);
|
||||
let values = [
|
||||
Time::new::<millisecond>(100.0),
|
||||
Time::new::<millisecond>(200.0),
|
||||
Time::new::<microsecond>(600_000.0),
|
||||
];
|
||||
let result = median(values);
|
||||
assert_relative_eq!(result.get::<millisecond>(), 200.0);
|
||||
|
||||
let values = [0.1, 0.2, 0.3, 0.6];
|
||||
assert_relative_eq!(median(&values), 0.25);
|
||||
let values = [
|
||||
Time::new::<millisecond>(100.0),
|
||||
Time::new::<millisecond>(200.0),
|
||||
Time::new::<microsecond>(300_000.0),
|
||||
Time::new::<microsecond>(600_000.0),
|
||||
];
|
||||
let result = median(values);
|
||||
assert_relative_eq!(result.get::<millisecond>(), 250.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_standard_deviation() {
|
||||
let values = [0.1, 0.2, 0.3];
|
||||
assert_relative_eq!(standard_deviation(&values, mean(values)), 0.1);
|
||||
let values = [
|
||||
Time::new::<millisecond>(100.0),
|
||||
Time::new::<millisecond>(200.0),
|
||||
Time::new::<microsecond>(300_000.0),
|
||||
];
|
||||
let result = standard_deviation(&values, mean(values));
|
||||
assert_relative_eq!(result.get::<millisecond>(), 100.0);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user