1 // Copyright 2012-2016 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10 
11 // Benchmarking code is taken from the bencher crate in the Android tree at
12 // https://android.googlesource.com/platform/external/rust/crates/bencher.
13 // Some modifications have been made to adapt it for use in Rust test TAs
14 // generated by the unittest-rust library.
15 
16 use core::cmp;
17 use core::time::Duration;
18 
19 use crate::stats;
20 
21 /// Manager of the benchmarking runs.
22 ///
23 /// This is fed into functions marked with `#[bench]` to allow for
24 /// set-up & tear-down before running a piece of code repeatedly via a
25 /// call to `iter`.
26 #[derive(Copy, Clone)]
27 pub struct Bencher {
28     iterations: u64,
29     dur: Duration,
30     pub bytes: u64,
31 }
32 
33 // TODO(b/357115387): Replace with std::time::Instant::now().
get_time_ns() -> u6434 fn get_time_ns() -> u64 {
35     let mut secure_time_ns = 0i64;
36     // Safety: external syscall gets valid raw pointer to a `i64` as defined
37     // in the Trusty syscall table at
38     // https://android.googlesource.com/trusty/lk/trusty/+/refs/heads/main/lib/trusty/include/syscall_table.h
39     unsafe { trusty_sys::gettime(0, 0, &mut secure_time_ns) };
40     secure_time_ns as u64
41 }
42 
43 #[derive(Clone, PartialEq)]
44 pub struct BenchSamples {
45     pub ns_iter_summ: stats::Summary,
46     mb_s: usize,
47 }
48 
49 impl Bencher {
50     /// Callback for benchmark functions to run in their body.
iter<T, F>(&mut self, mut inner: F) where F: FnMut() -> T,51     pub fn iter<T, F>(&mut self, mut inner: F)
52     where
53         F: FnMut() -> T,
54     {
55         let start = get_time_ns();
56         let k = self.iterations;
57         for _ in 0..k {
58             core::hint::black_box(inner());
59         }
60         self.dur = Duration::from_nanos(get_time_ns() - start);
61     }
62 
ns_elapsed(&mut self) -> u6463     pub fn ns_elapsed(&mut self) -> u64 {
64         self.dur.as_secs() * 1_000_000_000 + (self.dur.subsec_nanos() as u64)
65     }
66 
ns_per_iter(&mut self) -> u6467     pub fn ns_per_iter(&mut self) -> u64 {
68         if self.iterations == 0 {
69             0
70         } else {
71             self.ns_elapsed() / cmp::max(self.iterations, 1)
72         }
73     }
74 
bench_n<F>(&mut self, n: u64, f: F) where F: FnOnce(&mut Bencher),75     pub fn bench_n<F>(&mut self, n: u64, f: F)
76     where
77         F: FnOnce(&mut Bencher),
78     {
79         self.iterations = n;
80         f(self);
81     }
82 
83     // This is a more statistics-driven benchmark algorithm
auto_bench<F>(&mut self, mut f: F) -> stats::Summary where F: FnMut(&mut Bencher),84     pub fn auto_bench<F>(&mut self, mut f: F) -> stats::Summary
85     where
86         F: FnMut(&mut Bencher),
87     {
88         // Initial bench run to get ballpark figure.
89         let mut n = 1;
90         self.bench_n(n, |x| f(x));
91         let cold = self.ns_per_iter();
92 
93         // Try to estimate iter count for 1ms falling back to 1m
94         // iterations if first run took < 1ns.
95         if cold == 0 {
96             n = 1_000_000;
97         } else {
98             n = 1_000_000 / cmp::max(cold, 1);
99         }
100         // if the first run took more than 1ms we don't want to just
101         // be left doing 0 iterations on every loop. The unfortunate
102         // side effect of not being able to do as many runs is
103         // automatically handled by the statistical analysis below
104         // (i.e. larger error bars).
105         if n == 0 {
106             n = 1;
107         }
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 = get_time_ns();
113 
114             for p in &mut *samples {
115                 self.bench_n(n, |x| f(x));
116                 *p = self.ns_per_iter() as f64;
117             }
118 
119             stats::winsorize(samples, 5.0);
120             let summ = stats::Summary::new(cold as f64, samples);
121 
122             for p in &mut *samples {
123                 self.bench_n(5 * n, |x| f(x));
124                 *p = self.ns_per_iter() as f64;
125             }
126 
127             stats::winsorize(samples, 5.0);
128             let summ5 = stats::Summary::new(cold as f64, samples);
129             let loop_run = Duration::from_nanos(get_time_ns() - loop_start);
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 => return summ5,
153             };
154         }
155     }
156 }
157 
benchmark<F>(f: F) -> BenchSamples where F: FnMut(&mut Bencher),158 pub fn benchmark<F>(f: F) -> BenchSamples
159 where
160     F: FnMut(&mut Bencher),
161 {
162     let mut bs = Bencher { iterations: 0, dur: Duration::new(0, 0), bytes: 0 };
163     let ns_iter_summ = bs.auto_bench(f);
164     let ns_iter = cmp::max(ns_iter_summ.median as u64, 1);
165     let mb_s = bs.bytes * 1000 / ns_iter;
166     BenchSamples { ns_iter_summ: ns_iter_summ, mb_s: mb_s as usize }
167 }
168