quanta

repository·main·Indexed 19 days ago

https://github.com/metrics-rs/quanta

A high-performance Rust timing library providing low-overhead access to monotonic time and CPU cycle counts via the Time Stamp Counter (TSC). It offers features such as ultra-low-overhead 'recent' time, hardware-accelerated timing on x86/x86_64 with automatic OS fallbacks, and a mockable clock for deterministic testing of time-dependent logic.

Tokens
5.1K
Snippets
18
Records
25
Agent score
65%

What's inside quanta

  1. Overview of quanta

    main

    quanta is a high-speed timing library designed for extremely low-overhead time acquisition and manipulation. It is primarily used for getting the current time very quickly or counting CPU cycles.

    Key features include:

    • CPU Cycle Counting: Access the Time Stamp Counter (TSC) directly to count cycles.
    • Monotonic Time: Retrieve monotonic time in nanoseconds, using TSC or an OS fallback.
    • Low Overhead: Designed to be as fast as or faster than std::time::Instant::now().
    • Mockable: Supports mocking time, which is essential for testing time-dependent application logic.
    • Cross-platform: Works across major platforms with automatic fallbacks.
  2. Platform and TSC support for quanta

    main

    quanta provides hardware-accelerated timing via the Time Stamp Counter (TSC) on x86/x86_64 architectures. On other architectures (like ARM), it automatically falls back to standard library timing facilities.

    | Platform | stdlib fallback | TSC support? | | :--- | :---: | ::---: | | Linux (x86/x86_64) | ✅ | ✅ | | Linux (MIPS/ARM) | ✅ | ❌ | | Windows (x86/x86_64) | ✅ | ✅ | | Windows (ARM) | ✅ | ❌ | | macOS (x86/x86_64) | ✅ | ✅ | | macOS (ARM) | ✅ | ❌ | | iOS (ARM) | ✅ | ❌ |

  3. Why use quanta instead of stdlib?

    main

    While std::time::Instant is sufficient for many tasks, quanta offers several advantages for specific use cases:

    1. Performance: In many scenarios, quanta provides a performance edge over standard library timing.
    2. Cycle Counting: It provides a safe, thin wrapper over the Time Stamp Counter (TSC), allowing you to measure exact CPU cycle counts over short, performance-critical code sections.
    3. Testability: Because quanta is mockable, you can simulate the passage of time in unit tests, making it easier to verify logic that depends on specific time intervals or durations.
  4. Run quanta benchmarks

    main

    To run the benchmarks for the quanta crate, use cargo bench specifying the benchmark name. To compare results against a baseline (e.g., using critcmp), you can pipe the output through a series of commands to clean up the formatting for easier comparison.

    Benchmarks were performed on an AMD Ryzen 7 4800HS CPU.

    $ cargo bench --bench <name>
    $ critcmp new | tail +3 | sort | sed 's#        ? ?/sec##'
  5. How `quanta` clock sources work

    main

    quanta uses two layers of timing to balance speed and accuracy:

    1. Reference Clock: Provided by the OS (e.g., clock_gettime on Linux, QueryPerformanceCounter on Windows). It is always available and stable but involves system call overhead.
    2. Source Clock: If available (specifically on x86_64 with SSE2 and an invariant/nonstop TSC), quanta uses the CPU's Time Stamp Counter. This is extremely fast but requires calibration to map its raw ticks to nanoseconds.

    quanta automatically detects if the TSC is reliable. If not, it transparently uses the Reference Clock for all operations.

  6. Use `Instant` for high-speed timing

    main

    The Instant struct provides a point-in-time wall-clock measurement designed for high-speed timing and measurement. It mimics much of the functionality of std::time::Instant but includes specialized methods for quanta's "recent time" feature.

    Monotonicity and Safety

    Instant attempts to use monotonic OS APIs. However, due to potential hardware, virtualization, or OS bugs, monotonicity can be violated. To prevent panics in these cases, several methods in quanta saturate to zero instead of panicking:

    • duration_since
    • elapsed
    • sub (when subtracting two Instants)

    To explicitly detect monotonicity violations or incorrect ordering, use checked_duration_since.

    use quanta::Instant;
    use std::time::Duration;
    
    let now = Instant::now();
    // ... do work ...
    let elapsed = now.elapsed();
  7. How `Upkeep` and `Clock::recent` work together for low-overhead timing

    main

    In high-frequency applications, querying the current time repeatedly can introduce measurable overhead. quanta provides a way to access a slightly-delayed, ultra-low-overhead version of the time via Clock::recent().

    The Mechanism

    1. Upkeep: A configuration object that defines an update interval and a Clock.
    2. Background Thread: When you call Upkeep::start(), it spawns a background thread named quanta-upkeep. This thread periodically calls clock.now() and updates a global reference.
    3. Clock::recent(): Instead of querying the hardware (like the TSC) and performing scale conversions every time, callers read the pre-calculated global value. This can be 2-3x faster than a standard time read (e.g., 4-5ns vs 12-14ns).

    Trade-offs

    • Accuracy vs. Performance: A shorter update interval increases accuracy but consumes more CPU. A longer interval saves CPU but increases the delay (granularity) of the 'recent' time.
    • Global State: The recent time is global to the application. If multiple parts of your code attempt to start an upkeep thread, only one will succeed. The interval chosen by the first successful start() call will be the one used by all callers of Clock::recent().
    // Example pattern for using upkeep
    let upkeep = Upkeep::new(Duration::from_millis(1)); // 1ms granularity
    let _handle = upkeep.start().expect("failed to start upkeep");
    
    // Later in high-frequency loops:
    let time = clock.recent(); 
  8. Use `Mock` to control time in tests

    main

    The Mock struct provides a controllable time source designed for testing time-dependent code. Unlike a standard Clock, a Mock allows you to manually adjust the time forward or backward.

    Key behaviors:

    • Manual Control: You can use increment and decrement to shift the clock.
    • Non-monotonicity: While quanta's standard Clock guarantees monotonic values, using a Mock means the time is directly coupled to your manual adjustments; it may not be monotonic if you decrement the time.
    • Flexible Inputs: Methods accept both raw u64 nanoseconds and std::time::Duration objects via the IntoNanoseconds trait.
    use quanta::Mock;
    use std::time::Duration;
    
    let mock = Mock::new(); // Note: new() is currently pub(crate) in source, use available constructor
    
    // Increment time using Duration
    mock.increment(Duration::from_secs(1));
    
    // Increment time using raw nanoseconds (u64)
    mock.increment(1_000_000u64);
    
    // Decrement time
    mock.decrement(Duration::from_millis(500));
    
    // Get the current mocked time value in nanoseconds
    let current_time = mock.value();
  9. Start an upkeep thread with `Upkeep::start`

    main

    To begin updating the global coarse time, create an Upkeep instance and call .start(). This returns a Handle which acts as a drop guard.

    Important: The Handle Lifecycle

    The Handle must be kept in scope for the background thread to continue running. If the Handle is dropped, the upkeep thread will stop immediately, and Clock::recent() will cease to update.

    Errors

    • Error::UpkeepRunning: Returned if an upkeep thread is already running in the process. Only one upkeep thread can exist at a time.
    • Error::FailedToSpawnUpkeepThread: Returned if the OS fails to spawn the background thread.
    use quanta::{Upkeep, Error};
    use std::time::Duration;
    
    let upkeep = Upkeep::new(Duration::from_millis(1));
    match upkeep.start() {
        Ok(handle) => {
            // Keep 'handle' alive to keep the thread running
            let _keep_alive = handle;
        }
        Err(Error::UpkeepRunning) => println!("Upkeep is already active"),
        Err(e) => println!("Failed to start: {}", e),
    }
  10. Perform arithmetic on `Instant`

    main

    You can add or subtract Durations to an Instant using standard operators or checked methods:

    • Checked Arithmetic (Safe):
      • checked_add(duration) -> Option<Instant>
      • checked_sub(duration) -> Option<Instant>
    • Unchecked Arithmetic (Panics on overflow):
      • add(duration) -> Instant (via + operator)
      • sub(duration) -> Instant (via - operator)
      • add_assign(duration) (via += operator)
      • sub_assign(duration) (via -= operator)
    use quanta::Instant;
    use std::time::Duration;
    
    let now = Instant::now();
    let later = now + Duration::from_secs(5);
    let earlier = now - Duration::from_secs(5);
    
    // Safe version
    if let Some(future) = now.checked_add(Duration::from_secs(10)) {
        // ...
    }
  11. Mock API reference

    main

    The Mock struct is used to simulate and manipulate time passage in a testing environment.

    MethodSignatureDescription
    incrementfn increment<N: IntoNanoseconds>(&self, amount: N)Increases the mocked time by the specified amount (nanoseconds or Duration).
    decrementfn decrement<N: IntoNanoseconds>(&self, amount: N)Decreases the mocked time by the specified amount (nanoseconds or Duration).
    valuefn value(&self) -> u64Returns the current mocked time value in nanoseconds.
  12. Get the current time with `Instant::now()`

    main

    Returns the current time, scaled to the reference time. This method is the spiritual equivalent of std::time::Instant::now. It depends on a lazily initialized global clock, which may take up to 200ms to initialize and calibrate itself upon the first call.

    use quanta::Instant;
    
    let now = Instant::now();