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