Three clocks, and the one in the trenchcoat¶
Level: 201 · working knowledge
One line: std::chrono gives you a wall clock, a stopwatch, and a third name that is one of the other two — which one is up to your standard library, and in the library behind the talk's own error message it is the wall clock, the one that can be set back.
#include <chrono>
auto start = std::chrono::steady_clock::now(); // to measure how long something takes
auto stamp = std::chrono::system_clock::now(); // to record when it happened
Measure with steady_clock, timestamp with system_clock, and leave the third one alone.
The three¶
| Clock | Answers | Counts from | Can it be adjusted? | is_steady |
|---|---|---|---|---|
system_clock ↗ |
what time is it? | 1970-01-01 00:00:00 UTC — Unix time, fixed by C++20 | yes — NTP, or anyone setting the time | false |
steady_clock ↗ |
how long did that take? | unspecified — in practice, boot | no — "the clock may not be adjusted" | true |
high_resolution_clock ↗ |
whichever ticks fastest | whichever it is | whichever it is | unspecified |
The standard's words for the third: it "may be a synonym for system_clock or steady_clock" (time.clock.hires ↗).
What every library agrees on¶
Verified output of three_clocks.cpp — regenerated by tools/run_examples.py and held to the same answer key on libstdc++ and libc++ in CI, never hand-typed.
system_clock::is_steady = false
steady_clock::is_steady = true
high_resolution_clock is one of them = true
a system_clock time point prints as a date = true
system_clock's epoch = 1970-01-01 00:00:00
a steady_clock time point prints as a date = false
steady_clock tick = 1/1000000000 s
Every line is the same on libstdc++ and libc++, because every line is something the standard fixes. printable is a C++20 requires-expression that asks the compiler whether os << value would compile. A system_clock time point is a date, so it prints as one; a steady_clock time point counts from a moment nobody specified, so the library gives it no way to print as a date.
The trenchcoat¶
The talk's slide says high_resolution_clock is "steady_clock in a trenchcoat" — one clock dressed up as another. Asked directly, the three major standard libraries answer differently:
high_resolution_clock is system_clock tick
libstdc++ GCC 13.4–15.2 system_clock 1 ns ratio<1, 1000000000>
libc++ Clang 21 steady_clock 1 µs ratio<1, 1000000>
MSVC STL v19.latest steady_clock 100 ns ratio<1, 10000000>
The library decides, not the compiler: on Compiler Explorer the same Clang 21.1 reports system_clock when it uses libstdc++, its default there, and steady_clock with -stdlib=libc++. libstdc++'s source gives its reason in one line — using high_resolution_clock = system_clock;, under the comment "Alias to std::system_clock until higher-than-nanosecond definitions become feasible" (bits/chrono.h ↗). So in the library whose error message is on the talk's very next slide, the trenchcoat hides the clock that NTP can step backwards.
The name has been argued over for years. cppreference's notes record that Howard Hinnant, who says he introduced it, favoured deprecating it in 2016, because an alias that may be either clock "adds uncertainty to a program without benefit" (cppreference ↗).
A tick is a unit, not a resolution¶
period says how long one tick would be. It does not promise the clock moves that often, or that reading it is that cheap:
period smallest gap median gap
Apple clang 21 / libc++ 21, macOS 1 ns 35 ns 37 ns
GCC 13.4 / libstdc++, Docker's Linux VM 1 ns 15 ns 17 ns
Homebrew GCC 15.2 / libstdc++, macOS 1 ns 0 ns 0 ns moves in steps of 1000 ns
The first two rows measure the call, not the clock: one reading costs 15–37 ns here, and the talk measured 20–30 ns on its own machine. The third row is a clock that ticks once a microsecond while its type claims nanoseconds.
A flag is a promise, not a measurement¶
Homebrew's GCC 15.2 on this Mac builds libstdc++ without clock_gettime: its c++config.h leaves _GLIBCXX_USE_CLOCK_MONOTONIC undefined, and libstdc++'s steady_clock::now() then falls back to the wall clock (chrono.cc ↗):
So that build's "steady" clock counts from 1970 — while is_steady still says true:
counted so far is_steady
Apple clang 21 / libc++ 21 7.1 days since this Mac booted true
GCC 14.4 / libstdc++, Docker VM 4.8 days since the VM booted true
Homebrew GCC 15.2 / libstdc++ 56.7 years since 1970 true
is_steady is a constexpr bool that a library's authors write by hand, and nothing checks it against what now() actually does — the same is true of a clock you write yourself (What a clock is). This is not what GCC does on Linux, where the fallback is compiled out and steady_clock reads CLOCK_MONOTONIC. But it is a real compiler from a common package manager, and a benchmark built with it measures with the wall clock.
Does sleep count?¶
"Steady" promises the clock never goes back. It does not say whether the hours a laptop spends asleep are elapsed time, and on one machine the answers differ:
libc++ steady_clock 7.206 days reads CLOCK_MONOTONIC_RAW, which counts sleep
CLOCK_MONOTONIC_RAW 7.206 days
time since kern.boottime 7.205 days
CLOCK_UPTIME_RAW 4.859 days stops while the machine sleeps
Python time.monotonic() 4.859 days mach_absolute_time(), the same clock
libc++ chose CLOCK_MONOTONIC_RAW in 2020 (the change ↗), and its source gives the reason: that clock "is truly monotonic, because it also counts cycles when the system is asleep" (chrono.cpp ↗). Rust's Instant reads CLOCK_UPTIME_RAW on macOS, according to its documentation — so a C++ stopwatch and a Rust stopwatch left running across this week's sleeps would disagree by 2.35 days, both of them monotonic.
If you are coming from another language¶
- Rust. Two clocks instead of three —
Instantis the stopwatch andSystemTimethe calendar — and no third name to be surprised by: Two clocks: Instant and SystemTime ↗. Rust also promises less thanis_steadydoes: anInstantnever goes backwards, but its ticks "might not be the same length". - Python.
time.time()is the wall clock;time.monotonic()andtime.perf_counter()are stopwatches.time.get_clock_info()↗ reports what each one is on your machine: on this Mac with Python 3.14.7,timeisclock_gettime(CLOCK_REALTIME)withadjustable=True, and both stopwatches aremach_absolute_time()withmonotonic=True, adjustable=False.adjustable=Falseis Python's nearest thing tois_steady— and it is reported by the running interpreter rather than written into a header. - ABAP. (Not machine-checked — CI cannot run ABAP.)
GET TIME STAMP FIELDis the wall clock, a UTC time stamp;GET RUN TIME FIELDis the stopwatch, microseconds since its first call in the session. As in C++, choose by the question you are asking.
See also¶
- A clock is four types, a flag and
now()— whatperiodandis_steadyare, and a clock of your own - A time point knows its clock — why the two clocks' readings must not be mixed
- Timing a block of code —
steady_clockin use - What happens in
now()— the call chain under the clock (outline) - Josuttis, The C++ Standard Library, 2nd ed., §5.7 "Clocks and Timers" — details in Resources