fluxbench_core/
measure.rs1use std::sync::OnceLock;
7use std::time::Duration;
8
9#[cfg(target_arch = "x86_64")]
13#[inline(always)]
14fn read_cycles() -> u64 {
15 unsafe {
19 let mut _aux: u32 = 0;
20 std::arch::x86_64::__rdtscp(&mut _aux)
21 }
22}
23
24#[cfg(target_arch = "aarch64")]
26#[inline(always)]
27fn read_cycles() -> u64 {
28 let cnt: u64;
29 unsafe {
33 std::arch::asm!("mrs {}, cntvct_el0", out(reg) cnt, options(nostack, nomem));
34 }
35 cnt
36}
37
38#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
39#[inline(always)]
40fn read_cycles() -> u64 {
41 0
42}
43
44pub const HAS_CYCLE_COUNTER: bool = cfg!(target_arch = "x86_64") || cfg!(target_arch = "aarch64");
46
47#[derive(Debug, Clone, Copy)]
51pub struct Instant {
52 instant: std::time::Instant,
53 tsc: u64,
54}
55
56impl Instant {
57 #[inline(always)]
59 pub fn now() -> Self {
60 let tsc = read_cycles();
61 Self {
62 instant: std::time::Instant::now(),
63 tsc,
64 }
65 }
66
67 #[inline(always)]
69 pub fn elapsed(&self) -> Duration {
70 self.instant.elapsed()
71 }
72
73 #[inline(always)]
75 pub fn cycles(&self) -> u64 {
76 self.tsc
77 }
78}
79
80pub struct Timer {
84 start: Instant,
85 cycles_start: u64,
86}
87
88impl Timer {
89 #[inline(always)]
91 pub fn start() -> Self {
92 let cycles_start = read_cycles();
93 Self {
94 start: Instant::now(),
95 cycles_start,
96 }
97 }
98
99 #[inline(always)]
101 pub fn stop(&self) -> (u64, u64) {
102 let elapsed = self.start.elapsed();
103 let nanos = elapsed.as_nanos() as u64;
104 let cycles = read_cycles().saturating_sub(self.cycles_start);
105 (nanos, cycles)
106 }
107}
108
109const CALIBRATION_PAIRS: usize = 1_000;
113
114const CALIBRATION_BATCHES: usize = 20;
116
117static TIMER_OVERHEAD: OnceLock<(f64, f64)> = OnceLock::new();
119
120fn calibrate_timer() -> (f64, f64) {
125 let mut best_ns = f64::MAX;
126 let mut best_cycles = f64::MAX;
127 for _ in 0..CALIBRATION_BATCHES {
128 let outer = Timer::start();
129 for _ in 0..CALIBRATION_PAIRS {
130 let timer = Timer::start();
131 std::hint::black_box(std::hint::black_box(&timer).stop());
132 }
133 let (ns, cycles) = outer.stop();
134 best_ns = best_ns.min(ns as f64 / CALIBRATION_PAIRS as f64);
135 best_cycles = best_cycles.min(cycles as f64 / CALIBRATION_PAIRS as f64);
136 }
137 (best_ns, best_cycles)
138}
139
140pub fn timer_overhead_ns() -> f64 {
145 TIMER_OVERHEAD.get_or_init(calibrate_timer).0
146}
147
148pub fn cycles_overhead() -> f64 {
152 TIMER_OVERHEAD.get_or_init(calibrate_timer).1
153}
154
155#[cfg(target_os = "linux")]
159pub fn pin_to_cpu(cpu: usize) -> Result<(), std::io::Error> {
160 use std::mem::MaybeUninit;
161
162 unsafe {
163 let mut set = MaybeUninit::<libc::cpu_set_t>::zeroed();
164 let set_ref = set.assume_init_mut();
165
166 libc::CPU_ZERO(set_ref);
167 libc::CPU_SET(cpu, set_ref);
168
169 let result = libc::sched_setaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), set_ref);
170
171 if result == 0 {
172 Ok(())
173 } else {
174 Err(std::io::Error::last_os_error())
175 }
176 }
177}
178
179#[cfg(not(target_os = "linux"))]
180pub fn pin_to_cpu(_cpu: usize) -> Result<(), std::io::Error> {
181 Ok(())
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_instant_elapsed() {
191 let start = Instant::now();
192 std::thread::sleep(Duration::from_millis(10));
193 let elapsed = start.elapsed();
194
195 assert!(elapsed >= Duration::from_millis(5));
197 assert!(elapsed < Duration::from_millis(100));
199 }
200
201 #[test]
202 fn test_timer() {
203 let timer = Timer::start();
204 std::thread::sleep(Duration::from_millis(10));
205 let (nanos, _cycles) = timer.stop();
206
207 assert!(nanos >= 5_000_000);
209 }
210
211 #[test]
212 fn timer_overhead_calibrated_positive_and_small() {
213 let ns = timer_overhead_ns();
214 assert!(ns > 0.0, "timer overhead is {ns} ns");
215 assert!(ns < 1000.0, "timer overhead is {ns} ns");
216 assert_eq!(ns, timer_overhead_ns());
218 assert!(cycles_overhead() >= 0.0);
219 }
220
221 #[test]
222 fn test_cycle_counter() {
223 if HAS_CYCLE_COUNTER {
224 let a = read_cycles();
225 let b = read_cycles();
226 assert!(b >= a, "cycle counter should be monotonic");
227 }
228 }
229}