Skip to content

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:

Measured 2026-09-10 on this Mac and on Compiler Explorer — summary — not an answer key
                          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:

Measured 2026-09-10 — 200,000 back-to-back steady_clock::now() calls, gaps sorted — summary — not an answer key
                                          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 ↗):

#else
      return time_point(system_clock::now().time_since_epoch());
#endif

So that build's "steady" clock counts from 1970 — while is_steady still says true:

Measured 2026-09-10 20:51 on this Mac, up 7 days — steady_clock::now().time_since_epoch() — summary — not an answer key
                                  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:

Measured 2026-09-10 22:27 on this Mac, booted 2026-09-03 17:31 and asleep for part of the week — summary — not an answer key
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 — Instant is the stopwatch and SystemTime the calendar — and no third name to be surprised by: Two clocks: Instant and SystemTime ↗. Rust also promises less than is_steady does: an Instant never goes backwards, but its ticks "might not be the same length".
  • Python. time.time() is the wall clock; time.monotonic() and time.perf_counter() are stopwatches. time.get_clock_info() reports what each one is on your machine: on this Mac with Python 3.14.7, time is clock_gettime(CLOCK_REALTIME) with adjustable=True, and both stopwatches are mach_absolute_time() with monotonic=True, adjustable=False. adjustable=False is Python's nearest thing to is_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 FIELD is the wall clock, a UTC time stamp; GET RUN TIME FIELD is the stopwatch, microseconds since its first call in the session. As in C++, choose by the question you are asking.

See also