Skip to main content

teaql_tool_std/
high_res_timer.rs

1#[derive(Debug, Clone)]
2pub struct HighResTimerTool;
3
4impl HighResTimerTool {
5    pub fn new() -> Self {
6        Self
7    }
8
9    /// 启动一个高精度计时器
10    pub fn start(&self) -> HighResTimer {
11        HighResTimer::start()
12    }
13}
14
15impl Default for HighResTimerTool {
16    fn default() -> Self {
17        Self::new()
18    }
19}
20
21#[cfg(not(windows))]
22mod imp {
23    use std::sync::OnceLock;
24
25    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
26    use core::arch::x86_64::{_mm_lfence, _rdtsc};
27
28    #[derive(Debug, Clone)]
29    pub struct HighResTimer {
30        start_cycles: u64,
31    }
32
33    static TICK_HZ: OnceLock<u64> = OnceLock::new();
34
35    #[inline(always)]
36    fn global_tick_hz() -> u64 {
37        *TICK_HZ.get_or_init(|| calibrate_tick_hz())
38    }
39
40    impl HighResTimer {
41        pub fn start() -> Self {
42            let _ = global_tick_hz();
43            Self { start_cycles: Self::get_ticks() }
44        }
45
46        #[inline(always)]
47        fn get_ticks() -> u64 {
48            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
49            unsafe {
50                _mm_lfence();
51                let t = _rdtsc();
52                _mm_lfence();
53                return t;
54            }
55
56            #[cfg(target_arch = "aarch64")]
57            {
58                let val: u64;
59                unsafe {
60                    core::arch::asm!("mrs {}, cntvct_el0", out(reg) val);
61                }
62                return val;
63            }
64
65            #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
66            {
67                0
68            }
69        }
70
71        pub fn ns(&self) -> u64 {
72            let end_ticks = Self::get_ticks();
73            let delta = end_ticks.wrapping_sub(self.start_cycles) as u128;
74            ((delta * 1_000_000_000u128) / global_tick_hz() as u128) as u64
75        }
76    }
77
78    fn calibrate_tick_hz() -> u64 {
79        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
80        {
81            return calibrate_tsc_with_monotonic();
82        }
83
84        #[cfg(target_arch = "aarch64")]
85        {
86            return read_cntfrq_el0();
87        }
88        
89        #[allow(unreachable_code)]
90        2_500_000_000
91    }
92
93    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
94    fn calibrate_tsc_with_monotonic() -> u64 {
95        use libc::{clock_gettime, timespec, CLOCK_MONOTONIC};
96
97        unsafe {
98            let mut ts_start = timespec { tv_sec: 0, tv_nsec: 0 };
99            let mut ts_end = timespec { tv_sec: 0, tv_nsec: 0 };
100
101            clock_gettime(CLOCK_MONOTONIC, &mut ts_start);
102            _mm_lfence();
103            let tsc_start = _rdtsc();
104            _mm_lfence();
105
106            spin_wait_ns(10_000_000); // ~10ms
107
108            clock_gettime(CLOCK_MONOTONIC, &mut ts_end);
109            _mm_lfence();
110            let tsc_end = _rdtsc();
111            _mm_lfence();
112
113            let delta_tsc = tsc_end - tsc_start;
114            let delta_ns = (ts_end.tv_sec - ts_start.tv_sec) as u128 * 1_000_000_000u128
115                + (ts_end.tv_nsec - ts_start.tv_nsec) as u128;
116
117            (delta_tsc as u128 * 1_000_000_000u128 / delta_ns) as u64
118        }
119    }
120
121    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
122    #[inline(always)]
123    fn spin_wait_ns(ns: u64) {
124        use libc::{clock_gettime, timespec, CLOCK_MONOTONIC};
125
126        let start = unsafe {
127            let mut ts = timespec { tv_sec: 0, tv_nsec: 0 };
128            clock_gettime(CLOCK_MONOTONIC, &mut ts);
129            ts.tv_sec as u128 * 1_000_000_000u128 + ts.tv_nsec as u128
130        };
131
132        loop {
133            let now = unsafe {
134                let mut ts = timespec { tv_sec: 0, tv_nsec: 0 };
135                clock_gettime(CLOCK_MONOTONIC, &mut ts);
136                ts.tv_sec as u128 * 1_000_000_000u128 + ts.tv_nsec as u128
137            };
138            if now - start >= ns as u128 {
139                break;
140            }
141        }
142    }
143
144    #[cfg(target_arch = "aarch64")]
145    #[inline(always)]
146    fn read_cntfrq_el0() -> u64 {
147        let freq: u64;
148        unsafe {
149            core::arch::asm!("mrs {}, cntfrq_el0", out(reg) freq);
150        }
151        freq
152    }
153}
154
155#[cfg(windows)]
156mod imp {
157    use std::time::Instant;
158
159    #[derive(Debug, Clone)]
160    pub struct HighResTimer {
161        start_time: Instant,
162    }
163
164    impl HighResTimer {
165        pub fn start() -> Self {
166            Self { start_time: Instant::now() }
167        }
168
169        pub fn ns(&self) -> u64 {
170            self.start_time.elapsed().as_nanos() as u64
171        }
172    }
173}
174
175pub use imp::HighResTimer;
176
177impl HighResTimer {
178    #[inline(always)]
179    pub fn us(&self) -> u64 {
180        self.ns() / 1_000
181    }
182
183    #[inline(always)]
184    pub fn ms(&self) -> u64 {
185        self.ns() / 1_000_000
186    }
187}