tiny_bench/
timing.rs

1use crate::output;
2use crate::output::{
3    fallback_to_anonymous_on_invalid_label, ComparedStdout, LabeledOutput, Output, SimpleStdout,
4};
5use std::time::{Duration, Instant};
6
7/// The simplest possible timed function that just runs some `FnMut` closure and returns the time it took
8/// ```
9/// use std::time::Duration;
10/// use tiny_bench::run_timed;
11/// let time = run_timed(|| std::thread::sleep(Duration::from_micros(5)));
12/// assert!(time.as_micros() >= 5);
13/// ```
14pub fn run_timed<T, F: FnMut() -> T>(mut closure: F) -> Duration {
15    let start = Instant::now();
16    (closure)();
17    Instant::now().duration_since(start)
18}
19
20/// Runs some closure `n` times and returns the data gathered
21/// ```
22/// use std::time::Duration;
23/// use tiny_bench::run_timed_times;
24/// let data = run_timed_times(100, || std::thread::sleep(Duration::from_micros(1)));
25/// data.pretty_print();
26/// ```
27pub fn run_timed_times<T, F: FnMut() -> T>(iterations: usize, mut closure: F) -> TimingData {
28    let mut elapsed = Duration::ZERO;
29    let mut min_nanos = u128::MAX;
30    let mut max_nanos = 0;
31    for _ in 0..iterations {
32        let start = Instant::now();
33        closure();
34        let run_elapsed = Instant::now().duration_since(start);
35        let run_elapsed_nanos = run_elapsed.as_nanos();
36        if run_elapsed_nanos < min_nanos {
37            min_nanos = run_elapsed_nanos;
38        }
39        if run_elapsed_nanos > max_nanos {
40            max_nanos = run_elapsed_nanos;
41        }
42        elapsed += run_elapsed;
43    }
44    TimingData {
45        iterations: iterations as u128,
46        min_nanos,
47        max_nanos,
48        elapsed: elapsed.as_nanos(),
49    }
50}
51
52/// Drains an iterator and calls the closure with the yielded value, timing the closure's execution.
53/// ```
54/// use std::time::Duration;
55/// use tiny_bench::run_timed_from_iterator;
56/// let it = (0..100);
57/// let mut v = Vec::with_capacity(100);
58/// let mut counted_iterations = 0;
59/// let data = run_timed_from_iterator(it, |i| {
60///     v.push(i);
61///     counted_iterations += 1;
62/// });
63/// assert_eq!(100, v.len());
64/// assert_eq!(100, counted_iterations);
65/// data.pretty_print();
66/// ```
67pub fn run_timed_from_iterator<T, R, F: FnMut(R) -> T, It>(
68    iterator: It,
69    mut closure: F,
70) -> TimingData
71where
72    It: Iterator<Item = R>,
73{
74    let mut elapsed = Duration::ZERO;
75    let mut min_nanos = u128::MAX;
76    let mut max_nanos = 0;
77    let mut iterations = 0;
78    for v in iterator {
79        let start = Instant::now();
80        closure(v);
81        let run_elapsed = Instant::now().duration_since(start);
82        let run_elapsed_nanos = run_elapsed.as_nanos();
83        if run_elapsed_nanos < min_nanos {
84            min_nanos = run_elapsed_nanos;
85        }
86        if run_elapsed_nanos > max_nanos {
87            max_nanos = run_elapsed_nanos;
88        }
89        elapsed += run_elapsed;
90        iterations += 1;
91    }
92    TimingData {
93        iterations,
94        min_nanos,
95        max_nanos,
96        elapsed: elapsed.as_nanos(),
97    }
98}
99
100/// Data collected after a timed run
101#[derive(Copy, Clone, Debug)]
102#[cfg(feature = "timer")]
103#[cfg_attr(test, derive(Eq, PartialEq))]
104pub struct TimingData {
105    /// The last amount of time elapsed for an iteration
106    pub min_nanos: u128,
107    /// The most amount of time elapsed for an iteration
108    pub max_nanos: u128,
109    /// The total elapsed time for all iterations combined
110    pub elapsed: u128,
111    /// How many iterations were ran
112    pub iterations: u128,
113}
114
115#[cfg(feature = "timer")]
116impl TimingData {
117    /// Print the data with pretty colors to stdout
118    pub fn pretty_print(&self) {
119        output::print_timer_header("anonymous", self);
120        output::timer_print_elapsed(
121            self.min_nanos as f64,
122            self.elapsed as f64 / self.iterations as f64,
123            self.max_nanos as f64,
124        );
125    }
126}
127
128/// A trait for allowing iterators to be used as timers
129pub trait Timeable<It, T>: Sized
130where
131    It: Iterator<Item = T>,
132{
133    /// Time this iterator with an anonymous label
134    /// ```
135    /// use tiny_bench::Timeable;
136    /// let v: Vec<i32> = (0..100)
137    ///     .timed()
138    ///     .collect();
139    /// // Prints results when the iterator has been drained
140    /// assert_eq!(100, v.len());
141    /// ```
142    fn timed(self) -> TimedIterator<It, T, SimpleStdout> {
143        self.timed_labeled("anonymous")
144    }
145
146    /// Time this iterator with a specified label
147    /// ```
148    /// use tiny_bench::Timeable;
149    /// let v: Vec<i32> = (0..100)
150    ///     .timed_labeled("my_iterator_test")
151    ///     .collect();
152    /// // Prints results when the iterator has been drained
153    /// assert_eq!(100, v.len());
154    /// ```
155    fn timed_labeled(self, label: &'static str) -> TimedIterator<It, T, SimpleStdout>;
156
157    /// Time this iterator with an anonymous label and persist the result so that other anonymous
158    /// time results will be compared with it when they run next
159    fn timed_persisted(self) -> TimedIterator<It, T, ComparedStdout> {
160        self.timed_persisted_labeled("anonymous")
161    }
162
163    /// Time this iterator with a custom label to separate different runs for comparison
164    fn timed_persisted_labeled(self, label: &'static str) -> TimedIterator<It, T, ComparedStdout>;
165}
166
167impl<It, T> Timeable<It, T> for It
168where
169    It: Iterator<Item = T>,
170{
171    fn timed_labeled(self, label: &'static str) -> TimedIterator<It, T, SimpleStdout> {
172        TimedIterator::new(
173            self,
174            LabeledOutput::new(fallback_to_anonymous_on_invalid_label(label), SimpleStdout),
175        )
176    }
177
178    fn timed_persisted_labeled(self, label: &'static str) -> TimedIterator<It, T, ComparedStdout> {
179        TimedIterator::new(
180            self,
181            LabeledOutput::new(
182                fallback_to_anonymous_on_invalid_label(label),
183                ComparedStdout,
184            ),
185        )
186    }
187}
188
189/// An iterator that wraps another iterator and times each call to `next`
190pub struct TimedIterator<It, T, O>
191where
192    It: Iterator<Item = T>,
193{
194    inner: It,
195    iterations: u128,
196    min_nanos: u128,
197    max_nanos: u128,
198    elapsed: Duration,
199    out: LabeledOutput<O>,
200}
201
202impl<It, T, O> TimedIterator<It, T, O>
203where
204    It: Iterator<Item = T>,
205{
206    fn new(inner: It, out: LabeledOutput<O>) -> Self {
207        TimedIterator {
208            inner,
209            iterations: 0,
210            min_nanos: u128::MAX,
211            max_nanos: 0,
212            elapsed: Duration::ZERO,
213            out,
214        }
215    }
216}
217
218impl<It, T, O> Iterator for TimedIterator<It, T, O>
219where
220    It: Iterator<Item = T>,
221    O: Output,
222{
223    type Item = T;
224
225    fn next(&mut self) -> Option<Self::Item> {
226        let start = Instant::now();
227        let maybe_item = self.inner.next();
228        let run_elapsed = Instant::now().duration_since(start);
229        if let Some(item) = maybe_item {
230            let run_elapsed_nanos = run_elapsed.as_nanos();
231            if run_elapsed_nanos < self.min_nanos {
232                self.min_nanos = run_elapsed_nanos;
233            }
234            if run_elapsed_nanos > self.max_nanos {
235                self.max_nanos = run_elapsed_nanos;
236            }
237            self.elapsed += run_elapsed;
238            self.iterations += 1;
239            Some(item)
240        } else {
241            self.out.dump(TimingData {
242                min_nanos: self.min_nanos,
243                max_nanos: self.max_nanos,
244                elapsed: self.elapsed.as_nanos(),
245                iterations: self.iterations,
246            });
247            None
248        }
249    }
250}
251
252#[cfg(test)]
253#[cfg(feature = "timer")]
254mod tests {
255    use crate::timing::Timeable;
256
257    #[test]
258    fn time_iterator() {
259        let _v: Vec<i32> = (0..100).timed().chain(0..10_000).timed().collect();
260    }
261
262    #[test]
263    fn time_persisted_iterator() {
264        for _ in 0..2 {
265            let _v: Vec<i32> = (0..1_000_000).timed_persisted().collect();
266        }
267    }
268
269    #[test]
270    fn time_persisted_labled() {
271        for _ in 0..2 {
272            let _v: Vec<i32> = (0..1_000_000).timed_persisted_labeled("my_test").collect();
273        }
274    }
275}