It's often useful to be able to time code, for instance, to evaluate alternative approaches to the same problem. Because this is a thing I use frequently, I have created this Stopwatch templated class that I'd like for you all to review. Unlike some other recent code, this class is intended to be cross-platform and relies only on a working implementation of the std::chrono
utilities in C++11.
Specifically, does the interface make sense? Could the code be improved? Also, I have thought it might be nice to provide some kind of a "units" function to return, say, a std::string
containing "ms" if TimeT
is set to std::chrono::milliseconds
but ultimately decided against complicating the interface. I'd be open to comments on that decision as well.
stopwatch.h
#ifndef STOPWATCH_H
#define STOPWATCH_H
#include <chrono>
template<typename TimeT = std::chrono::microseconds,
typename ClockT=std::chrono::high_resolution_clock,
typename DurationT=double>
class Stopwatch
{
private:
std::chrono::time_point<ClockT> _start, _end;
public:
Stopwatch();
void start();
DurationT restart();
DurationT stop();
DurationT elapsed() const;
};
template<typename TimeT, typename ClockT, typename DurationT>
Stopwatch<TimeT, ClockT, DurationT>::Stopwatch()
{
start();
}
template<typename TimeT, typename ClockT, typename DurationT>
void Stopwatch<TimeT, ClockT, DurationT>::start()
{
_start = _end = ClockT::now();
}
template<typename TimeT, typename ClockT, typename DurationT>
DurationT Stopwatch<TimeT, ClockT, DurationT>::stop()
{
_end = ClockT::now();
return elapsed();
}
template<typename TimeT, typename ClockT, typename DurationT>
DurationT Stopwatch<TimeT, ClockT, DurationT>::restart()
{
DurationT ret = stop();
start();
return ret;
}
template<typename TimeT, typename ClockT, typename DurationT>
DurationT Stopwatch<TimeT, ClockT, DurationT>::elapsed() const
{
auto delta = std::chrono::duration_cast<TimeT>(_end-_start);
return delta.count();
}
#endif
stoptest.cpp
#include <iostream>
#include <cmath>
#include "stopwatch.h"
int main()
{
const int iterations = 1000000;
double sqrtsum = 0;
Stopwatch<> sw;
for (int i=0; i<iterations; ++i)
sqrtsum += sqrt(i);
std::cout << "calculated " << sqrtsum << " in " << sw.restart() << " us\n";
sqrtsum = 0;
for (int i=0; i<iterations; ++i)
sqrtsum += pow(i,0.5);
std::cout << "calculated " << sqrtsum << " in " << sw.stop() << " us\n";
}
A few words about usage
The goal was to create a minimal but complete interface. The constructor does nothing except calling start
.
start
sets both starting and ending times to the current underlying clock's now()
and essentially functions as a reset (which was another name I considered for this function.)
elapsed
does not modify the underlying Stopwatch
and only returns the difference between the internally stored _end
and _start
.
stop
returns the elapsed time between "now" and the internally stored _start
and so can be used to measure several times all relative to the same start time.
restart
does the same thing as stop
except it also resets the start time.
5 Answers 5
Ergonomy
The restart
method seems a bit unnatural: I wouldn't guess it returns the elapsed duration. When you look at this command as a sentence, it doesn't look right:
std::cout << "calculated " << sqrtsum << " in " << sw.restart() << " us\n";
How about renaming to delta()
?
Actually, I have a similar thing in Java. I have a delta()
method that returns the elapsed time since the last call (or since start), and an elapsed()
method that returns the total time since start. My start()
, stop()
, restart()
methods don't return anything, and I feel it's right that way. Perhaps you can consider this alternative approach.
Coding style
I don't know what is the standard coding style in C++ world (and would appreciate a link!), but I think many of the standards I know in Java could be applied to C++ too, so I'll mention a few.
for (int i=0; i<iterations; ++i) sqrtsum += pow(i,0.5);
It's recommended to use braces and indenting the block even for single statement fors, ifs, to avoid mistakes. Also, spaces around operators make the code more readable, like this:
for (int i = 0; i < iterations; ++i)
{
sqrtsum += pow(i, 0.5);
}
(Btw I'm very happy to see you using ++i
instead of i++
, few people I know appreciate its significance!)
Modeling real life
I'm getting the feeling that the start
and stop
methods come from the notion of a stop watch in real life. In real life you first click start, then click stop, and read the elapsed time. To measure another thing, you click reset and then start again, and so on.
I think you can free yourself from a real life stop watch and think in terms of the functionality you're actually going to use:
- Create
stopwatch
- Do something
- Print elapsed time
- Do something else
- Print elapsed time
- ...
The stop
and restart
actions just don't come into play here. When you think of what you actually want to do, the terms just don't pop up like that. You just want the elapsed time since the last "tick", again and again. start
also isn't there, because it make sense to do that when you create the stopwatch
, simply because there's nothing else really to do in the constructor of this object. Instead of doing nothing, you might as well just start it.
So I would rename restart
to delta
or similar (elapsed
, or even tick
), and omit start
and stop
completely. "stopwatch" might not be the best name for the class either. (I called mine "timer", but not sure that's much better.)
-
1\$\begingroup\$ C++ does not have a "standard coding style".
++i
vsi++
is basically a micro-optimisation because the compiler will typically (well, always really) do it for you in loops. Outside of that this advice is good. \$\endgroup\$Rapptz– Rapptz2014年07月25日 21:19:54 +00:00Commented Jul 25, 2014 at 21:19 -
\$\begingroup\$ I thought about naming and what (if anything) to return. Note that
stop
,start
andrestart
all imply a command, whileelapsed
is merely reporting, accurately reflecting actual implementation. That's the philosophy behind the existing names, but I'm open to suggestions for a better replacement set of names. Return values were based on observation of how I actually used the stopwatch. \$\endgroup\$Edward– Edward2014年07月25日 22:48:10 +00:00Commented Jul 25, 2014 at 22:48 -
\$\begingroup\$ That's the thing, since they imply a command, they don't fit intuitively in sentences like
"calculated x in " << restart() << " seconds"
or"calculated x in " << stop() << " seconds"
. Something that sounds reporting or evoking the notion of elapsed time would be more natural, and read like a sentence. \$\endgroup\$janos– janos2014年07月25日 22:53:42 +00:00Commented Jul 25, 2014 at 22:53 -
\$\begingroup\$ @janos: In principle, I agree. I just couldn't think of anything better. Can you? \$\endgroup\$Edward– Edward2014年07月26日 02:00:29 +00:00Commented Jul 26, 2014 at 2:00
-
1\$\begingroup\$ I name it
delta
(as I already wrote it in the answer). A sentence like"calculated x in " << sw.delta() << " seconds"
seems natural to me. Other ideas for the name:elapsed
,tick
. And I don't havestart
,stop
,restart
methods, as all I ever use is the.delta()
. \$\endgroup\$janos– janos2014年07月26日 05:33:46 +00:00Commented Jul 26, 2014 at 5:33
I would like this interface:
StopWatch sw;
SomeTimeType time = sw.time([](){
for (int i=0; i<iterations; ++i)
sqrtsum += sqrt(i);
});
std::cout << "It took: " << time << " Units\n";
I don't like the interface as it stands because you can call the functions in any order (which makes no sense). The interface should be designed so you can not call the functions in the wrong order. Which points at a design that does not involve explicit functions calls but a more declarative approach.
DurationT restart(); // Probably need to check the documentation on what that means.
DurationT elapsed() const; // What happens if I call this on a currently running watch?
// I would still expect it to give me the current elapsed time
// but currently it return 0
-
\$\begingroup\$ Perhaps the interface is too flexible, but being able to accumulate time spent in multiple arbitrary slices of code is a useful feature. Also, I'd prefer to measure performance without affecting variable scopes, which lambda could do. \$\endgroup\$200_success– 200_success2014年07月25日 21:32:54 +00:00Commented Jul 25, 2014 at 21:32
-
\$\begingroup\$ @200_success: One doesn't have to choose, really. I have a templated function called
timeit
that uses theStopwatch
class and accepts a function (including a lambda). I didn't post it because I found I never actually use it. \$\endgroup\$Edward– Edward2014年07月25日 22:40:12 +00:00Commented Jul 25, 2014 at 22:40 -
\$\begingroup\$ @200_success: Multiple arbitrary slices are not really affected (because addition is not hard to do). \$\endgroup\$Loki Astari– Loki Astari2014年07月26日 16:17:42 +00:00Commented Jul 26, 2014 at 16:17
-
\$\begingroup\$ @LokiAstari +1 "What happens if I call this on a currently running watch? I would still expect it to give me the current elapsed time but currently it returns 0" I also made this point, it's pretty serious. \$\endgroup\$Pharap– Pharap2014年07月26日 20:32:48 +00:00Commented Jul 26, 2014 at 20:32
Maybe we all have different ideas on what a Stopwatch
is. I see a Stopwatch
as an object that measures the total_elapsed_time
when the Stopwatch
is active. That means functionally, a Stopwatch
should have:
start()
- If inactive, define the start time for this period and make theStopwatch
active.stop()
- If active, add the elapsed time during this duration period to the total elapsed time theStopwatch
was active, then set theStopwatch
as inactive. Whether it returns the elapsed time is up to you.split()
orelapsed()
- If inactive, return thetotal_elapsed_time
theStopwatch
was active for. If active, calculate/update thetotal_elapsed_time
theStopwatch
was active for and return it.reset(state)
- Thetotal_elapsed_time
of theStopwatch
is reset to0
. State of theStopwatch
could be active or inactive (similar to the CTor).
An example of a Stopwatch class that follows this pattern of states used in production code is the System.Diagnostics.Stopwatch class in the .Net standard library.
I'm not a big fan of returning double
s for your DurationT
s. Return your durations as TypeT
objects to preserve the std::chrono
type-safe property.
Also, I have thought it might be nice to provide some kind of a "units" function to return, say, a
std::string
containing "ms" ifTimeT
is set tostd::chrono::milliseconds
but ultimately decided against complicating the interface. I'd be open to comments on that decision as well.
A conversion operator (operator std::string()
) that wraps elapsed()
might work. Use TimeT::period::num
and TimeT::period::den
against a prefix look-up table (could just use the std::ratio
as your key).
-
\$\begingroup\$ I like your prefix look-up table idea. I am experimenting with implementations of it now. \$\endgroup\$Edward– Edward2014年07月26日 16:53:01 +00:00Commented Jul 26, 2014 at 16:53
-
1\$\begingroup\$ I'd say the units thing should be delegated to a different class i.e. a
TimeFormatter
class. You could then provide different formatters for different time units and they'd all work through the same class/interface. +1 for Laying out the way a stopwatch should work, I totally agree that a stopwatch should work like that and the stopwatch presented does not. \$\endgroup\$Pharap– Pharap2014年07月26日 21:02:08 +00:00Commented Jul 26, 2014 at 21:02 -
\$\begingroup\$ @Pharap Your idea is much better than mine. \$\endgroup\$Snowhawk– Snowhawk2014年07月26日 21:25:00 +00:00Commented Jul 26, 2014 at 21:25
I have several things I'd like to mention, but to name a few:
- Your stopwatch doesn't truly stop. What good is a stopwatch that doesn't stop?
start
's body should be separated into two lines. Doing multiple assignments on a single line is bound to confuse at least one reader.Stop
andRestart
should return either void or a bool indicating success.elapsed
should return a valid result even if thestopwatch
is running, you should not have to stop thestopwatch
just to be able to get an accurate reading. Think of a real stopwatch, you don't need to stop it to read how much time has elapsed, you get an updated value when you consciously check for one.- The
stopwatch
should have a way of telling what state it is in to prevent calls being made out of order by throwing exceptions or by ignoring calls being made out of order by only changing state when it is valid to do so (i.e. stop would only run its body if thestopwatch
were in the running state).
-
\$\begingroup\$ The stopwatch does stop. But just like a mechanical stopwatch, it doesn't stop time itself (represented here by the underlying
ClockT
). \$\endgroup\$Edward– Edward2014年07月25日 20:09:11 +00:00Commented Jul 25, 2014 at 20:09 -
\$\begingroup\$ @Edward If I were to call
stop
, then record the result ofelapsed
, then call stop again, it would change the value ofelapsed
such that it would be as if the stopwatch had still been running. To truly make your stopwatch stop you should have a way of maintaining state, be it abool
indicating whether the stopwatch is running or anenum
with values ofRunning
andStopped
. \$\endgroup\$Pharap– Pharap2014年07月26日 01:28:26 +00:00Commented Jul 26, 2014 at 1:28 -
\$\begingroup\$ Have you got a use case in mind in which you'd use that? \$\endgroup\$Edward– Edward2014年07月26日 04:26:54 +00:00Commented Jul 26, 2014 at 4:26
-
1\$\begingroup\$ Calculating load times of reading multiple files, but pausing during the processing of the data, or vice-versa, or any other situation where you need to pause the stopwatch. \$\endgroup\$Snowhawk– Snowhawk2014年07月26日 04:31:58 +00:00Commented Jul 26, 2014 at 4:31
-
\$\begingroup\$ Sounds like
pause()
andunpause()
make more sense... You could store the paused time and when unpausing just add the difference to the start time or keep track of a paused time total to subtract when callingelapsed()
. I could see alap()
call being useful too, say if you wanted to use one stopwatch to record several parts of loading a file (i.e. reading from disk, parsing, inserting into database, etc.) with a lap() for each and the total time for processing the file from start to finish at the end. \$\endgroup\$Jason Goemaat– Jason Goemaat2014年07月26日 06:46:29 +00:00Commented Jul 26, 2014 at 6:46
Every time I've needed a stopwatch, this is the sort of thing I've been after. No complications, just a simple start and stop function. I reckon you just document what the units are in, and people can do conversion themselves.
One thing that is convenient is something I saw in the .NET version of this, where you just have a static class that returns a running clock. Makes it even easier.
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
// Do something
stopwatch.Stop();
Actually it looks like you're already doing that.
-
\$\begingroup\$ Note that the reason
StartNew
is a static method is because it is separate from the constructor, which avoids starting theStopwatch
. +1 for looking at an existing implementation as a reference though. \$\endgroup\$Pharap– Pharap2014年07月26日 22:52:57 +00:00Commented Jul 26, 2014 at 22:52