How performance.now Provides Sub-Millisecond Timing
In JavaScript, performance.now() provides
high-resolution time measurements with sub-millisecond precision by
reading from a monotonic hardware clock rather than the system’s wall
clock. This article explains the underlying mechanisms of the High
Resolution Time API, how floating-point timestamps deliver
fractional-millisecond accuracy, why monotonic clocks prevent timing
skews, and how modern browser security considerations affect
precision.
The Mechanism Behind Sub-Millisecond Accuracy
Unlike Date.now(), which returns an integer value
representing whole milliseconds elapsed since the Unix epoch (January 1,
1970), performance.now() returns a
DOMHighResTimeStamp. This value is a double-precision
floating-point number representing milliseconds elapsed since the
context’s timeOrigin (usually the moment the page or worker
started navigation).
Because the returned value is a floating-point number, the fractional
portion represents fractions of a millisecond. For example, a value of
1024.125 translates to 1,024 milliseconds and 125
microseconds (or 0.125 milliseconds).
Monotonic Clocks vs. System Time
The precision and reliability of performance.now() stem
from its use of a monotonic clock:
- Immunity to Time Drift: System wall clocks can jump backward or forward due to Network Time Protocol (NTP) synchronizations, manual user adjustments, or daylight saving time transitions. A monotonic clock strictly increments forward at a steady rate, making it suitable for benchmarking intervals.
- Direct Hardware Timer Access: Under the hood, the
browser communicates with low-level operating system APIs (such as
clock_gettime(CLOCK_MONOTONIC)on Linux/POSIX,QueryPerformanceCounteron Windows, ormach_absolute_timeon macOS). These OS APIs query hardware counters, such as the CPU’s Time Stamp Counter (TSC) or the High Precision Event Timer (HPET), enabling measurement intervals far finer than a single millisecond.
The Role of timeOrigin
The baseline for performance.now() is
performance.timeOrigin, recorded at the start of the
environment lifecycle. By decoupling timing calculations from the global
epoch and measuring purely against an internal origin, the runtime
eliminates overhead associated with date formatting and global time
translation, enabling lightweight and continuous high-precision
measurements.
Precision Clamping and Security
While hardware and API specifications support nanosecond-level
resolution, modern web browsers intentionally reduce the granularity of
performance.now(). To mitigate side-channel timing attacks
(such as Spectre) and browser fingerprinting, browsers clamp the
precision (typically rounding to 5, 20, or 100 microseconds) and
introduce slight jitter. Despite this clamping,
performance.now() remains significantly more precise than
whole-millisecond alternatives, delivering the sub-millisecond
resolution required for performance profiling, animation
synchronization, and network latency tracking.