Timing a block of code¶
Level: 201 · working knowledge
One line: steady_clock::now() before, steady_clock::now() after, subtract — the pattern is three lines, and what matters is which clock goes in them and how little one measurement can tell you.
#include <chrono>
auto start = std::chrono::steady_clock::now();
do_the_work();
auto elapsed = std::chrono::steady_clock::now() - start;
std::chrono::duration<double, std::milli> ms = elapsed; // e.g. 20.13 — different on every run
Verified output of timing_a_block.cpp — regenerated by tools/run_examples.py and held to the same answer key on libstdc++ and libc++ in CI, never hand-typed.
elapsed >= 20ms: true
elapsed is a steady_clock::duration: true
as_ms.count() >= 20.0: true
as_us >= 20000us: true
The example sleeps for 20 ms and times the sleep. It never prints the time — that differs on every run and every machine, and this library's answer keys have to hold on both of CI's. It prints what the standard guarantees about the time instead. A _for function waits at least as long as it was asked, and "Implementations should use a steady clock to measure time for these functions" (thread.req.timing ↗) — so elapsed >= 20ms is true on every conforming library. That check works on your own measurements too.
Which clock¶
steady_clock, because "the clock may not be adjusted".system_clockis the wall clock; NTP can slew or step it and a person can set it, so asystem_clockinterval can come out too long, too short, or negative.- Not
high_resolution_clock. In libstdc++ it issystem_clock(Three clocks); in libc++ and MSVC's library it issteady_clock, so naming it gains nothing there either. - And check your
steady_clock. With one real compiler on this Mac — Homebrew's GCC 15.2 —steady_clockreads the wall clock in whole microseconds (Three clocks).
Converting the result¶
elapsed is a steady_clock::duration, nanoseconds on all three major libraries. Convert at the edge, for whoever reads it:
duration<double, std::milli>for a person — fractional milliseconds, nothing rounded away. It converts implicitly, because a floating-point count cannot lose anything.duration_cast<microseconds>for a log with integer fields — truncating, on purpose, by name.
What the number includes¶
Everything between the two calls, and that is more than your code:
- the clock — back-to-back
now()calls were 35 ns apart with libc++ on this Mac and 15 ns in Docker's Linux VM (Three clocks); timing something that short measures mostly the clock — The cost of asking the time (outline) - the scheduler — a sleep lasts at least its duration, and any other code can be preempted part-way through
- the machine — caches, the branch predictor, frequency scaling, other programs — the chapter's outlines
- the optimizer — which may have removed or moved the work you meant to time — The optimizer deletes your benchmark
One timing is a sample, not a measurement.
If you are coming from another language¶
- Rust.
let start = Instant::now();thenstart.elapsed()— the same three lines, with the clock choice made for you, sinceInstantis the only stopwatch there is: Timing a block ↗. - Python.
start = time.perf_counter(), thentime.perf_counter() - start. Its documentation says the reference point is undefined, so "only the difference between the results of two calls is valid" (time.perf_counter ↗) — the same fact assteady_clock's unspecified epoch. For repeated timings,timeitruns the loop for you. - ABAP. (Not machine-checked.)
GET RUN TIME FIELD lv_t1.…GET RUN TIME FIELD lv_t2., thenlv_t2 - lv_t1in microseconds. For anything beyond a quick check, the runtime analysis in transactionSATmeasures for you.
See also¶
- Three clocks — the clock choice, measured
- A duration is a count and a unit — the conversions used above
- The optimizer deletes your benchmark — what happens when the timed work is a loop