#![allow(unsafe_code)]
use std::sync::atomic::{compiler_fence, Ordering};
use std::sync::Once;
use std::time::Instant;
#[derive(Clone, Copy)]
pub struct CalibrationData {
quotient: u64,
multiplier: u64,
bias_ticks: u64,
epoch_tsc: u64,
cpu_bias_ns: u64,
}
impl CalibrationData {
pub fn calibrate() -> Self {
static ONCE: Once = Once::new();
static mut CACHED: CalibrationData = CalibrationData {
quotient: 0,
multiplier: 0,
bias_ticks: 0,
epoch_tsc: 0,
cpu_bias_ns: 0,
};
unsafe {
ONCE.call_once(|| {
let (quotient, multiplier, epoch_tsc) = calibrate();
let bias_ticks = calibrate_bias();
let cpu_bias_f64_bits = crate::cpu_clock::calibrate_bias();
let cpu_bias_ns = f64::from_bits(cpu_bias_f64_bits).round() as u64;
CACHED = CalibrationData {
quotient,
multiplier,
bias_ticks,
epoch_tsc,
cpu_bias_ns,
};
});
CACHED
}
}
#[cfg(feature = "_test_internals")]
pub fn new_test(quotient: u64, multiplier: u64, bias_ticks: u64, epoch_tsc: u64) -> Self {
CalibrationData {
quotient,
multiplier,
bias_ticks,
epoch_tsc,
cpu_bias_ns: 0,
}
}
#[inline(always)]
pub fn ticks_to_ns(&self, ticks: u64) -> u64 {
if self.quotient == 0 && self.multiplier == 0 {
return 0;
}
let hi = ((ticks as u128 * self.multiplier as u128) >> 64) as u64;
ticks.wrapping_mul(self.quotient).wrapping_add(hi)
}
#[inline(always)]
pub fn now_ns(&self, raw_ticks: u64) -> u64 {
self.ticks_to_ns(raw_ticks.wrapping_sub(self.epoch_tsc))
}
#[inline(always)]
pub fn bias_ns(&self) -> u64 {
self.ticks_to_ns(self.bias_ticks)
}
#[inline(always)]
pub fn cpu_bias_ns(&self) -> u64 {
self.cpu_bias_ns
}
}
#[inline(always)]
pub fn read() -> u64 {
#[cfg(target_arch = "x86_64")]
unsafe {
core::arch::x86_64::_rdtsc()
}
#[cfg(target_arch = "aarch64")]
{
let val: u64;
unsafe { core::arch::asm!("mrs {}, cntvct_el0", out(reg) val) };
val
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
static FALLBACK_ONCE: Once = Once::new();
static mut FALLBACK_EPOCH: Option<Instant> = None;
unsafe {
FALLBACK_ONCE.call_once(|| {
FALLBACK_EPOCH = Some(Instant::now());
});
Instant::now()
.saturating_duration_since(FALLBACK_EPOCH.unwrap_or_else(Instant::now))
.as_nanos() as u64
}
}
}
fn compute_multiplier(remainder: u64, divisor: u64) -> u64 {
if remainder == 0 {
return 0;
}
let product = (remainder as u128) << 64;
let div = product / divisor as u128;
let rem = product % divisor as u128;
if rem > 0 {
(div + 1) as u64
} else {
div as u64
}
}
fn calibrate() -> (u64, u64, u64) {
const CALIBRATION_SPIN_MS: u64 = 2;
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
let _ = CALIBRATION_SPIN_MS;
return (1, 0, 0);
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
{
let wall_start = Instant::now();
let tsc_start = read();
let target = std::time::Duration::from_millis(CALIBRATION_SPIN_MS);
while wall_start.elapsed() < target {}
let tsc_end = read();
let wall_ns = wall_start.elapsed().as_nanos() as u64;
let tsc_ticks = tsc_end.wrapping_sub(tsc_start);
if tsc_ticks == 0 {
return (1, 0, tsc_start);
}
let q = wall_ns / tsc_ticks;
let r = wall_ns % tsc_ticks;
let m = compute_multiplier(r, tsc_ticks);
(q, m, tsc_start)
}
}
fn calibrate_bias() -> u64 {
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
return 0;
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
{
const SAMPLES: usize = 10_000;
const TRIM_PCT: usize = 2; const TRIM_COUNT: usize = SAMPLES * TRIM_PCT / 100;
let mut deltas = Vec::with_capacity(SAMPLES);
for _ in 0..SAMPLES {
let start = read();
compiler_fence(Ordering::SeqCst);
let end = read();
deltas.push(end.wrapping_sub(start));
}
deltas.sort_unstable();
let trimmed = &deltas[TRIM_COUNT..SAMPLES - TRIM_COUNT];
let sum: u64 = trimmed.iter().sum();
sum / trimmed.len() as u64
}
}