test_r_core/
bench.rs

1use crate::stats::{winsorize, Summary};
2use std::cmp::max;
3use std::hint::black_box;
4use std::time::{Duration, Instant};
5
6pub struct Bencher {
7    summary: Option<Summary>,
8    pub bytes: u64,
9}
10
11impl Bencher {
12    pub(crate) fn new() -> Self {
13        Self {
14            summary: None,
15            bytes: 0,
16        }
17    }
18
19    /// Callback for benchmark functions to run in their body.
20    pub fn iter<T, F>(&mut self, mut inner: F)
21    where
22        F: FnMut() -> T,
23    {
24        self.summary = Some(iter(&mut inner));
25    }
26
27    pub(crate) fn summary(&self) -> Option<Summary> {
28        self.summary
29    }
30}
31
32#[cfg(feature = "tokio")]
33pub struct AsyncBencher {
34    summary: Option<Summary>,
35    pub bytes: u64,
36}
37
38#[cfg(feature = "tokio")]
39impl AsyncBencher {
40    pub(crate) fn new() -> Self {
41        Self {
42            summary: None,
43            bytes: 0,
44        }
45    }
46
47    /// Callback for benchmark functions to run in their body.
48    pub async fn iter<T, F>(&mut self, mut inner: F)
49    where
50        F: FnMut() -> std::pin::Pin<Box<dyn std::future::Future<Output = T> + Send>>
51            + Send
52            + Sync
53            + 'static,
54    {
55        self.summary = Some(async_iter(&mut inner).await);
56    }
57
58    pub(crate) fn summary(&self) -> Option<Summary> {
59        self.summary
60    }
61}
62
63fn ns_iter_inner<T, F>(inner: &mut F, k: u64) -> u64
64where
65    F: FnMut() -> T,
66{
67    let start = Instant::now();
68    for _ in 0..k {
69        black_box(inner());
70    }
71    start.elapsed().as_nanos() as u64
72}
73
74#[cfg(feature = "tokio")]
75async fn async_ns_iter_inner<T, F>(inner: &mut F, k: u64) -> u64
76where
77    F: FnMut() -> std::pin::Pin<Box<dyn std::future::Future<Output = T> + Send>>
78        + Send
79        + Sync
80        + 'static,
81{
82    let start = tokio::time::Instant::now();
83    for _ in 0..k {
84        black_box(inner().await);
85    }
86    start.elapsed().as_nanos() as u64
87}
88
89// From https://github.com/rust-lang/rust/blob/master/library/test/src/bench.rs
90pub fn iter<T, F>(inner: &mut F) -> Summary
91where
92    F: FnMut() -> T,
93{
94    // Initial bench run to get ballpark figure.
95    let ns_single = ns_iter_inner(inner, 1);
96
97    // Try to estimate iter count for 1ms falling back to 1m
98    // iterations if first run took < 1ns.
99    let ns_target_total = 1_000_000; // 1ms
100    let mut n = ns_target_total / max(1, ns_single);
101
102    // if the first run took more than 1ms we don't want to just
103    // be left doing 0 iterations on every loop. The unfortunate
104    // side effect of not being able to do as many runs is
105    // automatically handled by the statistical analysis below
106    // (i.e., larger error bars).
107    n = max(1, n);
108
109    let mut total_run = Duration::new(0, 0);
110    let samples: &mut [f64] = &mut [0.0_f64; 50];
111    loop {
112        let loop_start = Instant::now();
113
114        for p in &mut *samples {
115            *p = ns_iter_inner(inner, n) as f64 / n as f64;
116        }
117
118        winsorize(samples, 5.0);
119        let summ = Summary::new(samples);
120
121        for p in &mut *samples {
122            let ns = ns_iter_inner(inner, 5 * n);
123            *p = ns as f64 / (5 * n) as f64;
124        }
125
126        winsorize(samples, 5.0);
127        let summ5 = Summary::new(samples);
128
129        let loop_run = loop_start.elapsed();
130
131        // If we've run for 100ms and seem to have converged to a
132        // stable median.
133        if loop_run > Duration::from_millis(100)
134            && summ.median_abs_dev_pct < 1.0
135            && summ.median - summ5.median < summ5.median_abs_dev
136        {
137            return summ5;
138        }
139
140        total_run += loop_run;
141        // Longest we ever run for is 3s.
142        if total_run > Duration::from_secs(3) {
143            return summ5;
144        }
145
146        // If we overflow here just return the results so far. We check a
147        // multiplier of 10 because we're about to multiply by 2 and the
148        // next iteration of the loop will also multiply by 5 (to calculate
149        // the summ5 result)
150        n = match n.checked_mul(10) {
151            Some(_) => n * 2,
152            None => {
153                return summ5;
154            }
155        };
156    }
157}
158
159#[cfg(feature = "tokio")]
160pub async fn async_iter<T, F>(inner: &mut F) -> Summary
161where
162    F: FnMut() -> std::pin::Pin<Box<dyn std::future::Future<Output = T> + Send>>
163        + Send
164        + Sync
165        + 'static,
166{
167    // Initial bench run to get ballpark figure.
168    let ns_single = async_ns_iter_inner(inner, 1).await;
169
170    // Try to estimate iter count for 1ms falling back to 1m
171    // iterations if first run took < 1ns.
172    let ns_target_total = 1_000_000; // 1ms
173    let mut n = ns_target_total / max(1, ns_single);
174
175    // if the first run took more than 1ms we don't want to just
176    // be left doing 0 iterations on every loop. The unfortunate
177    // side effect of not being able to do as many runs is
178    // automatically handled by the statistical analysis below
179    // (i.e., larger error bars).
180    n = max(1, n);
181
182    let mut total_run = Duration::new(0, 0);
183    let samples: &mut [f64] = &mut [0.0_f64; 50];
184    loop {
185        let loop_start = tokio::time::Instant::now();
186
187        for p in &mut *samples {
188            *p = async_ns_iter_inner(inner, n).await as f64 / n as f64;
189        }
190
191        winsorize(samples, 5.0);
192        let summ = Summary::new(samples);
193
194        for p in &mut *samples {
195            let ns = async_ns_iter_inner(inner, 5 * n).await;
196            *p = ns as f64 / (5 * n) as f64;
197        }
198
199        winsorize(samples, 5.0);
200        let summ5 = Summary::new(samples);
201
202        let loop_run = loop_start.elapsed();
203
204        // If we've run for 100ms and seem to have converged to a
205        // stable median.
206        if loop_run > Duration::from_millis(100)
207            && summ.median_abs_dev_pct < 1.0
208            && summ.median - summ5.median < summ5.median_abs_dev
209        {
210            return summ5;
211        }
212
213        total_run += loop_run;
214        // Longest we ever run for is 3s.
215        if total_run > Duration::from_secs(3) {
216            return summ5;
217        }
218
219        // If we overflow here just return the results so far. We check a
220        // multiplier of 10 because we're about to multiply by 2 and the
221        // next iteration of the loop will also multiply by 5 (to calculate
222        // the summ5 result)
223        n = match n.checked_mul(10) {
224            Some(_) => n * 2,
225            None => {
226                return summ5;
227            }
228        };
229    }
230}