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