In PHP, why should you time a block of code with hrtime(true) instead of microtime(true), and how do you turn the result into milliseconds?
answer
- monotonic versus wall clock
- NTP can step the system clock
- nanoseconds as int on 64-bit
- subtract, then divide by 1e6
- microtime() without true returns a string
basics
~20 shrtime(true) reads a monotonic clock in nanoseconds, so the difference of two calls is a true elapsed time; microtime(true) reads the adjustable wall clock as float seconds. Subtract two hrtime(true) values and divide by 1e6 for milliseconds.
solid answer
~40 s`hrtime(bool $as_number = false)` returns the system's high-resolution **monotonic** time, counted from an arbitrary point: with `true` an `int` of nanoseconds on 64-bit builds (a `float` on 32-bit), otherwise a `[seconds, nanoseconds]` array. A single value means nothing; only the difference of two calls does, and that difference cannot jump when NTP or an administrator adjusts the clock. `microtime(true)` returns the current Unix time as float seconds from `gettimeofday()`: right for timestamps, wrong for durations, because a clock step can make an interval negative or inflated. Without `true`, `microtime()` returns a string such as `"0.65432100 1790000000"`, and subtracting two of those is a classic bug. For milliseconds: `(hrtime(true) - $start) / 1e6`. `hrtime()` has existed since PHP 7.3.
code
php · 13 lines<?php
declare(strict_types=1);
$products = [];
for ($i = 0; $i < 50_000; $i++) {
$products[] = ['sku' => "SKU-$i", 'price' => random_int(100, 99_999)];
}
$start = hrtime(true);
usort($products, fn (array $a, array $b): int => $a['price'] <=> $b['price']);
$elapsedMs = (hrtime(true) - $start) / 1e6;
printf("usort of %d rows: %.2f ms\n", count($products), $elapsedMs);go deeper
Remember the pair: hrtime(true) for durations, microtime(true) for timestamps. Know that hrtime(true) is nanoseconds, so divide by 1e6 for milliseconds.
Explain monotonic versus wall-clock time and why an NTP step corrupts microtime differences. Mention the string form of microtime() as a legacy trap and the 32-bit float case of hrtime.
Talk about measurement hygiene: warm-up runs, repeated iterations, percentiles, and measuring with production OPcache and JIT settings. Timers show which phase is slow; a profiler shows why.
Frame timing as instrumentation policy: which spans a codebase records with hrtime by default, how they reach logs, and why cross-host correlation must use wall-clock timestamps instead.
## Two clocks, two jobs A computer keeps more than one notion of time, and PHP exposes both: - **Wall-clock time** is the calendar time of day: seconds since the Unix epoch. It is what `time()`, `microtime()` and `$_SERVER['REQUEST_TIME_FLOAT']` report. The operating system may **adjust** it at any moment: an NTP daemon slews or steps it, an administrator sets it, a virtual machine resyncs after being paused. - **Monotonic time** only ever moves forward at a steady rate from some arbitrary starting point (often boot). It has no meaning as a date, but the difference between two readings is exactly the time that elapsed. `hrtime()` reads this clock: `CLOCK_MONOTONIC` via `clock_gettime()` on Linux and other POSIX systems, `QueryPerformanceCounter` on Windows, and on macOS `clock_gettime_nsec_np(CLOCK_UPTIME_RAW)` since PHP 8.5. For measuring **how long something took**, you want the monotonic clock. For recording **when something happened**, you want the wall clock. ## The signatures | Function | Returns | Use it for | |---|---|---| | `hrtime(true)` | `int` nanoseconds on 64-bit (`float` on 32-bit), or `false` on failure | durations | | `hrtime()` | `[seconds, nanoseconds]` array of ints | durations, when you want the parts | | `microtime(true)` | `float` Unix seconds, roughly microsecond resolution | timestamps | | `microtime()` | string `"<fraction> <seconds>"` | legacy code only | Both functions live in the standard extension: `hrtime(bool $as_number = false): array|int|float|false` and `microtime(bool $as_float = false): string|float`. ## Timing a block correctly ```php <?php declare(strict_types=1); $start = hrtime(true); $html = renderProductPage($product); $elapsedMs = (hrtime(true) - $start) / 1e6; error_log(sprintf('renderProductPage took %.2f ms', $elapsedMs)); ``` The steps are always the same: 1. Read `hrtime(true)` once before the work. 2. Read it again after the work and subtract. 3. Convert: divide by `1e6` for milliseconds, `1e9` for seconds, `1e3` for microseconds. A 64-bit `int` of nanoseconds holds centuries of uptime, so overflow is not a practical concern; the subtraction stays exact integer arithmetic until the division. ## What goes wrong with microtime() - **Clock steps.** If NTP corrects the clock by half a second while your code runs, `microtime(true)` differences include that half second, or come out negative. On a fleet of servers these small corrections show up as impossible outliers in latency logs. - **The string form.** `microtime()` without an argument returns something like `"0.65432100 1790000000"`. Subtracting two such strings makes PHP 8 use only the leading fraction and emit `E_WARNING` "A non-numeric value encountered", so a duration that crosses a second boundary comes out negative. Code written before `microtime(true)` existed is full of this. - **Float precision.** A float holding about 1.8 billion seconds has room for only about a quarter of a microsecond of resolution. That is fine for most request timing, but it cannot resolve very short operations, and it adds noise when you sum many small intervals. ## Measuring fairly A single timing of a fast operation is mostly noise. When you compare two implementations: - run each many times in a loop and time the loop, not one iteration; - discard the first runs, which pay for autoloading, compilation and cold caches; - report the median or a percentile rather than a single best or worst number; - measure the code as it runs in production, with the same OPcache and JIT settings, because a CLI run with different settings answers a different question. A small helper keeps call sites honest and makes the unit explicit in its name: ```php function elapsedMs(int $startNs): float { return (hrtime(true) - $startNs) / 1e6; } ``` Naming the unit matters more than it looks: mixing nanoseconds, microseconds and milliseconds in one log is how a 3 ms query gets reported as 3 seconds. Timers also answer only **how long**, not **why**. Once `hrtime()` spans tell you which phase of a request is slow, a profiler tells you what inside that phase is responsible. ## When wall-clock values are still right `$_SERVER['REQUEST_TIME_FLOAT']` (the timestamp at which PHP started processing the request, with microsecond precision) and `microtime(true)` are the right tools for log timestamps and for correlating events across machines, where you need a shared calendar. Just do not subtract them to measure short durations inside one process when `hrtime()` is available.
- What does hrtime() return without an argument, and when would you use that form?It returns a two-element array of ints, `[seconds, nanoseconds]`, from the same monotonic clock. It exists mainly for 32-bit builds, where `hrtime(true)` must return a `float` and could lose precision on very long uptimes. On 64-bit PHP, `hrtime(true)` as an `int` is simpler and exact, so the array form is rarely needed.
- Can you compare an hrtime(true) value taken in one PHP-FPM request with one taken in another request or on another server?Only on the same machine, and even then carefully: the starting point is arbitrary but shared by the host's monotonic clock, so two workers on one server read comparable values. Across servers the origins differ entirely, so the numbers mean nothing together. For cross-machine correlation use wall-clock timestamps such as `microtime(true)` and accept their adjustments.
saying these in an interview costs you the question
- Subtracts two plain microtime() calls, which return strings, to get a duration.
- Believes hrtime(true) returns the current Unix time in nanoseconds.
- Thinks wall-clock time never jumps backwards on a production server.
- Divides the hrtime(true) difference by 1000 and calls the result milliseconds.
- Benchmarks a fast function with one single timing and treats the number as exact.