mirror of
https://github.com/sharkdp/hyperfine.git
synced 2024-11-25 19:19:31 +03:00
Add support for floats in --parameter-scan
This commit is contained in:
parent
737403df35
commit
b10b93f473
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -180,6 +180,7 @@ dependencies = [
|
||||
"csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"indicatif 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rust_decimal 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"statistical 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -516,6 +517,16 @@ name = "rgb"
|
||||
version = "0.8.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rust_decimal"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
@ -757,6 +768,7 @@ dependencies = [
|
||||
"checksum regex-automata 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3ed09217220c272b29ef237a974ad58515bde75f194e3ffa7e6d0bf0f3b01f86"
|
||||
"checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96"
|
||||
"checksum rgb 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "4f089652ca87f5a82a62935ec6172a534066c7b97be003cc8f702ee9a7a59c92"
|
||||
"checksum rust_decimal 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f7a28ded8f10361cefb69a8d8e1d195acf59344150534c165c401d6611cf013d"
|
||||
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997"
|
||||
"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d"
|
||||
|
@ -19,6 +19,7 @@ cfg-if = "0.1.9"
|
||||
csv = "1.1.1"
|
||||
serde = { version = "1.0.99", features = ["derive"] }
|
||||
serde_json = "1.0.40"
|
||||
rust_decimal = "1.0"
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
libc = "0.2"
|
||||
|
@ -1,3 +1,4 @@
|
||||
use rust_decimal::Error as DecimalError;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::num;
|
||||
@ -5,9 +6,11 @@ use std::num;
|
||||
#[derive(Debug)]
|
||||
pub enum ParameterScanError {
|
||||
ParseIntError(num::ParseIntError),
|
||||
ParseDecimalError(DecimalError),
|
||||
EmptyRange,
|
||||
TooLarge,
|
||||
ZeroStep,
|
||||
StepRequired,
|
||||
}
|
||||
|
||||
impl From<num::ParseIntError> for ParameterScanError {
|
||||
@ -16,6 +19,12 @@ impl From<num::ParseIntError> for ParameterScanError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DecimalError> for ParameterScanError {
|
||||
fn from(e: DecimalError) -> ParameterScanError {
|
||||
ParameterScanError::ParseDecimalError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ParameterScanError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.description())
|
||||
@ -26,9 +35,11 @@ impl Error for ParameterScanError {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
ParameterScanError::ParseIntError(ref e) => e.description(),
|
||||
ParameterScanError::ParseDecimalError(ref e) => e.description(),
|
||||
ParameterScanError::EmptyRange => "Empty parameter range",
|
||||
ParameterScanError::TooLarge => "Parameter range is too large",
|
||||
ParameterScanError::ZeroStep => "Zero is not a valid parameter step",
|
||||
ParameterScanError::StepRequired => "Step is required when range bounds are floats",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ pub mod export;
|
||||
pub mod format;
|
||||
pub mod internal;
|
||||
pub mod outlier_detection;
|
||||
pub mod parameter_range;
|
||||
pub mod shell;
|
||||
pub mod timer;
|
||||
pub mod types;
|
||||
|
176
src/hyperfine/parameter_range.rs
Normal file
176
src/hyperfine/parameter_range.rs
Normal file
@ -0,0 +1,176 @@
|
||||
use crate::hyperfine::error::ParameterScanError;
|
||||
use crate::hyperfine::types::{Command, NumericType};
|
||||
use clap::Values;
|
||||
use rust_decimal::Decimal;
|
||||
use std::ops::{Add, AddAssign, Div, Sub};
|
||||
use std::str::FromStr;
|
||||
|
||||
trait Numeric:
|
||||
Add<Output = Self>
|
||||
+ Sub<Output = Self>
|
||||
+ Div<Output = Self>
|
||||
+ AddAssign
|
||||
+ PartialOrd
|
||||
+ Copy
|
||||
+ Clone
|
||||
+ From<i32>
|
||||
+ Into<NumericType>
|
||||
{
|
||||
}
|
||||
impl<
|
||||
T: Add<Output = Self>
|
||||
+ Sub<Output = Self>
|
||||
+ Div<Output = Self>
|
||||
+ AddAssign
|
||||
+ PartialOrd
|
||||
+ Copy
|
||||
+ Clone
|
||||
+ From<i32>
|
||||
+ Into<NumericType>,
|
||||
> Numeric for T
|
||||
{
|
||||
}
|
||||
|
||||
struct RangeStep<T> {
|
||||
state: T,
|
||||
end: T,
|
||||
step: T,
|
||||
}
|
||||
|
||||
impl<T: Numeric> RangeStep<T> {
|
||||
fn new(start: T, end: T, step: T) -> RangeStep<T> {
|
||||
RangeStep {
|
||||
state: start,
|
||||
end,
|
||||
step,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric> Iterator for RangeStep<T> {
|
||||
type Item = T;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.state > self.end {
|
||||
return None;
|
||||
}
|
||||
let return_val = self.state;
|
||||
self.state += self.step;
|
||||
|
||||
Some(return_val)
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_params<T: Numeric>(start: T, end: T, step: T) -> Result<(), ParameterScanError> {
|
||||
if end < start {
|
||||
return Err(ParameterScanError::EmptyRange);
|
||||
}
|
||||
|
||||
if step == T::from(0) {
|
||||
return Err(ParameterScanError::ZeroStep);
|
||||
}
|
||||
|
||||
const MAX_PARAMETERS: i32 = 100_000;
|
||||
let steps = (end - start + T::from(1)) / step;
|
||||
if steps > T::from(MAX_PARAMETERS) {
|
||||
return Err(ParameterScanError::TooLarge);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_parameterized_commands<'a, T: Numeric>(
|
||||
param_min: T,
|
||||
param_max: T,
|
||||
step: T,
|
||||
command_strings: Vec<&'a str>,
|
||||
param_name: &'a str,
|
||||
) -> Result<Vec<Command<'a>>, ParameterScanError> {
|
||||
validate_params(param_min, param_max, step)?;
|
||||
let param_range = RangeStep::new(param_min, param_max, step);
|
||||
let mut commands = vec![];
|
||||
|
||||
for value in param_range {
|
||||
for cmd in &command_strings {
|
||||
commands.push(Command::new_parametrized(cmd, param_name, value.into()));
|
||||
}
|
||||
}
|
||||
Ok(commands)
|
||||
}
|
||||
|
||||
pub fn get_parameterized_commands<'a>(
|
||||
command_strings: Values<'a>,
|
||||
mut vals: clap::Values<'a>,
|
||||
step: Option<&str>,
|
||||
) -> Result<Vec<Command<'a>>, ParameterScanError> {
|
||||
let command_strings = command_strings.collect::<Vec<&str>>();
|
||||
let param_name = vals.next().unwrap();
|
||||
let param_min = vals.next().unwrap();
|
||||
let param_max = vals.next().unwrap();
|
||||
|
||||
// attempt to parse as integers
|
||||
if let (Ok(param_min), Ok(param_max)) = (param_min.parse::<i32>(), param_max.parse::<i32>()) {
|
||||
let step = step.unwrap_or("1").parse::<i32>()?;
|
||||
return build_parameterized_commands(
|
||||
param_min,
|
||||
param_max,
|
||||
step,
|
||||
command_strings,
|
||||
param_name,
|
||||
);
|
||||
}
|
||||
|
||||
// try parsing them as decimals
|
||||
let param_min = Decimal::from_str(param_min)?;
|
||||
let param_max = Decimal::from_str(param_max)?;
|
||||
|
||||
if step.is_none() {
|
||||
return Err(ParameterScanError::StepRequired);
|
||||
}
|
||||
|
||||
let step = Decimal::from_str(step.unwrap())?;
|
||||
build_parameterized_commands(param_min, param_max, step, command_strings, param_name)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integer_range() {
|
||||
let param_range: Vec<i32> = RangeStep::new(0, 10, 3).collect();
|
||||
|
||||
assert_eq!(param_range.len(), 4);
|
||||
assert_eq!(param_range[0], 0);
|
||||
assert_eq!(param_range[3], 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decimal_range() {
|
||||
let param_min = Decimal::from(0);
|
||||
let param_max = Decimal::from(1);
|
||||
let step = Decimal::from_str("0.1").unwrap();
|
||||
|
||||
let param_range: Vec<Decimal> = RangeStep::new(param_min, param_max, step).collect();
|
||||
|
||||
assert_eq!(param_range.len(), 11);
|
||||
assert_eq!(param_range[0], Decimal::from(0));
|
||||
assert_eq!(param_range[10], Decimal::from(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_parameterized_commands_int() {
|
||||
let commands =
|
||||
build_parameterized_commands(1i32, 7i32, 3i32, vec!["echo {val}"], "val").unwrap();
|
||||
assert_eq!(commands.len(), 3);
|
||||
assert_eq!(commands[2].get_shell_command(), "echo 7");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_parameterized_commands_decimal() {
|
||||
let param_min = Decimal::from_str("0").unwrap();
|
||||
let param_max = Decimal::from_str("1").unwrap();
|
||||
let step = Decimal::from_str("0.33").unwrap();
|
||||
|
||||
let commands =
|
||||
build_parameterized_commands(param_min, param_max, step, vec!["echo {val}"], "val")
|
||||
.unwrap();
|
||||
assert_eq!(commands.len(), 4);
|
||||
assert_eq!(commands[3].get_shell_command(), "echo 0.99");
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
use rust_decimal::Decimal;
|
||||
/// This module contains common internal types.
|
||||
use serde::*;
|
||||
use std::fmt;
|
||||
@ -10,6 +11,34 @@ pub const DEFAULT_SHELL: &str = "sh";
|
||||
#[cfg(windows)]
|
||||
pub const DEFAULT_SHELL: &str = "cmd.exe";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Copy)]
|
||||
#[serde(untagged)]
|
||||
pub enum NumericType {
|
||||
Int(i32),
|
||||
Decimal(Decimal),
|
||||
}
|
||||
|
||||
impl fmt::Display for NumericType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
NumericType::Int(i) => fmt::Display::fmt(&i, f),
|
||||
NumericType::Decimal(i) => fmt::Display::fmt(&i, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<NumericType> for i32 {
|
||||
fn into(self) -> NumericType {
|
||||
NumericType::Int(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<NumericType> for Decimal {
|
||||
fn into(self) -> NumericType {
|
||||
NumericType::Decimal(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A command that should be benchmarked.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Command<'a> {
|
||||
@ -17,7 +46,7 @@ pub struct Command<'a> {
|
||||
expression: &'a str,
|
||||
|
||||
/// A possible parameter value.
|
||||
parameter: Option<(&'a str, i32)>,
|
||||
parameter: Option<(&'a str, NumericType)>,
|
||||
}
|
||||
|
||||
impl<'a> Command<'a> {
|
||||
@ -28,7 +57,11 @@ impl<'a> Command<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_parametrized(expression: &'a str, parameter: &'a str, value: i32) -> Command<'a> {
|
||||
pub fn new_parametrized(
|
||||
expression: &'a str,
|
||||
parameter: &'a str,
|
||||
value: NumericType,
|
||||
) -> Command<'a> {
|
||||
Command {
|
||||
expression,
|
||||
parameter: Some((parameter, value)),
|
||||
@ -45,7 +78,7 @@ impl<'a> Command<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_parameter(&self) -> Option<(&'a str, i32)> {
|
||||
pub fn get_parameter(&self) -> Option<(&'a str, NumericType)> {
|
||||
self.parameter
|
||||
}
|
||||
}
|
||||
@ -180,7 +213,7 @@ pub struct BenchmarkResult {
|
||||
|
||||
/// Any parameter used
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub parameter: Option<i32>,
|
||||
pub parameter: Option<NumericType>,
|
||||
}
|
||||
|
||||
impl BenchmarkResult {
|
||||
@ -195,7 +228,7 @@ impl BenchmarkResult {
|
||||
min: Second,
|
||||
max: Second,
|
||||
times: Vec<Second>,
|
||||
parameter: Option<i32>,
|
||||
parameter: Option<NumericType>,
|
||||
) -> Self {
|
||||
BenchmarkResult {
|
||||
command,
|
||||
|
49
src/main.rs
49
src/main.rs
@ -2,8 +2,6 @@ use std::cmp;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::iter::StepBy;
|
||||
use std::ops::Range;
|
||||
|
||||
use atty::Stream;
|
||||
use clap::ArgMatches;
|
||||
@ -13,9 +11,10 @@ mod hyperfine;
|
||||
|
||||
use crate::hyperfine::app::get_arg_matches;
|
||||
use crate::hyperfine::benchmark::{mean_shell_spawning_time, run_benchmark};
|
||||
use crate::hyperfine::error::{OptionsError, ParameterScanError};
|
||||
use crate::hyperfine::error::OptionsError;
|
||||
use crate::hyperfine::export::{ExportManager, ExportType};
|
||||
use crate::hyperfine::internal::write_benchmark_comparison;
|
||||
use crate::hyperfine::parameter_range::get_parameterized_commands;
|
||||
use crate::hyperfine::types::{
|
||||
BenchmarkResult, CmdFailureAction, Command, HyperfineOptions, OutputStyleOption,
|
||||
};
|
||||
@ -42,35 +41,6 @@ fn run(commands: &[Command<'_>], options: &HyperfineOptions) -> io::Result<Vec<B
|
||||
Ok(timing_results)
|
||||
}
|
||||
|
||||
/// A function to read the `--parameter-scan` arguments
|
||||
fn parse_parameter_scan_args<'a>(
|
||||
mut vals: clap::Values<'a>,
|
||||
step_size: &str,
|
||||
) -> Result<(&'a str, StepBy<Range<i32>>), ParameterScanError> {
|
||||
let param_name = vals.next().unwrap();
|
||||
let param_min: i32 = vals.next().unwrap().parse()?;
|
||||
let param_max: i32 = vals.next().unwrap().parse()?;
|
||||
let step_size = step_size.parse()?;
|
||||
|
||||
const MAX_PARAMETERS: i32 = 100_000;
|
||||
if param_max - param_min > MAX_PARAMETERS {
|
||||
return Err(ParameterScanError::TooLarge);
|
||||
}
|
||||
|
||||
if param_max < param_min {
|
||||
return Err(ParameterScanError::EmptyRange);
|
||||
}
|
||||
|
||||
if step_size == 0 {
|
||||
// Iterator::step_by panics for zero step
|
||||
return Err(ParameterScanError::ZeroStep);
|
||||
}
|
||||
|
||||
let param_range = (param_min..(param_max + 1)).step_by(step_size);
|
||||
|
||||
Ok((param_name, param_range))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let matches = get_arg_matches(env::args_os());
|
||||
let options = build_hyperfine_options(&matches);
|
||||
@ -216,18 +186,9 @@ fn build_commands<'a>(matches: &'a ArgMatches<'_>) -> Vec<Command<'a>> {
|
||||
let command_strings = matches.values_of("command").unwrap();
|
||||
|
||||
if let Some(args) = matches.values_of("parameter-scan") {
|
||||
let step_size = matches.value_of("parameter-step-size").unwrap_or("1");
|
||||
match parse_parameter_scan_args(args, step_size) {
|
||||
Ok((param_name, param_range)) => {
|
||||
let mut commands = vec![];
|
||||
let command_strings = command_strings.collect::<Vec<&str>>();
|
||||
for value in param_range {
|
||||
for cmd in &command_strings {
|
||||
commands.push(Command::new_parametrized(cmd, param_name, value));
|
||||
}
|
||||
}
|
||||
commands
|
||||
}
|
||||
let step_size = matches.value_of("parameter-step-size");
|
||||
match get_parameterized_commands(command_strings, args, step_size) {
|
||||
Ok(commands) => commands,
|
||||
Err(e) => error(e.description()),
|
||||
}
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user