rusty_engine/timing.rs
1//! High-performance timing utilities (quanta 쓰는 개빠른 시간 측정)
2//! 캐싱해뒀다가 그거 갖다쓰는거임
3//! This module provides high-precision timing facilities using the quanta crate.
4//! It configures the "recent time" feature that provides ultra-low-overhead access
5//! to a slightly-delayed global time, which can be 4-10x faster than direct time queries.
6
7use fastrace::prelude::*;
8use log::{debug, info, warn};
9use quanta::{Clock, Instant, Upkeep};
10use std::sync::atomic::{AtomicBool, Ordering};
11use std::time::Duration;
12
13/// Singleton upkeep handle to prevent multiple upkeep threads
14static UPKEEP_STARTED: AtomicBool = AtomicBool::new(false);
15
16/// Upkeep handle for the high-performance timing system
17#[derive(Debug)]
18pub struct TimingUpkeep {
19 /// The quanta upkeep handle
20 _handle: Option<quanta::Handle>,
21 /// Whether this upkeep is active
22 active: bool,
23}
24
25impl TimingUpkeep {
26 /// Create and start a new timing upkeep thread
27 ///
28 /// This configures the quanta crate's "recent time" feature, which provides
29 /// ultra-low-overhead access to a slightly-delayed global time. This is useful
30 /// for high-performance applications where the cost of time queries is significant.
31 ///
32 /// The upkeep thread updates the global time value at the specified interval,
33 /// allowing other threads to read it without the overhead of a full time query.
34 ///
35 /// # Arguments
36 ///
37 /// * `update_interval` - How often to update the global time (smaller values
38 /// provide more accurate time but increase CPU usage)
39 ///
40 /// # Returns
41 ///
42 /// A handle to the upkeep thread. The thread runs until this handle is dropped.
43 #[must_use]
44 pub fn start(update_interval: Duration) -> Self {
45 let span = Span::root("timing_upkeep_start", SpanContext::random());
46 let _guard = span.set_local_parent();
47
48 // Ensure we don't start multiple upkeep threads
49 if UPKEEP_STARTED.swap(true, Ordering::SeqCst) {
50 warn!("Timing upkeep thread already running, not starting another one");
51 return Self {
52 _handle: None,
53 active: false,
54 };
55 }
56
57 info!("Starting quanta timing upkeep thread with interval: {update_interval:?}");
58
59 // Use an existing clock to avoid recalibration
60 let clock = Clock::new();
61 let upkeep = Upkeep::new_with_clock(update_interval, clock);
62
63 match upkeep.start() {
64 Ok(handle) => {
65 debug!("Timing upkeep thread started successfully");
66 Self {
67 _handle: Some(handle),
68 active: true,
69 }
70 }
71 Err(e) => {
72 // Reset the flag since we couldn't start the thread
73 UPKEEP_STARTED.store(false, Ordering::SeqCst);
74 warn!("Failed to start timing upkeep thread: {e:?}");
75 Self {
76 _handle: None,
77 active: false,
78 }
79 }
80 }
81 }
82
83 /// Check if this upkeep is active
84 pub const fn is_active(&self) -> bool {
85 self.active
86 }
87
88 /// Manually update the global recent time
89 ///
90 /// This is useful if you don't want to use the upkeep thread but still
91 /// want to occasionally update the global time.
92 pub fn update_recent_time() {
93 let now = Clock::new().now();
94 quanta::set_recent(now);
95 }
96
97 /// Get the current time using the most efficient method available
98 ///
99 /// This function uses the ultra-low-overhead recent time if available,
100 /// falling back to a regular time query if not.
101 #[must_use]
102 pub fn now() -> Instant {
103 Instant::recent()
104 }
105
106 /// Get the current timestamp in nanoseconds
107 ///
108 /// This function uses the ultra-low-overhead recent time if available,
109 /// falling back to a regular time query if not.
110 #[must_use]
111 pub fn now_ns() -> u64 {
112 // Use `quanta::Clock` directly to obtain a raw timestamp in
113 // nanoseconds. This avoids the incorrect calculation that was based on
114 // a one hour offset and provides the same monotonic timestamp used
115 // throughout the rest of the codebase.
116 Clock::new().raw()
117 }
118}
119
120impl Drop for TimingUpkeep {
121 fn drop(&mut self) {
122 if self.active {
123 info!("Stopping timing upkeep thread");
124 UPKEEP_STARTED.store(false, Ordering::SeqCst);
125 }
126 }
127}
128
129/// Initialize the timing subsystem with recommended settings for HFT applications
130///
131/// This function sets up the quanta timing system with the recommended settings
132/// for high-frequency trading applications. It configures the "recent time" feature
133/// to provide ultra-low-overhead access to a slightly-delayed global time.
134///
135/// # Returns
136///
137/// A handle to the upkeep thread. The thread runs until this handle is dropped.
138#[must_use]
139pub fn init_timing() -> TimingUpkeep {
140 // For HFT applications, we want to update the global time frequently to
141 // minimize the staleness of the cached time, while not updating so frequently
142 // that it creates too much overhead
143 let update_interval = Duration::from_micros(500); // 500 μs update interval
144
145 // Start the upkeep thread
146 TimingUpkeep::start(update_interval)
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use std::thread;
153
154 #[test]
155 fn test_upkeep_initialization() {
156 // Reset the static flag for testing purposes
157 UPKEEP_STARTED.store(false, Ordering::SeqCst);
158
159 // First attempt should try to create an upkeep
160 let _upkeep = init_timing();
161
162 // Second upkeep should not be active since we already have one
163 let upkeep2 = init_timing();
164 assert!(!upkeep2.is_active());
165
166 // In tests we can't reliably check if upkeep.is_active() because
167 // it depends on system thread creation which might fail in test environments
168 }
169
170 #[test]
171 fn test_recent_time() {
172 // Reset the static flag for testing purposes
173 UPKEEP_STARTED.store(false, Ordering::SeqCst);
174
175 // Start upkeep thread
176 let _upkeep = init_timing();
177
178 // Allow the upkeep thread to update the recent time
179 thread::sleep(Duration::from_millis(50)); // Increased sleep time for reliability
180
181 // Get recent time
182 let recent_time = TimingUpkeep::now();
183
184 // Ensure it's within a reasonable range of the current time
185 // The recent time should be very close to now
186 let now = Clock::new().now();
187
188 // Handle both cases: recent_time might be slightly before or after 'now'
189 let diff = if now > recent_time {
190 now.duration_since(recent_time)
191 } else {
192 // In rare cases, recent_time might be slightly ahead due to timing precision
193 Duration::from_nanos(0)
194 };
195
196 // Should be less than 100ms difference (increased tolerance for test reliability)
197 assert!(
198 diff < Duration::from_millis(100),
199 "Time difference {} ms is too large",
200 diff.as_millis()
201 );
202 }
203
204 #[test]
205 fn test_now_ns() {
206 // Reset the static flag for testing purposes
207 UPKEEP_STARTED.store(false, Ordering::SeqCst);
208
209 // Start upkeep thread
210 let _upkeep = init_timing();
211
212 // Get nanosecond timestamp
213 let ns = TimingUpkeep::now_ns();
214
215 // Just verify it's a reasonable value (non-zero)
216 assert!(ns > 0);
217
218 // For a more reliable test of the timing functionality,
219 // we'll use direct Clock measurements
220 let clock = Clock::new();
221 let t1 = clock.raw();
222 thread::sleep(Duration::from_millis(1));
223 let t2 = clock.raw();
224 assert!(t2 > t1, "Clock timestamps should increase over time");
225 }
226}