use std::hint::black_box;
use std::mem;
use std::time::{Duration, Instant};
use nexus_timer::Wheel;
const SAMPLES: usize = 50_000;
const WARMUP: usize = 5_000;
const POLL_BATCH: usize = 100;
const STEADY_SIZE: usize = 100_000;
#[inline(always)]
fn rdtsc_start() -> u64 {
unsafe {
std::arch::x86_64::_mm_lfence();
std::arch::x86_64::_rdtsc()
}
}
#[inline(always)]
fn rdtsc_end() -> u64 {
unsafe {
let tsc = std::arch::x86_64::__rdtscp(&mut 0u32 as *mut _);
std::arch::x86_64::_mm_lfence();
tsc
}
}
fn percentile(sorted: &[u64], p: f64) -> u64 {
let idx = ((sorted.len() as f64) * p / 100.0) as usize;
sorted[idx.min(sorted.len() - 1)]
}
fn print_row(label: &str, samples: &mut [u64]) {
samples.sort_unstable();
println!(
" {:<30} p50={:>4} p90={:>4} p99={:>5} p999={:>6} max={:>8}",
label,
percentile(samples, 50.0),
percentile(samples, 90.0),
percentile(samples, 99.0),
percentile(samples, 99.9),
samples[samples.len() - 1],
);
}
fn main() {
let now = Instant::now();
println!(
"TIMER WHEEL LATENCY (cycles/op) — {} samples, {} warmup",
SAMPLES, WARMUP
);
println!("================================================================\n");
let deadline_near = now + Duration::from_millis(50);
let far_future = now + Duration::from_secs(1_000_000);
{
let mut wheel: Wheel<u64> = Wheel::unbounded(4096, now);
let mut samples = Vec::with_capacity(SAMPLES);
for _ in 0..WARMUP {
let h = wheel.schedule(deadline_near, 0);
black_box(wheel.cancel(h));
}
for _ in 0..SAMPLES {
let s = rdtsc_start();
let h = wheel.schedule(deadline_near, 0);
black_box(wheel.cancel(h));
let e = rdtsc_end();
samples.push(e.wrapping_sub(s));
}
print_row("schedule + cancel (paired)", &mut samples);
}
{
let mut wheel: Wheel<u64> = Wheel::unbounded(SAMPLES + WARMUP + 16, now);
let mut samples = Vec::with_capacity(SAMPLES);
for _ in 0..WARMUP {
wheel.schedule_forget(deadline_near, 0);
}
let mut buf = Vec::new();
wheel.poll(far_future, &mut buf);
for _ in 0..SAMPLES {
let s = rdtsc_start();
wheel.schedule_forget(deadline_near, 0);
let e = rdtsc_end();
samples.push(e.wrapping_sub(s));
}
print_row("schedule_forget", &mut samples);
buf.clear();
wheel.poll(far_future, &mut buf);
}
{
let mut wheel: Wheel<u64> = Wheel::unbounded(SAMPLES + WARMUP + 16, now);
let mut samples = Vec::with_capacity(SAMPLES);
for _ in 0..WARMUP {
let h = wheel.schedule(deadline_near, 0);
black_box(wheel.cancel(h));
}
let handles: Vec<_> = (0..SAMPLES)
.map(|i| wheel.schedule(deadline_near, i as u64))
.collect();
for h in handles {
let s = rdtsc_start();
black_box(wheel.cancel(h));
let e = rdtsc_end();
samples.push(e.wrapping_sub(s));
}
print_row("cancel (pre-scheduled)", &mut samples);
}
{
let expired_deadline = now + Duration::from_millis(1);
let poll_time = now + Duration::from_millis(100);
let mut samples = Vec::with_capacity(SAMPLES);
for _ in 0..WARMUP {
let mut wheel: Wheel<u64> = Wheel::unbounded(POLL_BATCH + 16, now);
for i in 0..POLL_BATCH {
wheel.schedule_forget(expired_deadline, i as u64);
}
let mut buf = Vec::with_capacity(POLL_BATCH);
wheel.poll(poll_time, &mut buf);
}
for _ in 0..SAMPLES {
let mut wheel: Wheel<u64> = Wheel::unbounded(POLL_BATCH + 16, now);
for i in 0..POLL_BATCH {
wheel.schedule_forget(expired_deadline, i as u64);
}
let mut buf = Vec::with_capacity(POLL_BATCH);
let s = rdtsc_start();
black_box(wheel.poll(poll_time, &mut buf));
let e = rdtsc_end();
samples.push(e.wrapping_sub(s) / POLL_BATCH as u64);
}
print_row("poll (per entry, 100 batch)", &mut samples);
}
{
let mut wheel: Wheel<u64> = Wheel::unbounded(16, now);
let poll_time = now + Duration::from_millis(100);
let mut samples = Vec::with_capacity(SAMPLES);
let mut buf = Vec::new();
for _ in 0..WARMUP {
black_box(wheel.poll(poll_time, &mut buf));
}
for _ in 0..SAMPLES {
let s = rdtsc_start();
black_box(wheel.poll(poll_time, &mut buf));
let e = rdtsc_end();
samples.push(e.wrapping_sub(s));
}
print_row("poll (empty wheel)", &mut samples);
}
{
let mut wheel: Wheel<u64> = Wheel::unbounded(STEADY_SIZE + WARMUP + 16, now);
let mut steady_handles = Vec::with_capacity(STEADY_SIZE);
for i in 0..STEADY_SIZE {
let offset = Duration::from_millis(1 + (i as u64 % 10_000));
steady_handles.push(wheel.schedule(now + offset, i as u64));
}
let mut samples = Vec::with_capacity(SAMPLES);
for _ in 0..WARMUP {
let h = wheel.schedule(deadline_near, 0);
black_box(wheel.cancel(h));
}
for _ in 0..SAMPLES {
let s = rdtsc_start();
let h = wheel.schedule(deadline_near, 0);
black_box(wheel.cancel(h));
let e = rdtsc_end();
samples.push(e.wrapping_sub(s));
}
print_row("sched+cancel @100k active", &mut samples);
for h in steady_handles {
mem::forget(h);
}
}
}