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.
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,
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.
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:
Clockcan 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
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.
quanta comes with multiple feature flags that enable convenient conversions to time types in
other popular crates:
metrics- provides an implementation of
prost- provides an implementation into
At a high level,
quanta carries support for most major operating systems out of the box:
- Windows (QueryPerformanceCounter)
- macOS/OS X/iOS (mach_continuous_time)
- Linux/*BSD/Solaris (clock_gettime)
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.
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
quanta will query CPUID information to determine if the processor has the
required features to use the TSC.
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,
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
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,
reach a stable calibration quickly (usually 10-20ms, if not less) and so this deadline is
unlikely to be reached.
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
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.
Errors thrown during the creation/spawning of the upkeep thread.
Type which can be converted into a nanosecond representation.