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