fastant/
lib.rs

1// Copyright 2021 TiKV Project Authors. Licensed under Apache-2.0.
2
3//! A drop-in replacement for [`std::time::Instant`](https://doc.rust-lang.org/std/time/struct.Instant.html)
4//! that measures time with high performance and high accuracy powered by [Time Stamp Counter (TSC)](https://en.wikipedia.org/wiki/Time_Stamp_Counter).
5//!
6//! ## Example
7//!
8//! ```rust
9//! let start = fastant::Instant::now();
10//! let duration: std::time::Duration = start.elapsed();
11//! ```
12//!
13//! ## Platform Support
14//!
15//! Currently, only the Linux on `x86` or `x86_64` is backed by Time Stamp Counter (TSC).
16//! On other platforms, `fastant` falls back to coarse time.
17//!
18//! ## Calibration
19//!
20//! Time Stamp Counter (TSC) doesn't necessarily tick in constant speed and even doesn't synchronize
21//! across CPU cores. The calibration detects the TSC deviation and calculates the correction
22//! factors with the assistance of a source wall clock. Once the deviation is beyond a crazy
23//! threshold, the calibration will fail, and then we will fall back to coarse time.
24//!
25//! This calibration is stored globally and reused. In order to start the calibration before any
26//! call to `fastant` as to make sure that the time spent on `fastant` is constant, we link the
27//! calibration into application's initialization linker section, so it'll get executed once the
28//! process starts.
29//!
30//! **[See also the `Instant` type](Instant).**
31
32#![cfg_attr(docsrs, feature(doc_cfg))]
33
34mod instant;
35#[cfg(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64")))]
36mod tsc_now;
37
38pub use instant::Anchor;
39#[cfg(all(feature = "atomic", target_has_atomic = "64"))]
40#[cfg_attr(docsrs, doc(cfg(all(feature = "atomic", target_has_atomic = "64"))))]
41pub use instant::Atomic;
42pub use instant::Instant;
43
44/// Return `true` if the current platform supports Time Stamp Counter (TSC),
45/// and the calibration has succeeded.
46///
47/// The result is always the same during the lifetime of the application process.
48#[inline]
49pub fn is_tsc_available() -> bool {
50    #[cfg(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64")))]
51    {
52        tsc_now::is_tsc_available()
53    }
54    #[cfg(not(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64"))))]
55    {
56        false
57    }
58}
59
60#[inline]
61pub(crate) fn current_cycle() -> u64 {
62    #[cfg(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64")))]
63    {
64        if tsc_now::is_tsc_available() {
65            tsc_now::current_cycle()
66        } else {
67            current_cycle_fallback()
68        }
69    }
70    #[cfg(not(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64"))))]
71    {
72        current_cycle_fallback()
73    }
74}
75
76#[cfg(not(feature = "fallback-coarse"))]
77pub(crate) fn current_cycle_fallback() -> u64 {
78    web_time::SystemTime::now()
79        .duration_since(web_time::UNIX_EPOCH)
80        .map(|d| d.as_nanos() as u64)
81        .unwrap_or(0)
82}
83
84#[cfg(feature = "fallback-coarse")]
85pub(crate) fn current_cycle_fallback() -> u64 {
86    let coarse = coarsetime::Instant::now_without_cache_update();
87    coarsetime::Duration::from_ticks(coarse.as_ticks()).as_nanos()
88}
89
90#[inline]
91pub(crate) fn nanos_per_cycle() -> f64 {
92    #[cfg(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64")))]
93    {
94        tsc_now::nanos_per_cycle()
95    }
96    #[cfg(not(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64"))))]
97    {
98        1.0
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use std::time::Duration;
105    use std::time::Instant as StdInstant;
106
107    use rand::Rng;
108    use wasm_bindgen_test::wasm_bindgen_test;
109
110    use super::*;
111
112    #[test]
113    #[wasm_bindgen_test]
114    fn test_is_tsc_available() {
115        let _ = is_tsc_available();
116    }
117
118    #[test]
119    #[wasm_bindgen_test]
120    fn test_monotonic() {
121        let mut prev = 0;
122        for _ in 0..10000 {
123            let cur = current_cycle();
124            assert!(cur >= prev);
125            prev = cur;
126        }
127    }
128
129    #[test]
130    #[wasm_bindgen_test]
131    fn test_nanos_per_cycle() {
132        let _ = nanos_per_cycle();
133    }
134
135    #[test]
136    #[wasm_bindgen_test]
137    fn test_unix_time() {
138        let now = Instant::now();
139        let anchor = Anchor::new();
140        let unix_nanos = now.as_unix_nanos(&anchor);
141        assert!(unix_nanos > 0);
142    }
143
144    #[test]
145    fn test_duration() {
146        let mut rng = rand::rng();
147        for _ in 0..10 {
148            let instant = Instant::now();
149            let std_instant = StdInstant::now();
150            std::thread::sleep(Duration::from_millis(rng.random_range(100..500)));
151            let check = move || {
152                let duration_ns_fastant = instant.elapsed();
153                let duration_ns_std = std_instant.elapsed();
154
155                #[cfg(target_os = "windows")]
156                let expect_max_delta_ns = 40_000_000;
157                #[cfg(not(target_os = "windows"))]
158                let expect_max_delta_ns = 5_000_000;
159
160                let real_delta = (duration_ns_std.as_nanos() as i128
161                    - duration_ns_fastant.as_nanos() as i128)
162                    .abs();
163                assert!(
164                    real_delta < expect_max_delta_ns,
165                    "real delta: {}",
166                    real_delta
167                );
168            };
169            check();
170            std::thread::spawn(check)
171                .join()
172                .expect("failed to join thread");
173        }
174    }
175}