performance_timing/
lib.rs1use std::time::Instant;
2use core::arch::x86_64::_rdtsc;
3use criterion::measurement::Measurement;
4use criterion::measurement::ValueFormatter;
5use criterion::Throughput;
6
7pub fn const_cycle_loop(mut cycles: u64) -> u64 {
14 assert_eq!(cycles % 4, 0);
15 assert!(cycles < 0xFFFFFFFFu64);
16
17 cycles = cycles / 2;
19 while cycles > 0 {
20 cycles = cycles - 1;
21 cycles = cycles & 0x700FFFFFFFFu64;
22 }
23 return cycles;
24}
25
26#[derive(Debug)]
28pub struct FreqInfo {
29 frequency: f32,
31 tsc_scaling: f32
33}
34
35pub struct CPUInfo;
37
38impl CPUInfo {
39 pub fn get_frequency_hz() -> FreqInfo {
43 let tot_cycles = 1_000_000;
44 let start = Instant::now();
45 let ts_s = CPUInfo::get_time_stamp();
47 let r = const_cycle_loop(tot_cycles);
48 let ts_e = CPUInfo::get_time_stamp();
49 let elapsed = start.elapsed();
50 let time_ns = elapsed.as_nanos() + r as u128;
51 let mut freq = (1e9 * tot_cycles as f32) / ( time_ns as f32 );
52 freq = (freq / 50_000_000f32) * 50_000_000f32;
53
54 return FreqInfo {
55 frequency: freq,
56 tsc_scaling: tot_cycles as f32 / (ts_e - ts_s) as f32 };
57 }
58
59 pub fn get_frequency_ghz() -> FreqInfo {
62 let mut r = CPUInfo::get_frequency_hz();
63 r.frequency /= 1e9f32;
64 return r;
65 }
66
67 pub fn get_time_stamp() -> u64 {
70 let r: u64;
71 unsafe {
72 r = _rdtsc();
73 }
74 return r;
75 }
76}
77
78pub struct MeasureRegion {
92 region_name: String,
93 dump_on_drop: bool,
94 num_samples: u64,
95 sum_samples: u64
96}
97
98pub struct MeasureSample<'a> {
101 parent: &'a mut MeasureRegion,
102 start_time: u64,
103 end_time: u64
104}
105
106impl MeasureRegion {
107 pub fn new_named(region_name: String, dump_on_drop: bool) -> Self {
108 MeasureRegion { region_name, dump_on_drop, num_samples: 0, sum_samples: 0 }
109 }
110
111 pub fn new() -> Self {
112 MeasureRegion { region_name: String::from("default_name"), dump_on_drop: false,
113 num_samples: 0, sum_samples: 0 }
114 }
115
116 pub fn get_sample(&mut self) -> MeasureSample {
117 MeasureSample::new(self)
118 }
119
120 pub fn get_average_sample(&self) -> f32 {
121 return self.sum_samples as f32 / self.num_samples as f32;
122 }
123
124 pub fn get_total_time(&self) -> u64 {
126 return self.sum_samples;
127 }
128
129 fn record_sample(&mut self, sample: u64) {
130 self.num_samples += 1;
131 self.sum_samples += sample;
132 }
133}
134
135impl Drop for MeasureRegion {
136 fn drop(&mut self) {
137 if self.dump_on_drop {
138 println!("{}: {} ref.cycles", self.region_name, self.get_average_sample());
139 }
140 }
141}
142
143impl<'a> MeasureSample<'a> {
144
145 pub fn new(parent: &'a mut MeasureRegion) -> Self {
146 MeasureSample { parent, start_time: CPUInfo::get_time_stamp(), end_time: 0 }
147 }
148
149 fn get_value(&self) -> u64 {
151 self.end_time - self.start_time
152 }
153}
154
155impl<'a> Drop for MeasureSample<'a> {
156 fn drop(&mut self) {
157 if self.end_time == 0 {
159 self.end_time = CPUInfo::get_time_stamp();
160 }
161 self.parent.record_sample(self.get_value());
162 }
163}
164
165pub fn measure_function_perf<F>(f: F) -> f32
167where F: Fn() {
168 let min_test: usize = 100;
169 let min_bench_time: u64 = 10_000_000;
170
171 let mut m = MeasureRegion::new();
172
173 while m.get_total_time() < min_bench_time {
174 let _s = m.get_sample();
175 for _ in 0..min_test {
176 f();
177 }
178 }
179 return m.get_average_sample() / min_test as f32;
180}
181
182pub struct CycleInstant {
183 start: u64
184}
185
186impl CycleInstant {
187 pub fn now() -> CycleInstant {
188 CycleInstant { start: CPUInfo::get_time_stamp() }
189 }
190
191 pub fn elapsed(&self) -> u64 {
192 CPUInfo::get_time_stamp() - self.start
193 }
194}
195
196pub struct CriterionCycleCounter;
214
215impl Measurement for CriterionCycleCounter {
216 type Intermediate = CycleInstant;
217 type Value = u64;
218
219 fn start(&self) -> Self::Intermediate {
220 CycleInstant::now()
221 }
222
223 fn end(&self, i: Self::Intermediate) -> Self::Value {
224 i.elapsed()
225 }
226
227 fn add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value {
228 *v1 + *v2
229 }
230
231 fn zero(&self) -> Self::Value {
232 0u64
233 }
234
235 fn to_f64(&self, val: &Self::Value) -> f64 {
236 *val as f64 * CPUInfo::get_frequency_hz().tsc_scaling as f64
237 }
238
239 fn formatter(&self) -> &dyn ValueFormatter {
240 &CriterionCycleCounter
241 }
242}
243
244impl ValueFormatter for CriterionCycleCounter {
245 fn format_value(&self, value: f64) -> String {
246 format!("{:.3} clocks", value)
247 }
248
249 fn format_throughput(&self, throughput: &Throughput, value: f64) -> String {
250 match *throughput {
251 Throughput::Bytes(bytes) => format!(
252 "{} b/c",
253 bytes as f64 / (value)
254 ),
255 Throughput::Elements(elems) => format!(
256 "{} elem/c",
257 elems as f64 / (value)
258 ),
259 }
260 }
261
262 fn scale_values(&self, _typical_value: f64, _values: &mut [f64]) -> &'static str {
263 "clocks"
264 }
265
266 fn scale_throughputs(&self, _typical_value: f64, throughput: &Throughput, _values: &mut [f64]) -> &'static str {
267 match *throughput {
268 Throughput::Bytes(_bytes) => {
269 "b/c"
270 }
271 Throughput::Elements(_elems) => {
272 "elem/c"
273 }
274 }
275 }
276
277 fn scale_for_machines(&self, _values: &mut [f64]) -> &'static str {
278 "clocks"
279 }
280}
281
282pub fn cycle_accurate_config() -> criterion::Criterion<CriterionCycleCounter> {
283 criterion::Criterion::default().with_measurement(CriterionCycleCounter)
284}
285
286#[cfg(test)]
287mod tests {
288 use crate::{CPUInfo, MeasureRegion};
289 use crate::const_cycle_loop;
290
291 #[test]
292 fn it_works() {
293 const_cycle_loop(200_000_000); for _ in 0..10 {
295 println!("Current CPU freq: {}GHz", CPUInfo::get_frequency_hz().frequency / 1e9f32);
296 }
297
298 let mut loop_timing = MeasureRegion::new();
299 for _ in 0..10 {
300 let _s = loop_timing.get_sample();
301 const_cycle_loop(100_000_000);
302 }
303 println!("Timing info: {}",loop_timing.get_average_sample());
304 }
305
306 #[test]
307 fn test_tsc_scaling() {
308 let ckl_cnt = 100_000_000u64;
309 const_cycle_loop(ckl_cnt); let mut loop_timing = MeasureRegion::new();
311 for _ in 0..10 {
312 let _s = loop_timing.get_sample();
313 const_cycle_loop(ckl_cnt);
314 }
315 let cpu_info = CPUInfo::get_frequency_hz();
316
317 let measured_cycles = loop_timing.get_average_sample() * cpu_info.tsc_scaling;
318 let d = (ckl_cnt as f32 - measured_cycles).abs();
319
320 println!("CPU info: {:?}", cpu_info);
321 println!("expected {}, measured {}", ckl_cnt, measured_cycles);
322
323 let accuracy = 0.05f32;
325 assert_eq!(d < (ckl_cnt as f32 * accuracy), true);
326 }
327}