benchlib/
benching.rs

1use howlong::*;
2use std::fmt::{self, Display};
3use std::fs::File;
4use std::io;
5use std::io::{BufWriter, Write};
6use std::time::Instant;
7
8use rayon::prelude::*;
9use termion::{color, style};
10
11#[derive(Debug, Clone)]
12pub struct BenchDuration {}
13
14#[derive(Debug, Clone)]
15pub struct BenchVec {
16    pub inner: Vec<Duration>,
17}
18
19/// A struct that stores a vector of Durations for benchmarks
20/// and allows some statistical operations on it
21impl BenchVec {
22    /// Creates a new empty BenchVec
23    pub fn new() -> Self {
24        Self { inner: Vec::new() }
25    }
26
27    /// Creates a BenchVec from an existing vector of Durations
28    pub fn from_vec(vec: &Vec<Duration>) -> Self {
29        Self { inner: vec.clone() }
30    }
31
32    /// Adds an element to the BenchVec
33    pub fn push(&mut self, item: Duration) -> &mut Self {
34        self.inner.push(item);
35
36        self
37    }
38
39    /// Appends a different BenchVec to this one
40    pub fn append(&mut self, other: Self) -> &mut Self {
41        self.inner.append(&mut other.inner.clone());
42
43        self
44    }
45
46    /// Returns the length of stored elements
47    pub fn len(&self) -> usize {
48        self.inner.len()
49    }
50
51    /// Returns the sum of all stored elements
52    pub fn sum(&self) -> Duration {
53        self.inner.par_iter().sum::<Duration>()
54    }
55
56    /// Returns the average of all durations
57    pub fn average(&self) -> Duration {
58        self.sum() / self.inner.len() as u32
59    }
60
61    /// Returns the standard deviation of all durations
62    pub fn standard_deviation(&self) -> f64 {
63        (self.sum().as_nanos() as f64 / (self.len() as f64 - 1f64)).sqrt()
64    }
65
66    /// Compares two benchmarks by calculating the average
67    pub fn compare(&self, other: Self) -> DurationDifference {
68        let avg1 = self.average();
69        let avg2 = other.average();
70        if avg1 > avg2 {
71            DurationDifference {
72                inner: avg1 - avg2,
73                positive: true,
74            }
75        } else {
76            DurationDifference {
77                inner: avg2 - avg1,
78                positive: false,
79            }
80        }
81    }
82}
83
84impl Display for BenchVec {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        let avg_duration = self.average();
87        let standard_deviation = self.standard_deviation();
88        write!(
89            f,
90            "{:?} (±{:.2}ns ~ {:.2}%)",
91            avg_duration,
92            standard_deviation,
93            (standard_deviation / avg_duration.as_nanos() as f64) * 100f64
94        )
95    }
96}
97
98#[derive(Debug, Clone)]
99pub struct DurationDifference {
100    pub inner: Duration,
101    pub positive: bool,
102}
103
104impl DurationDifference {
105    pub fn new(left: &BenchVec, right: &BenchVec) -> Self {
106        let left_avg = left.average();
107        let right_avg = right.average();
108        if left_avg > right_avg {
109            Self {
110                inner: left_avg - right_avg,
111                positive: true,
112            }
113        } else {
114            Self {
115                inner: right_avg - left_avg,
116                positive: false,
117            }
118        }
119    }
120}
121
122impl Display for DurationDifference {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        write!(
125            f,
126            "{}{:?}",
127            if self.positive { "+" } else { "-" },
128            self.inner
129        )
130    }
131}
132
133pub struct Bencher {
134    measurements: Vec<BenchVec>,
135    iterations: usize,
136    max_auto_iterations: usize,
137    bench_duration: Duration,
138    writer: Option<BufWriter<File>>,
139}
140
141pub const BENCH_FILE_HEAD: &str = "name\tduration\tstandard_deviation\n";
142
143impl Bencher {
144    pub fn new() -> Self {
145        Self {
146            bench_duration: Self::calculate_bench_duration(),
147            measurements: Vec::new(),
148            iterations: 100,
149            max_auto_iterations: 10000,
150            writer: None,
151        }
152    }
153
154    /// Calculates the time it takes to measure a benchmark
155    fn calculate_bench_duration() -> Duration {
156        let mut durations = BenchVec::new();
157        for _ in 0..1000 {
158            let start = HighResolutionClock::now();
159            durations.push(HighResolutionClock::now() - start);
160        }
161
162        durations.average()
163    }
164
165    /// Sets the number of iterations a benchmark will be run
166    /// If set to 0 it iterates until the standard deviation is below 1%
167    pub fn set_iterations(&mut self, iterations: usize) -> &mut Self {
168        self.iterations = iterations;
169
170        self
171    }
172
173    pub fn set_max_iterations(&mut self, iterations: usize) -> &mut Self {
174        self.max_auto_iterations = iterations;
175
176        self
177    }
178
179    /// Benchmarks a closure a configured number of times.
180    /// The result will be printed to the console with the given name.
181    pub fn bench<T, F: FnMut() -> T>(&mut self, name: &str, mut func: F) -> &mut Self {
182        let mut durations = BenchVec::new();
183        println!(
184            "\n{}{}{}{}",
185            color::Fg(color::LightBlue),
186            style::Bold,
187            name,
188            style::Reset
189        );
190        if self.iterations == 0 {
191            let mut count = 0;
192            while count < self.max_auto_iterations {
193                let start = Instant::now();
194                func();
195                let duration = start.elapsed();
196                if duration > self.bench_duration {
197                    durations.push(duration - self.bench_duration);
198                } else {
199                    durations.push(duration);
200                }
201                if (durations.standard_deviation() / durations.average().as_nanos() as f64) < 0.01
202                    && count > 1
203                {
204                    break;
205                }
206                count += 1;
207            }
208            println!("{}After {} iterations{}", style::Faint, count, style::Reset);
209        } else {
210            for _ in 0..self.iterations {
211                let start = Instant::now();
212                func();
213                let duration = start.elapsed();
214                if duration > self.bench_duration {
215                    durations.push(duration - self.bench_duration);
216                } else {
217                    durations.push(duration);
218                }
219            }
220        }
221        println!("Result: {}", durations);
222        if let Some(writer) = &mut self.writer {
223            let _ = writer.write(
224                format!(
225                    "{}\t{:?}\t{:.2}ns\n",
226                    name,
227                    durations.average(),
228                    durations.standard_deviation()
229                )
230                .as_bytes(),
231            );
232        }
233        self.measurements.push(durations);
234
235        self
236    }
237
238    /// Compares the last two benchmarks
239    /// If the number of benchmarks is below 2 it doesn't do anything
240    pub fn compare(&mut self) -> &mut Self {
241        if self.measurements.len() > 1 {
242            let left = self.measurements.get(self.measurements.len() - 1).unwrap();
243            let right = self.measurements.get(self.measurements.len() - 2).unwrap();
244            let diff = DurationDifference::new(left, right);
245            println!("Difference: {}", diff);
246        }
247
248        self
249    }
250
251    /// Prints the settings of the Bencher
252    pub fn print_settings(&mut self) -> &mut Self {
253        println!(
254            "\n{}{}Benchmarking Settings{}",
255            color::Fg(color::Green),
256            style::Underline,
257            style::Reset
258        );
259        println!("Benchmarking accuracy delay:\t {:?}", self.bench_duration);
260        println!(
261            "Number of iterations:\t {}",
262            if self.iterations > 0 {
263                self.iterations.to_string()
264            } else {
265                "auto".to_string()
266            }
267        );
268        if self.iterations == 0 {
269            println!("Maximum number of iterations: {}", self.max_auto_iterations)
270        }
271
272        self
273    }
274
275    /// Adds a file to write the output to
276    pub fn write_output_to(&mut self, mut writer: BufWriter<File>) -> &mut Self {
277        writer.write(BENCH_FILE_HEAD.as_bytes()).unwrap();
278        self.writer = Some(writer);
279
280        self
281    }
282
283    pub fn flush(&mut self) -> io::Result<()> {
284        if let Some(writer) = &mut self.writer {
285            writer.flush()
286        } else {
287            Ok(())
288        }
289    }
290}