Skip to main content

simoom_sys/
time.rs

1//! Cheap monotonic clocks.
2//!
3//! [`now_ns`] is the precise monotonic clock (`CLOCK_MONOTONIC` through the vDSO on Linux,
4//! `QueryPerformanceCounter` on Windows). [`coarse_ns`] is cheaper and has the resolution of
5//! the kernel tick: `CLOCK_MONOTONIC_COARSE` on Linux (same epoch as [`now_ns`]); elsewhere it
6//! is the precise clock. Both count nanoseconds from an arbitrary fixed point.
7
8use crate::os::sys;
9
10/// Nanoseconds on the precise monotonic clock.
11#[inline]
12pub fn now_ns() -> u64 {
13    #[cfg(unix)]
14    {
15        let mut ts = sys::timespec { tv_sec: 0, tv_nsec: 0 };
16        // SAFETY: `ts` is a valid output struct; CLOCK_MONOTONIC always exists.
17        unsafe { sys::clock_gettime(sys::CLOCK_MONOTONIC, &mut ts) };
18        ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64
19    }
20    #[cfg(windows)]
21    {
22        use std::sync::atomic::{AtomicU64, Ordering};
23        static FREQ: AtomicU64 = AtomicU64::new(0);
24        let mut f = FREQ.load(Ordering::Relaxed);
25        if f == 0 {
26            let mut v = 0i64;
27            // SAFETY: writes one i64; never fails on XP and later.
28            unsafe { sys::QueryPerformanceFrequency(&mut v) };
29            f = v.max(1) as u64;
30            FREQ.store(f, Ordering::Relaxed);
31        }
32        let mut c = 0i64;
33        // SAFETY: writes one i64; never fails on XP and later.
34        unsafe { sys::QueryPerformanceCounter(&mut c) };
35        let c = c as u64;
36        // Split to avoid overflow: whole seconds, then the remainder.
37        (c / f) * 1_000_000_000 + (c % f) * 1_000_000_000 / f
38    }
39}
40
41/// Nanoseconds since the Unix epoch on the wall clock (0 if it is set before 1970). On Linux a
42/// direct `clock_gettime`: `SystemTime` costs code for its error path.
43pub fn wall_ns() -> u64 {
44    #[cfg(target_os = "linux")]
45    {
46        let mut ts = sys::timespec { tv_sec: 0, tv_nsec: 0 };
47        // SAFETY: `ts` is a valid output struct; CLOCK_REALTIME always exists.
48        unsafe { sys::clock_gettime(sys::CLOCK_REALTIME, &mut ts) };
49        if ts.tv_sec < 0 {
50            return 0;
51        }
52        ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64
53    }
54    #[cfg(not(target_os = "linux"))]
55    {
56        std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_or(0, |d| d.as_nanos() as u64)
57    }
58}
59
60/// Nanoseconds on the cheapest monotonic clock (kernel-tick resolution on Linux).
61#[inline]
62pub fn coarse_ns() -> u64 {
63    #[cfg(target_os = "linux")]
64    {
65        let mut ts = sys::timespec { tv_sec: 0, tv_nsec: 0 };
66        // SAFETY: `ts` is a valid output struct; CLOCK_MONOTONIC_COARSE exists since 2.6.32.
67        unsafe { sys::clock_gettime(sys::CLOCK_MONOTONIC_COARSE, &mut ts) };
68        ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64
69    }
70    #[cfg(not(target_os = "linux"))]
71    {
72        now_ns()
73    }
74}
75
76/// CPU time (user and system) used by the whole process so far, in nanoseconds:
77/// `CLOCK_PROCESS_CPUTIME_ID` on Unix, `GetProcessTimes` on Windows (100 ns steps, updated at
78/// the scheduler tick). For measurements such as "an idle server uses no CPU".
79pub fn process_cpu_ns() -> u64 {
80    #[cfg(unix)]
81    {
82        let mut ts = sys::timespec { tv_sec: 0, tv_nsec: 0 };
83        // SAFETY: `ts` is a valid output struct; the clock exists on every supported system.
84        unsafe { sys::clock_gettime(sys::CLOCK_PROCESS_CPUTIME_ID, &mut ts) };
85        ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64
86    }
87    #[cfg(windows)]
88    {
89        let (mut c, mut e, mut k, mut u) = (0u64, 0u64, 0u64, 0u64);
90        // SAFETY: the pseudo handle of the current process; four writable FILETIME-sized slots.
91        unsafe { sys::GetProcessTimes(sys::GetCurrentProcess(), &mut c, &mut e, &mut k, &mut u) };
92        (k + u) * 100
93    }
94}
95
96/// CPU time of the calling thread in nanoseconds (tests only).
97#[cfg(test)]
98pub(crate) fn thread_cpu_ns_for_tests() -> u64 {
99    #[cfg(unix)]
100    {
101        let mut ts = sys::timespec { tv_sec: 0, tv_nsec: 0 };
102        // SAFETY: `ts` is a valid output struct; the clock exists on every supported system.
103        unsafe { sys::clock_gettime(sys::CLOCK_THREAD_CPUTIME_ID, &mut ts) };
104        ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64
105    }
106    #[cfg(windows)]
107    {
108        let (mut c, mut e, mut k, mut u) = (0u64, 0u64, 0u64, 0u64);
109        // SAFETY: the pseudo handle of the current thread; four writable FILETIME-sized slots.
110        unsafe { sys::GetThreadTimes(sys::GetCurrentThread(), &mut c, &mut e, &mut k, &mut u) };
111        (k + u) * 100
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn cpu_clocks_count_work_not_sleep() {
121        let (p0, t0) = (process_cpu_ns(), thread_cpu_ns_for_tests());
122        std::thread::sleep(std::time::Duration::from_millis(50));
123        let t1 = thread_cpu_ns_for_tests();
124        assert!(t1 - t0 < 30_000_000, "sleeping used {} ns of thread CPU", t1 - t0);
125        let end = now_ns() + 60_000_000;
126        let mut x = 0u64;
127        while now_ns() < end {
128            x = x.wrapping_add(1);
129        }
130        let (p2, t2) = (process_cpu_ns(), thread_cpu_ns_for_tests());
131        assert!(t2 - t1 >= 20_000_000, "spinning 60 ms counted {} ns ({x})", t2 - t1);
132        assert!(p2 - p0 >= t2 - t1 - 16_000_000);
133    }
134
135    #[test]
136    fn clocks_are_monotonic_and_track_real_time() {
137        let a = now_ns();
138        let c0 = coarse_ns();
139        std::thread::sleep(std::time::Duration::from_millis(30));
140        let b = now_ns();
141        let c1 = coarse_ns();
142        assert!(b >= a + 25_000_000, "{a} {b}");
143        assert!(b < a + 5_000_000_000);
144        assert!(c1 >= c0 + 10_000_000);
145        // Cost of a read, printed for the record.
146        let n = 1_000_000;
147        let t = std::time::Instant::now();
148        let mut x = 0u64;
149        for _ in 0..n {
150            x = x.wrapping_add(now_ns());
151        }
152        let precise = t.elapsed().as_nanos() as f64 / n as f64;
153        let t = std::time::Instant::now();
154        for _ in 0..n {
155            x = x.wrapping_add(coarse_ns());
156        }
157        let coarse = t.elapsed().as_nanos() as f64 / n as f64;
158        eprintln!("now_ns {precise:.1} ns, coarse_ns {coarse:.1} ns per read ({x})");
159    }
160}