Durations and Time Units in C++
Naming the unit in the cast is what fixes the unit of the result.
Why Timing Needs Two Readings
Suppose you want to know how long a piece of C++ code takes to run. You do not need to build your own stopwatch. The C++ standard library already provides facilities for dealing with time, and the std::chrono library together with high_resolution_clock is the standard tool for this job. The core idea is simple: read the clock once before the work starts, read it again after the work finishes, and report the gap between the two readings.
What do you think happens?
Suppose do_work contains a loop that runs for a while, and the timing code is the only timing you do. Which lines do you think are included in the number that gets printed: only the call to do_work, or also the cout line? Write your answer down before reading on.
Reveal answer
Only the call to do_work is included.
Only the code between the two clock readings is included in the difference. The cout line runs after the second reading has already been taken, so it sits outside the measured interval.
Following the Timing Pattern Step by Step
Before reading any explanation, look at the complete pattern and follow it line by line, keeping track of what happens first, what happens second, and what gets printed at the end. In the pattern, do_work is a placeholder for whatever code you want to measure. It is not part of the timing facility. Everything else in the pattern is bookkeeping that wraps that one call with two readings of the time and reports the gap between them.
- 1Line 1 reads the clock and stores the time point in t0 before the work starts.
- 2Line 2 runs
do_work(), the code being measured. - 3Line 3 reads the clock again and stores the time point in t1 after the work finishes.
- 4Line 4 subtracts t0 from t1 to get the elapsed time as a duration, converts it to milliseconds with
duration_cast, and callscount()to get a plain number. - 5Line 5 prints that number followed by the text msec.
high_resolution_clock::now()reads the current time and returns a time point- auto stores each time point in a variable; the compiler decides its type from the call
do_work()is the only operation being timed- The subtraction t1 - t0 yields a duration, which is then converted and extracted
Trace the pattern in the order the program runs it. The first clock reading produces t0 and waits in its variable. The work runs. The second clock reading produces t1. Only the work sitting between these two readings contributes to the gap. The subtraction and the conversion that follow are arithmetic on the stored values; they are not a second run of your program's logic. The value that actually gets printed is produced at the final step, when count() extracts the converted number.
What duration_cast and count() Actually Do
The subtraction t1 minus t0 gives the difference between the two readings. That difference is a duration, not a plain number, so the pattern passes it through duration_cast before printing. Here, duration_cast is used to convert the time duration, and the target unit is named explicitly as milliseconds. Naming the unit in the cast is what fixes the unit of the result.
The call to count() at the end turns the converted duration into a plain number that cout can print. The number is the value in milliseconds, so the unit is already settled by the cast. The label msec written after the number simply documents that unit; it does not perform the conversion. Because the output is a plain number, the cast and the count() call are both needed. Printing the duration object without count() does not give you the bare number, and a label that names a different unit than the cast will mislead a reader.
Where the Two Readings Belong
Only the code between the two readings is included in the difference, so placement determines what the number means. Imagine a print statement placed just before the first clock reading, or just after the second. Each placement changes what the reported time describes. The two diagrams below show the same program with the print line in two different positions.
In the first arrangement, the print line sits between the first reading and the work, so its execution time is counted in the interval. In the second, the print line runs after t1 has already been stored, so the interval covers only do_work. The span between the two clock readings is a stretch of time on a single timeline, and the reported number corresponds exactly to whatever code falls inside that stretch.
Mistakes That Make Timings Misleading
The most common ways a timing result misleads are misplaced output, a missing count(), and a mismatched unit label.
Common mistake Putting output or setup calls between the clock readings
What it looks likePlacing cout between the first now() and do_work(), so the printing time is measured along with the work.
Why it failsOnly the code between the two readings is included in the difference, so anything extra sitting there inflates the number.
Do this insteadKeep the two readings tight around the code you want to measure, and print only after the second reading has been stored.
Common mistake Leaving out count()
What it looks likePrinting the duration object directly after the cast instead of extracting its number.
Why it failsThe converted result is a duration, not a plain number, so the printed output is not the bare value in milliseconds.
Do this insteadCall count() on the converted duration before sending it to cout.
Common mistake Mismatched unit label
What it looks likeCasting to milliseconds but printing a label such as usec after the number.
Why it failsThe number is already in milliseconds because the cast set the unit, so a different label describes the wrong quantity.
Do this insteadMake the unit in the cast and the unit in the printed label agree.
Common mistake Reading a zero as no work done
What it looks likeSeeing 0 msec printed for a short function and concluding it took no time.
Why it failsThe conversion to whole milliseconds drops any fraction, so a very short duration can print as 0.
Do this insteadUse a finer unit if the code is short enough that milliseconds hide the result.
Practice Reading a Timing Block
Consider this variant: a student places cout << "starting" right after the line that stores t0, then calls do_work(), then stores t1, then computes and prints the elapsed milliseconds. Which operations are inside the measured interval, and which are outside? Explain how the reported number would differ from the original pattern.
Hints
- Ask which operations sit between the two stored readings.
- Trace the order: t0 read, cout,
do_work, t1 read, then the subtraction and printing. - Think about whether the cout line is between the readings in this variant.
Key Takeaways
- Two calls to
high_resolution_clock::now(), stored with auto in t0 and t1, bound the interval that gets measured; only the code between them contributes to the difference. - The subtraction t1 minus t0 produces a duration, which
duration_cast<milliseconds> converts to milliseconds andcount()extracts as a plain number for printing. - Placement decides meaning: output or setup calls placed between the two readings inflate the reported time, while output placed after the second reading stays outside it.
- Printed results can mislead through a missing
count(), a unit label that does not match the cast, or truncation of fractions when casting to milliseconds.