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:

  1. 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.
  2. 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, QueryPerformanceCounter on Windows, or mach_absolute_time on 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.