Stopwatch

Anybody curious about application performance needs a way to time sections of code without using expensive (in overhead) tools like Callgrind/KCachegrind. This article is about building a stopwatch class that works well in multithread-ready and exception-safe code.

template <typename TClock>
class stopwatch
{

The reason for the TClock type parameter is because there are an array of clocks available to use with the C++ standard, each with different guarantees. The most relevant for our purposes is high_resolution_clock, but you might find steady_clock or some other future clock type interesting.

public:
    typedef TClock                     clock;
    typedef typename clock::time_point time_point;
    typedef typename clock::duration   duration;
    typedef typename duration::rep     tick_type;

Fairly standard member type declarations to pull in the member types from TClock.

private:
    std::atomic<tick_type> _ticks;
}

The only member variable that our stopwatch class has is _ticks, which is used to keep track of how many times the clock has ticked. We define it as one of those fancy atomics (tick_type is just a number) so we can get atomic addition and whatnot without having to use the vendor-specific implementations or TBB. This is a great step in the direction of thread-safety.

stopwatch() :
        _acquired(0)
{ }

duration total() const
{
    return duration(_ticks);
}

Those functions are fairly uninteresting...initialize the time to 0 and a way to get the total elapsed time.

Making the Stopwatch Go the Wrong Way

That class lacks a very important bit of functionality – a way to add time. In a few implementations, people add start and stop as member functions and code to use them would look like this:

watch.start();
do_some_operation();
watch.stop();

There are two problems with that code The first problem is that it is not exception-safe. What happens if the call to do_some_operation throws an exception? In this case, nothing – the execution would skip right over watch.stop(). Making this exception-safe would be painful, too, since C++ does not have a finally. What happens next time we call start without stopping first?

The second problem is that the code is not thread-safe. Implementing the member functions start and stop would be painful to make thread-safe. What happens when multiple threads call start at the same time? It would be pretty easy to only log one thread at a time – just grab the current thread ID when you start and only listen to stop calls on that same thread. That plays horribly with exception-safety, though; what if that thread never tells you to stop because of an exception? You could do thread-local tracking of all outstanding start calls without a stop, but then you have to deal with destruction issues (thread-local storage and C++ classes have classically not played well together).

Making the Stopwatch Go the Right Way

Hopefully that short parade of horrors convinced you that start and stop member functions are not the way to go. The real problem is that giving stopwatch functions to start and stop it violates the object-oriented Single Responsibility Principle. The only responsibility of stopwatch should be to keep track of how many times it has ticked, not control the actual ticking. So, the real function we need is this extremely simple one:

void add(const duration& tm)
{
    _ticks.fetch_add(tm.count(), std::memory_order_relaxed);
}

Thanks to the power of std::atomic, the code is thread-safe. How could it not be? So how do we call add in an exception-safe manner? Easy, wrap the logic of timing and reporting into the lifetime of a class! A decent name for this is ticker, which is an internal class of stopwatch.

class ticker
{
public:
    ticker() :
            _owner(nullptr)
    { }

    explicit ticker(stopwatch* owner) :
            _owner(owner),
            _starttime(clock::now())
    { }

    ~ticker()
    {
        if (_owner)
        {
            duration tick_time = clock::now() - _starttime;
            _owner->add(tick_time);
        }
    }

private:
    stopwatch* _owner;
    time_point _starttime;
};

We are not quite done here. The first problem is copying. If someone takes a ticker and makes a copy of it, the destructor for both ticker instances will be called and the duration will be reported double. Copying is not desirable for this class, so let’s just disable it:

ticker(const ticker&) = delete;
ticker& operator =(const ticker&) = delete;

To use this code, you have to say something like:

{
    stopwatch_type::ticker(&amp;watch);
    do_some_operation();
}

That does not look too bad, but I am super lazy and want my code to look more like this:

{
    auto ticker = watch.start();
    do_some_operation();
}

Thanks to the power of C++ move semantics, you can do just that! In stopwatch, add this member function:

ticker start()
{
    return ticker(this);
}

And add a move constructor to ticker:

ticker(ticker&& source) :
        _owner(source._owner),
        _starttime(source._starttime)
{
    source._owner = nullptr;
}

That function looks a lot like a copy constructor, with the added bonus of being able to manipulate the contents of source. It is a good thing we can, since source will still be destructed. Setting source._owner to null lets the destructor know not to do anything later. If we did not do this, we would face the same problem of time from a ticker being reported twice.

A Slight Problem

One problem that was immediately pointed out to me was: What if somebody wrote this reasonable-looking bit of code:

{
    watch.start();
    do_some_operation();
}

The problem is that the ticker would be destroyed immediately after being returned from watch.start() and watch would forever remain at zero (or really close to it). Unfortunately, there is not a good way to solve this issue (not even the GCC attribute warn_unused_result is capable of producing a warning message here). Since they haven’t read the documentation, my best hope is that they are confused by the lack of a function named stop and consult it, but probably not. There might be a better name for the function (like create_ticker). Overall, this problem fits into the “possibly dodgy behaviour that still makes some sense” on the behavior spectrum, so we have to live with it.

More Info

Source Code

Get the source code for the stopwatch class here: stopwatch.hpp. It is Apache licensed, so use it under those terms (just put the header in your code). There is also a (tiny) demo program: stopwatch.cpp. It has been compiled and tested with g++ 4.6.2, but any compiler with good support for the C++0x standard should be perfectly fine with it.