Stopwatch Class

Introduction

The <utilities/stopwatch.h> header defines utilities::stopwatch, a simple stopwatch class to measure execution times.

You might use it like this:

utilities::stopwatch sw;
do_work();
std::cout << sw << '\n';

The output will be the time taken in seconds for the do_work() call to run.

Declaration

template<typename Clock = std::chrono::high_resolution_clock>
class utilities::stopwatch;

The header-only class is templatized over the specific clock choice, likely one of the clocks from std::chrono.

Our default clock is std::::chrono::high_resolution_clock. Discussions on sites like Stack Overflow instead favour std::chrono::steady_clock. That clock is guaranteed to be monotonic and unaffected by changing the system time, so it is suitable for long-running processes. However, in practice, we primarily examine much shorter code blocks and value accuracy over “steadiness.” We also note that, in any case, the two clocks are often identical!

If the clock choice matters, you can use one of the following type aliases:

utilities::precise_stopwatch = utilities::stopwatch<std::chrono::high_resolution_clock>;
utilities::steady_stopwatch  = utilities::stopwatch<std::chrono::steady_clock>;
utilities::system_stopwatch  = utilities::stopwatch<std::chrono::system_clock>;

We always store elapsed times as a double number of seconds — this is also contrary to advice that advocates the use of std::chrono::duration.

The primary goal for utilities::stopwatch is ease of use. Sticking to seconds as the standard unit makes everything consistent. Besides, a double number of seconds gives you 15 or 16 places of accuracy, which is enough for any conceivable application.

Use the utilities::stopwatch class for cheap and cheerful performance measurement. It is not a replacement for the many more complete but more complex code profiling tools.

Construction

utilities::stopwatch(const std::string& name = "");

Creates a stopwatch and sets its zero time to now. You can give it a name, which is helpful if multiple stopwatches run in one executable.

Timing Methods

The stopwatch class is kept deliberately simple.

At its core, it measures the elapsed time in seconds from a zero time set on creation or by calling the stopwatch’s reset() method. It also supports the idea of clicking a stopwatch to record a split time–the time in seconds from the zero point to the click event. Finally, it records a lap time, which is the time between the last two stopwatch clicks. However, it has no memory further back than that.

1constexpr void reset();
2constexpr double elapsed() const;
3constexpr double click();
4constexpr double split() const;
5constexpr double lap() const;
1
Clears out any recorded split time and resets the zero time to now.
2
Returns the number of seconds from the zero_time to now.
3
Creates a split by recording the elapsed time in seconds from the zero time to the click() call and returns that time.
4
Returns the last recorded split time in seconds.
5
Returns the time in seconds between the last two splits — i.e., between the previous two click events.

Other Methods

1const std::string& name() const;
std::string& name();
1
Read-only and read-write access to the stopwatch name field.

Output Functions

template<typename Clock>
std::ostream &
1operator<<(std::ostream&, const utilities::stopwatch<Clock>&);

template<typename Clock>
2struct std::formatter<utilities::stopwatch<Clock>>;
1
The usual output operator for a stopwatch.
2
Adds stopwatch support to std::format.
These functions output the stopwatch’s elapsed time only. The output will look like “3.2s” without a name. The output will look like “name: 3.2s” with a name.

Other Functions

template<class Rep, class Period>
constexpr double
1to_seconds(const std::chrono::duration<Rep, Period>);
1
This converts a std::chrono::duration to a double number of seconds.

Example: How efficient is it to put a thread to sleep?

#include <utilities/stopwatch.h>
#include <format>
#include <thread>

int main()
{
    using namespace std::literals;
    utilities::stopwatch sw;

    for (auto sleep_duration = 0ms; sleep_duration <= 2s; sleep_duration += 200ms) {

1        sw.click();
2        std::this_thread::sleep_for(sleep_duration);
3        sw.click();

4        double sleep_ms = 1000 * utilities::to_seconds(sleep_duration);
5        double actual_ms = 1000 * sw.lap();
        double diff = actual_ms - sleep_ms;
        double percent = sleep_ms != 0 ? 100 * diff / sleep_ms : 0;

        std::cout << std::format("Requested sleep for {:8.2f}ms, measured wait was {:8.2f}ms => overhead {:.2f}ms ({:.2f}%)\n",
                   sleep_ms, actual_ms, diff, percent);
    }
    std::cout << "Total elapsed time: " << sw << '\n';
    return 0;
}
1
Create a split.
2
Sleep for a set number of milliseconds.
3
Create a second split and, hence, a lap.
4
Convert the sleep duration to a double number of seconds.
5
Get the lap time for that last call to sleep_for(...).

Output

Requested sleep for     0.00ms, measured wait was     0.00ms => overhead 0.00ms (0.00%)
Requested sleep for   200.00ms, measured wait was   202.96ms => overhead 2.96ms (1.48%)
Requested sleep for   400.00ms, measured wait was   401.59ms => overhead 1.59ms (0.40%)
Requested sleep for   600.00ms, measured wait was   604.82ms => overhead 4.82ms (0.80%)
Requested sleep for   800.00ms, measured wait was   804.06ms => overhead 4.06ms (0.51%)
Requested sleep for  1000.00ms, measured wait was  1001.97ms => overhead 1.97ms (0.20%)
Requested sleep for  1200.00ms, measured wait was  1202.99ms => overhead 2.99ms (0.25%)
Requested sleep for  1400.00ms, measured wait was  1405.10ms => overhead 5.10ms (0.36%)
Requested sleep for  1600.00ms, measured wait was  1603.67ms => overhead 3.67ms (0.23%)
Requested sleep for  1800.00ms, measured wait was  1803.70ms => overhead 3.70ms (0.21%)
Requested sleep for  2000.00ms, measured wait was  2004.82ms => overhead 4.82ms (0.24%)
Total elapsed time: 11.036255763s

See Also

std::chrono

Back to top