Crate quanta

source ·
Expand description

Performant cross-platform timing with goodies.

quanta provides a simple and fast API for measuring the current time and the duration between events. It does this by providing a thin layer on top of native OS timing functions, or, if available, using the Time Stamp Counter feature found on modern CPUs.

Design

Internally, quanta maintains the concept of two potential clock sources: a reference clock and a source clock.

The reference clock is provided by the OS, and always available. It is equivalent to what is provided by the standard library in terms of the underlying system calls being made. As it uses the native timing facilities provided by the operating system, we ultimately depend on the OS itself to give us a stable and correct value.

The source clock is a potential clock source based on the Time Stamp Counter feature found on modern CPUs. If the TSC feature is not present or is not reliable enough, quanta will transparently utilize the reference clock instead.

Depending on the underlying processor(s) in the system, quanta will figure out the most accurate/efficient way to calibrate the source clock to the reference clock in order to provide measurements scaled to wall clock time.

Details on TSC support, and calibration, are detailed below.

Features

Beyond simply taking measurements of the current time, quanta provides features for more easily working with clocks, as well as being able to enhance performance further:

  • Clock can be mocked for testing
  • globally accessible “recent” time with amortized overhead

For any code that uses a Clock, a mocked version can be substituted. This allows for application authors to control the time in tests, which allows simulating not only the normal passage of time but provides the ability to warp time forwards and backwards in order to test corner cases in logic, etc. Creating a mocked clock can be acheived with Clock::mock, and Mock contains more details on mock usage.

quanta also provides a “recent” time feature, which allows a slightly-delayed version of time to be provided to callers, trading accuracy for speed of access. An upkeep thread is spawned, which is responsible for taking measurements and updating the global recent time. Callers then can access the cached value by calling Clock::recent. This interface can be 4-10x faster than directly calling Clock::now, even when TSC support is available. As the upkeep thread is the only code updating the recent time, the accuracy of the value given to callers is limited by how often the upkeep thread updates the time, thus the trade off between accuracy and speed of access.

Feature Flags

quanta comes with feature flags that enable convenient conversions to time types in other popular crates, such as:

  • prost - provides an implementation into Timestamp from prost_types

Platform Support

At a high level, quanta carries support for most major operating systems out of the box:

These platforms are supported in the “reference” clock sense, and support for using the Time Stamp Counter as a clocksource is more subtle, and explained below.

Time Stamp Counter support

Accessing the TSC requires being on the x86_64 architecture, with access to SSE2. Additionally, the processor must support either constant or nonstop/invariant TSC. This ensures that the TSC ticks at a constant rate which can be easily scaled.

A caveat is that “constant” TSC doesn’t account for all possible power states (levels of power down or sleep that a CPU can enter to save power under light load, etc) and so a constant TSC can lead to drift in measurements over time, after they’ve been scaled to reference time.

This is a limitation of the TSC mode, as well as the nature of quanta not being able to know, as the OS would, when a power state transition has happened, and thus compensate with a recalibration. Nonstop/invariant TSC does not have this limitation and is stable over long periods of time.

Roughly speaking, the following list contains the beginning model/generation of processors where you should be able to expect having invariant TSC support:

  • Intel Nehalem and newer for server-grade
  • Intel Skylake and newer for desktop-grade
  • VIA Centaur Nano and newer (circumstantial evidence here)
  • AMD Phenom and newer

Ultimately, quanta will query CPUID information to determine if the processor has the required features to use the TSC.

Calibration

As the TSC doesn’t necessarily tick at reference scale – i.e. one tick isn’t always one nanosecond – we have to apply a scaling factor when converting from source to reference time scale. We acquire this scaling factor by querying the processor or calibrating our source clock to the reference clock.

In some cases, on newer processors, the frequency of the TSC can be queried directly, providing a fixed scaling factor with no further calibration necessary. In other cases, quanta will have to run its own calibration before the clock is ready to be used: repeatedly taking measurements from both the reference and source clocks until a stable scaling factor has been established.

This calibration is stored globally and reused. However, the first Clock that is created in an application will block for a small period of time as it runs this calibration loop. The time spent in the calibration loop is limited to 200ms overall. In practice, quanta will reach a stable calibration quickly (usually 10-20ms, if not less) and so this deadline is unlikely to be reached.

Caveats

Utilizing the TSC can be a tricky affair, and so here is a list of caveats that may or may not apply, and is in no way exhaustive:

  • CPU hotplug behavior is undefined
  • raw values may time warp
  • measurements from the TSC may drift past or behind the comparable reference clock

WASM support

This library can be built for WASM targets, but in this case the resolution and accuracy of measurements can be limited by the WASM environment. In particular, when running on the wasm32-unknown-unknown target in browsers, quanta will use windows.performance.now as a clock. This mean the accuracy is limited to milliseconds instead of the usual nanoseconds on other targets. When running within a WASI environment (target wasm32-wasi), the accuracy of the clock depends on the VM implementation.

Structs

  • Unified clock for taking measurements.
  • Handle to a running upkeep thread.
  • A point-in-time wall-clock measurement.
  • Controllable time source for use in tests.
  • Ultra-low-overhead access to slightly-delayed time.

Enums

  • Errors thrown during the creation/spawning of the upkeep thread.

Traits

  • Type which can be converted into a nanosecond representation.

Functions

  • Sets the global recent time.
  • Sets this clock as the default for the duration of a closure.