use crate::{Duration, arch::GuardedArch, time};
use std::{
hint::black_box,
sync::{
LazyLock,
atomic::{Ordering, compiler_fence},
},
};
const OVERHEAD_SAMPLES: usize = 100;
const WARMUP_TIME: std::time::Duration = std::time::Duration::from_millis(50);
static OVERHEAD: LazyLock<Duration> = LazyLock::new(|| {
let stopwatch = Stopwatch::with_overhead(Duration::ZERO);
let mut samples: Vec<Duration> = Vec::with_capacity(OVERHEAD_SAMPLES);
let start_time = std::time::Instant::now();
while start_time.elapsed() < WARMUP_TIME {
for _ in 0..1000 {
black_box(stopwatch.time(black_box(|| {})));
}
}
for _ in 0..OVERHEAD_SAMPLES {
samples.push(stopwatch.time(black_box(|| {})));
}
samples.sort();
let interquartile_start = (samples.len() as f64 * 0.25) as usize;
let interquartile_end = (samples.len() as f64 * 0.75) as usize;
let second_quartile = &samples[interquartile_start..interquartile_end];
let mut second_quartile_sum = Duration::ZERO;
for observation in second_quartile {
second_quartile_sum = second_quartile_sum + *observation;
}
second_quartile_sum / second_quartile.len()
});
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Stopwatch {
arch: GuardedArch,
overhead: Duration,
}
impl Stopwatch {
fn with_overhead(overhead: Duration) -> Stopwatch {
let sw = Stopwatch {
arch: GuardedArch::best(),
overhead,
};
let _ = sw.flushed_now();
sw
}
#[must_use]
pub fn new() -> Stopwatch {
Stopwatch::with_overhead(*OVERHEAD)
}
#[must_use]
pub fn is_hw_supported(&self) -> bool {
self.arch.is_hw_supported()
}
#[must_use]
pub fn overhead(&self) -> Duration {
self.overhead
}
#[must_use]
pub fn time<T, F: FnOnce() -> T>(&self, fun: F) -> Duration {
let start = self.flushed_now();
let retval = black_box(fun());
let end = self.flushed_now();
std::mem::drop(retval); self.arch.elapsed(start, end) - self.overhead
}
#[must_use]
#[inline(always)]
fn flushed_now(&self) -> time::Instant {
self.arch.clock_fence();
compiler_fence(Ordering::SeqCst);
let instant = self.arch.now();
compiler_fence(Ordering::SeqCst);
self.arch.clock_fence();
instant
}
}
impl Default for Stopwatch {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::assert_roughly_eq;
use std::thread::sleep;
use std::time::{Duration, Instant};
const FUDGE_FACTOR: f64 = 0.05;
#[test]
fn basic() {
let sw = Stopwatch::new();
let mut std_nanos = 0.0;
let our_nanos = sw
.time(|| {
let start = Instant::now();
sleep(Duration::from_millis(10));
std_nanos = start.elapsed().as_nanos() as f64;
})
.to_nanos();
assert_roughly_eq(our_nanos, std_nanos, FUDGE_FACTOR);
}
}