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 intoTimestamp
fromprost_types
Platform Support
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.
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.