From 677f02161023bfb435e2c1fee48498bca40ebb52 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Fri, 3 Apr 2020 10:57:05 +0200 Subject: [PATCH] Add library content --- .gitignore | 2 + .idea/.gitignore | 2 + .idea/benchlib-rs.iml | 14 +++ .idea/discord.xml | 9 ++ .idea/misc.xml | 6 ++ .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 ++ Cargo.toml | 11 +++ src/benching.rs | 195 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 40 +++++++++ 10 files changed, 293 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/benchlib-rs.iml create mode 100644 .idea/discord.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 Cargo.toml create mode 100644 src/benching.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..5c98b42 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,2 @@ +# Default ignored files +/workspace.xml \ No newline at end of file diff --git a/.idea/benchlib-rs.iml b/.idea/benchlib-rs.iml new file mode 100644 index 0000000..b7b4242 --- /dev/null +++ b/.idea/benchlib-rs.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..59b11d1 --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..28a804d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..eb7ff92 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a31f92a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "benchlib-rs" +version = "0.1.0" +authors = ["Trivernis "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rayon = "1.3.0" +termion = "1.5.5" diff --git a/src/benching.rs b/src/benching.rs new file mode 100644 index 0000000..380aff4 --- /dev/null +++ b/src/benching.rs @@ -0,0 +1,195 @@ +use std::time::{Duration, Instant}; +use std::fmt::{self, Display}; +use rayon::prelude::*; +use termion::{color, style}; + +#[derive(Debug, Clone)] +pub struct BenchVec { + pub inner: Vec, +} + +/// A struct that stores a vector of Durations for benchmarks +/// and allows some statistical operations on it +impl BenchVec { + /// Creates a new empty BenchVec + pub fn new() -> Self { + Self { inner: Vec::new() } + } + + /// Creates a BenchVec from an existing vector of Durations + pub fn from_vec(vec: &Vec) -> Self { + Self { inner: vec.clone() } + } + + /// Adds an element to the BenchVec + pub fn push(&mut self, item: Duration) -> &mut Self { + self.inner.push(item); + + self + } + + /// Appends a different BenchVec to this one + pub fn append(&mut self, other: Self) -> &mut Self { + self.inner.append(&mut other.inner.clone()); + + self + } + + /// Returns the length of stored elements + pub fn len(&self) -> usize { + self.inner.len() + } + + /// Returns the sum of all stored elements + pub fn sum(&self) -> Duration { + self.inner.par_iter().sum::() + } + + /// Returns the average of all durations + pub fn average(&self) -> Duration { + self.sum() / self.inner.len() as u32 + } + + /// Returns the standard deviation of all durations + pub fn standard_deviation(&self) -> f64 { + (self.sum().as_nanos() as f64 / (self.len() as f64 - 1f64)).sqrt() + } + + /// Compares two benchmarks by calculating the average + pub fn compare(&self, other: Self) -> DurationDifference { + let avg1 = self.average(); + let avg2 = other.average(); + if avg1 > avg2 { + DurationDifference { + inner: avg1 - avg2, + positive: true, + } + } else { + DurationDifference { + inner: avg2 - avg1, + positive: false, + } + } + } +} + +impl Display for BenchVec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let avg_duration = self.average(); + let standard_deviation = self.standard_deviation(); + write!( + f, + "{:?} (±{:.2}ns ~ {:.2}%)", + avg_duration, + standard_deviation, + (standard_deviation / avg_duration.as_nanos() as f64) * 100f64 + ) + } +} + +#[derive(Debug, Clone)] +pub struct DurationDifference { + pub inner: Duration, + pub positive: bool, +} + +impl DurationDifference { + pub fn new(left: &BenchVec, right: &BenchVec) -> Self { + let left_avg = left.average(); + let right_avg = right.average(); + if left_avg > right_avg { + Self { + inner: left_avg - right_avg, + positive: true, + } + } else { + Self { + inner: right_avg - left_avg, + positive: false, + } + } + } +} + +impl Display for DurationDifference { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}{:?}", + if self.positive { "+" } else { "-" }, + self.inner + ) + } +} + +pub struct Bencher { + measurements: Vec, + iterations: usize, +} + +const MAX_AUTO_ITERATIONS: usize = 1000; + +impl Bencher { + pub fn new() -> Self { + Self { + measurements: Vec::new(), + iterations: 100, + } + } + + /// Sets the number of iterations a benchmark will be run + /// If set to 0 it iterates until the standard deviation is below 1% + pub fn set_iterations(&mut self, iterations: usize) -> &mut Self { + self.iterations = iterations; + + self + } + + /// Benchmarks a closure a configured number of times. + /// The result will be printed to the console with the given name. + pub fn bench T>(&mut self, name: &str, mut func: F) -> &mut Self { + let mut durations = BenchVec::new(); + println!( + "\n{}{}{}{}", + color::Fg(color::LightBlue), + style::Bold, + name, + style::Reset + ); + if self.iterations == 0 { + let mut count = 0; + while count < MAX_AUTO_ITERATIONS { + let start = Instant::now(); + func(); + durations.push(start.elapsed()); + if (durations.standard_deviation() / durations.average().as_nanos() as f64) < 0.01 && count > 1{ + break; + } + count += 1; + } + } else { + for _ in 0..self.iterations { + let start = Instant::now(); + func(); + durations.push(start.elapsed()); + } + } + println!("Result: {}", durations); + self.measurements.push(durations); + + self + } + + /// Compares the last two benchmarks + /// If the number of benchmarks is below 2 it doesn't do anything + pub fn compare(&mut self) -> &mut Self { + if self.measurements.len() > 1 { + let left = self.measurements.get(self.measurements.len() - 1).unwrap(); + let right = self.measurements.get(self.measurements.len() - 2).unwrap(); + let diff = DurationDifference::new(left, right); + println!("Difference: {}", diff); + } + + self + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..02c315c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,40 @@ +pub mod benching; + +#[cfg(test)] +mod tests { + use super::benching::Bencher; + + #[test] + fn bencher_works() { + let mut bencher = Bencher::new(); + let mut executed = false; + bencher.bench("lol", || executed = true); + assert!(executed) + } + + #[test] + fn bench_iterations() { + let mut bencher = Bencher::new(); + let mut count = 0; + bencher.set_iterations(243); + bencher.bench("lol", || count += 1); + assert_eq!(count, 243); + } + + #[test] + fn bench_auto() { + let mut bencher = Bencher::new(); + let mut count = 0; + bencher.set_iterations(0); + bencher.bench("lol", || count += 1); + assert!(count > 1); + } + + #[test] + fn bench_difference() { + let mut bencher = Bencher::new(); + bencher.bench("lol", || 3*4); + bencher.bench("lol2", || 35*4); + bencher.compare(); + } +}