1use std::fmt;
16use std::hint::black_box;
17use std::io::{self, Write as _};
18use std::time::{Duration, Instant, SystemTime};
19
20use win_time_sys::{
21 Clock, CoarseSystemClock, InterruptClock, PerformanceClock, PerformanceCounter,
22 PreciseInterruptClock, PreciseSystemClock, PreciseUnbiasedInterruptClock, TimePoint, Timeline,
23 UnbiasedInterruptClock,
24};
25
26#[cfg(test)]
27mod tests;
28
29const READS_PER_BATCH: u32 = 10_000;
31const BATCHES: usize = 31;
33const STEPS: usize = 5;
35const STEP_BUDGET: Duration = Duration::from_millis(250);
37
38struct Row {
40 name: &'static str,
41 cost: Summary,
43 step: Option<Duration>,
45}
46
47#[derive(Debug, PartialEq)]
49struct Summary {
50 least: f64,
51 median: f64,
52 most: f64,
53}
54
55fn summarize(samples: &mut [f64]) -> Option<Summary> {
57 samples.sort_by(f64::total_cmp);
58 Some(Summary {
59 least: *samples.first()?,
60 median: samples[(samples.len() - 1) / 2],
61 most: *samples.last()?,
62 })
63}
64
65fn smallest_step(readings: &[u128]) -> Option<u128> {
67 readings
68 .windows(2)
69 .filter_map(|pair| pair[1].checked_sub(pair[0]).filter(|step| *step > 0))
70 .min()
71}
72
73fn cost<R>(read: impl Fn() -> R) -> Summary {
75 for _ in 0..READS_PER_BATCH {
76 black_box(read());
77 }
78 let mut samples: Vec<f64> = (0..BATCHES)
79 .map(|_| {
80 let started = Instant::now();
81 for _ in 0..READS_PER_BATCH {
82 black_box(read());
83 }
84 started.elapsed().as_nanos() as f64 / f64::from(READS_PER_BATCH)
85 })
86 .collect();
87 summarize(&mut samples).expect("at least one batch")
88}
89
90fn step(nanos: impl Fn() -> u128) -> Option<Duration> {
92 let started = Instant::now();
93 let mut readings = vec![nanos()];
94 while readings.len() <= STEPS && started.elapsed() < STEP_BUDGET {
95 let next = nanos();
96 if next != *readings.last().expect("one reading") {
97 readings.push(next);
98 }
99 }
100 smallest_step(&readings)
101 .map(|step| Duration::from_nanos(u64::try_from(step).unwrap_or(u64::MAX)))
102}
103
104fn clock_nanos<C: Clock>(clock: &C) -> u128 {
106 let since_zero = clock.now() - TimePoint::from_ticks(0);
107 since_zero.abs_duration().as_nanos()
108}
109
110fn clock_row<C: Clock>(name: &'static str, clock: C) -> Row {
111 Row {
112 name,
113 cost: cost(|| clock.now()),
114 step: step(|| clock_nanos(&clock)),
115 }
116}
117
118fn rows() -> Vec<Row> {
119 let mut rows = vec![
120 clock_row("InterruptClock", InterruptClock),
121 clock_row("PreciseInterruptClock", PreciseInterruptClock),
122 clock_row("UnbiasedInterruptClock", UnbiasedInterruptClock),
123 clock_row(
124 "PreciseUnbiasedInterruptClock",
125 PreciseUnbiasedInterruptClock,
126 ),
127 clock_row("CoarseSystemClock", CoarseSystemClock),
128 clock_row("PreciseSystemClock", PreciseSystemClock),
129 clock_row("PerformanceClock", PerformanceClock),
130 ];
131 let origin = Instant::now();
132 rows.push(Row {
133 name: "std Instant (reference)",
134 cost: cost(Instant::now),
135 step: step(|| origin.elapsed().as_nanos()),
136 });
137 rows.push(Row {
138 name: "std SystemTime (reference)",
139 cost: cost(SystemTime::now),
140 step: step(|| {
141 SystemTime::now()
142 .duration_since(SystemTime::UNIX_EPOCH)
143 .map_or(0, |since| since.as_nanos())
144 }),
145 });
146 rows
147}
148
149fn report(out: &mut impl fmt::Write, frequency: u64, rows: &[Row]) -> fmt::Result {
151 writeln!(out, "win-time-sys clock costs, observed on this machine")?;
152 writeln!(out, "performance counter frequency: {frequency} Hz")?;
153 writeln!(
154 out,
155 "{READS_PER_BATCH} reads per batch, {BATCHES} batches; ns per read as least / median / most"
156 )?;
157 writeln!(out)?;
158 writeln!(
159 out,
160 "{:<32} {:>10} {:>10} {:>10} {:>14}",
161 "clock", "least", "median", "most", "smallest step"
162 )?;
163 for row in rows {
164 let step = row
165 .step
166 .map_or_else(|| "none seen".to_owned(), |step| format!("{step:?}"));
167 writeln!(
168 out,
169 "{:<32} {:>10.1} {:>10.1} {:>10.1} {:>14}",
170 row.name, row.cost.least, row.cost.median, row.cost.most, step
171 )?;
172 }
173 Ok(())
174}
175
176fn main() -> io::Result<()> {
177 let frequency = PerformanceCounter::ticks_per_second().get();
178 let rows = rows();
179 let mut text = String::new();
180 report(&mut text, frequency, &rows).expect("writing to a String does not fail");
181 io::stdout().lock().write_all(text.as_bytes())
182}