use std::sync::atomic::{AtomicU64, Ordering};
use std::time::SystemTime;
fn get_time() -> u64 {
unsafe { core::arch::x86_64::_rdtsc() }
}
fn rdsysns() -> u64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("Time went backwards")
.as_nanos() as u64
}
pub struct SimpleHighPrecisionClock {
base_tsc: AtomicU64,
base_ns: AtomicU64,
ns_per_tsc: f64,
calibration_interval_ns: u64,
base_ns_err: i64,
next_calibrate_tsc: u64,
}
impl SimpleHighPrecisionClock {
pub fn new(calibration_interval_ns: u64) -> Self {
let (base_tsc, base_ns, ns_per_tsc) = Self::calibrate_once();
let next_calibrate_tsc = base_tsc + (calibration_interval_ns as f64 / ns_per_tsc) as u64;
Self {
base_tsc: AtomicU64::new(base_tsc),
base_ns: AtomicU64::new(base_ns),
ns_per_tsc,
calibration_interval_ns,
base_ns_err: 0,
next_calibrate_tsc,
}
}
pub fn calibrate(&mut self) {
let current_tsc = get_time();
if current_tsc < self.next_calibrate_tsc {
return;
}
let (tsc, ns) = Self::sync_time();
let calculated_ns = self.tsc_to_ns(tsc);
let ns_err = calculated_ns as i64 - ns as i64;
let expected_err_next = ns_err
+ (ns_err - self.base_ns_err) * self.calibration_interval_ns as i64
/ (ns as i64 - self.base_ns.load(Ordering::SeqCst) as i64 + self.base_ns_err);
self.ns_per_tsc *= 1.0 - (expected_err_next as f64 / self.calibration_interval_ns as f64);
self.save_params(tsc, calculated_ns, ns_err);
}
#[inline]
fn tsc_to_ns(&self, tsc: u64) -> u64 {
let elapsed_cycles = tsc - self.base_tsc.load(Ordering::SeqCst);
self.base_ns.load(Ordering::SeqCst) + (elapsed_cycles as f64 * self.ns_per_tsc) as u64
}
fn calibrate_once() -> (u64, u64, f64) {
let base_tsc = get_time();
let base_ns = rdsysns();
std::thread::sleep(std::time::Duration::from_millis(20));
let new_tsc = get_time();
let new_ns = rdsysns();
let ns_per_tsc = (new_ns - base_ns) as f64 / (new_tsc - base_tsc) as f64;
(base_tsc, base_ns, ns_per_tsc)
}
fn sync_time() -> (u64, u64) {
const SYNC_ATTEMPTS: usize = 10;
let mut min_diff = u64::MAX;
let mut best_tsc = 0;
let mut best_ns = 0;
for _ in 0..SYNC_ATTEMPTS {
let tsc_before = get_time();
let ns = rdsysns();
let tsc_after = get_time();
let diff = tsc_after - tsc_before;
if diff < min_diff {
min_diff = diff;
best_tsc = (tsc_before + tsc_after) / 2;
best_ns = ns;
}
}
(best_tsc, best_ns)
}
fn save_params(&mut self, tsc: u64, calculated_ns: u64, ns_err: i64) {
self.base_ns_err = ns_err;
self.next_calibrate_tsc =
tsc + (self.calibration_interval_ns as f64 / self.ns_per_tsc) as u64;
self.base_tsc.store(tsc, Ordering::SeqCst);
self.base_ns.store(calculated_ns, Ordering::SeqCst);
}
pub fn now(&self) -> u64 {
let current_tsc = get_time();
self.tsc_to_ns(current_tsc)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread::sleep;
use std::time::Duration;
#[test]
fn test_initialization() {
let clock = SimpleHighPrecisionClock::new(10000);
let time_ns = clock.now();
assert!(time_ns > 0, "The initial time should be positive.");
}
#[test]
fn test_increasing_time() {
let clock = SimpleHighPrecisionClock::new(10000);
let time_ns1 = clock.now();
sleep(Duration::from_millis(100));
let time_ns2 = clock.now();
assert!(
time_ns2 > time_ns1,
"Time should increase with each call to now: {} vs. {}",
time_ns2,
time_ns1
);
}
#[test]
fn test_drift_with_consecutive_calls() {
let clock = SimpleHighPrecisionClock::new(10000);
let time_ns1 = clock.now();
sleep(Duration::from_millis(100));
let time_ns2 = clock.now();
let drift = time_ns2 - time_ns1;
assert!(
drift > 90_000_000 && drift < 110_000_000,
"Drift should be close to 100 milliseconds but was {} nanoseconds",
drift
);
}
#[test]
fn test_drift_with_calibration() {
let mut clock = SimpleHighPrecisionClock::new(3_000_000_000);
let initial_time_ns = clock.now();
sleep(Duration::from_secs(2)); let time_ns_after_drift = clock.now();
let drift_before_calibration = (time_ns_after_drift - initial_time_ns) as i64 - 2_000_000_000;
clock.calibrate();
let time_ns_after_calibration = clock.now();
sleep(Duration::from_secs(2)); let time_ns_final = clock.now();
let drift_after_calibration = (time_ns_final - time_ns_after_calibration) as i64 - 2_000_000_000;
assert!(
drift_after_calibration.abs() < drift_before_calibration.abs(),
"Drift should be reduced after calibration."
);
}
#[test]
fn test_multiple_calibrations() {
let mut clock = SimpleHighPrecisionClock::new(1_000_000_000); let mut previous_ns_per_tsc = clock.ns_per_tsc;
for _ in 0..5 {
sleep(Duration::from_secs(1));
clock.calibrate();
let current_ns_per_tsc = clock.ns_per_tsc;
assert_ne!(
previous_ns_per_tsc, current_ns_per_tsc,
"ns_per_tsc should adjust on each calibration call"
);
previous_ns_per_tsc = current_ns_per_tsc;
}
}
}