I had time measuring pretty much figured out when I ended up using this structure.
#include <iostream>
#include <chrono>
template<typename TimeT = std::chrono::milliseconds>
struct measure
{
template<typename F>
static typename TimeT::rep execution(F const &func)
{
auto start = std::chrono::system_clock::now();
func();
auto duration = std::chrono::duration_cast< TimeT>(
std::chrono::system_clock::now() - start);
return duration.count();
}
};
a use case would be
struct functor
{
int state;
functor(int state) : state(state) {}
void operator()() const
{
std::cout << "In functor run for ";
}
};
void func()
{
std::cout << "In function, run for " << std::endl;
}
void func(int arg) {}
int main()
{
int dummy(1);
std::cout << measure<>::execution( [&]() {
func(dummy);
}) << std::endl;
std::cout << measure<>::execution(functor(dummy)) << std::endl;
std::cout << measure<>::execution(func);
return 0;
}
But since I started using my struct in various posts on Stack Exchange sites, I've had remarks and comments about the code (like on func
being a const ref or not etc).
I'd like a review on the above and your suggestions on how I can approve this code (or leave it alone at last).
-
2\$\begingroup\$ Your class won't work with functions which need paramaters. Is it supposed to be so? \$\endgroup\$edmz– edmz2014年05月03日 18:42:59 +00:00Commented May 3, 2014 at 18:42
-
\$\begingroup\$ It'll work, just call the function throught a lambda \$\endgroup\$Nikos Athanasiou– Nikos Athanasiou2014年05月03日 18:45:12 +00:00Commented May 3, 2014 at 18:45
-
\$\begingroup\$ @ NikosAthanasiou Yes, that's possible. But measurements will be out of phase, since you are measuring the other function's execution time too. \$\endgroup\$edmz– edmz2014年05月03日 18:50:57 +00:00Commented May 3, 2014 at 18:50
-
\$\begingroup\$ @black Lambda functions are always inlined, so the code is called in place. Besides, for any measurement to take place, a single function call (which as stated before will be inlined) is not a factor. \$\endgroup\$Nikos Athanasiou– Nikos Athanasiou2014年05月03日 18:53:47 +00:00Commented May 3, 2014 at 18:53
-
\$\begingroup\$ @NikosAthanasiou As you prefer. But consider that it is neither logical nor expected to work that way. \$\endgroup\$edmz– edmz2014年05月03日 19:07:22 +00:00Commented May 3, 2014 at 19:07
4 Answers 4
- Extend your timer function so it can take all the parameters needed by
func()
- Don bother to pass
func
by const reference.
What I would have done
template<typename TimeT = std::chrono::milliseconds>
struct measure
{
template<typename F, typename ...Args>
static typename TimeT::rep execution(F func, Args&&... args)
{
auto start = std::chrono::system_clock::now();
// Now call the function with all the parameters you need.
func(std::forward<Args>(args)...);
auto duration = std::chrono::duration_cast< TimeT>(std::chrono::system_clock::now() - start);
return duration.count();
}
};
-
1\$\begingroup\$ +1 . Those variadic templates solve so many problems! Would you recommend a source where I can learn more ? (please don't say the Standard) \$\endgroup\$Nikos Athanasiou– Nikos Athanasiou2014年05月03日 19:34:48 +00:00Commented May 3, 2014 at 19:34
-
2\$\begingroup\$ @NikosAthanasiou: This person seems to know what he is talking about. codereview.stackexchange.com/users/39083/iavr read some of his answers \$\endgroup\$Loki Astari– Loki Astari2014年05月03日 19:36:30 +00:00Commented May 3, 2014 at 19:36
-
\$\begingroup\$ Quick question. Why can't it compile the original example ? I'm supposing due to the overloads func, but can I have any thoughts on that? \$\endgroup\$Nikos Athanasiou– Nikos Athanasiou2014年05月03日 19:52:50 +00:00Commented May 3, 2014 at 19:52
-
1\$\begingroup\$ @NikosAthanasiou: Its because you have two functions named
func()
. It does not know which one you mean when you gomeasure<>::execution(func);
(it could be either). You can either name the functions differently or you can be specific when specifying them by providing the functions typemeasure<>::execution<void()>(func);
. \$\endgroup\$Loki Astari– Loki Astari2014年05月03日 23:20:55 +00:00Commented May 3, 2014 at 23:20 -
\$\begingroup\$ I updated the SO post with an improvement : We can use a universal reference for the
func
provided that we forward the function call \$\endgroup\$Nikos Athanasiou– Nikos Athanasiou2015年08月28日 12:00:08 +00:00Commented Aug 28, 2015 at 12:00
It seems a bit of a bother to approach it as a function template when it seems to me that it's more like an object. For that reason, here's an alternative that might be a little more natural to use:
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() { start(); }
void start() { _start = _end = ClockT::now(); }
DurationT stop() { _end = ClockT::now(); return elapsed();}
DurationT elapsed() {
auto delta = std::chrono::duration_cast<TimeT>(_end-_start);
return delta.count();
}
};
Then you can use it like this:
Stopwatch<> sw;
functionToBeTimed(arg1, arg2);
sw.stop();
std::cout << "functionToBeTimed took " << sw.elapsed() << " microseconds\n";
-
\$\begingroup\$ Disagree with
more natural
. But this has the advantage you can time multiple function calls. \$\endgroup\$Loki Astari– Loki Astari2014年05月03日 23:29:56 +00:00Commented May 3, 2014 at 23:29 -
1\$\begingroup\$ You can also time things that are not functions. \$\endgroup\$Edward– Edward2014年05月04日 00:38:30 +00:00Commented May 4, 2014 at 0:38
-
2\$\begingroup\$ I would be careful about timing things that are not functions because of optimizations that the compiler is allowed to do. I am not sure about the sequencing requirements and the observable behavior understanding of such a program. With a function call the compiler is not allowed to move statements across the call point (they are sequenced before or after the call). \$\endgroup\$Loki Astari– Loki Astari2014年05月06日 15:52:15 +00:00Commented May 6, 2014 at 15:52
-
\$\begingroup\$ +1. I find this quite natural. I usually write my own timer object like that, with members called
tic()
,toc()
(instead ofstart()
,stop()
), following the matlab style. Plustac()
, which measures elapsed time without stopping the timer. But all this is complementary rather than competitive to the proposedmeasure()
function template (including parameters). Why not have all in a timer object? \$\endgroup\$iavr– iavr2014年05月07日 00:53:06 +00:00Commented May 7, 2014 at 0:53 -
\$\begingroup\$ @iavr: good point about them being complementary mechanisms. Also, while I never liked the overly cute matlab names, I'll point out that
stop()
isn't an entirely accurate name and actually functions more liketoc()
in that neitherstop()
nortoc()
actually stop the timer. \$\endgroup\$Edward– Edward2014年05月07日 01:51:06 +00:00Commented May 7, 2014 at 1:51
You should use std::chrono::steady_clock
instead of std::chrono::system_clock
. This is because system_clock
may get adjusted every now and then (e.g. it may rewind back a bit, or jump forward more than the actual elapsed time in order to keep in sync with the wall/world clock), whereas steady_clock is a monotonic clock that is never adjusted. Therefore, steady_clock
is better suited for what you're doing.
P.S. If you need to know how much CPU-time your function is actually taking, even steady_clock
may not be ideal, as it will only measure the duration from the time your function is called, to the time it returns. This may not necessarily be equal to the amount of CPU-time the function took, as other processes and threads can interrupt your function and result in significantly longer execution times. If you want to isolate the effects of other system activities, you should google OS/platform-specific alternatives for getting the thread execution time or look up the chrono functions in Boost (look for the process_...cpu_clock
and thread_clock
classes).
There isn't much more to improve after the code has begun to accept function parameters.
These are some details, you could freely ignore them:
Class names should begin with an uppercase letter
struct Measure
You don't need the
duration
variable; just do:return std::chrono::duration_cast< TimeT>(std::chrono::system_clock::now() - start).count();
-
2\$\begingroup\$ I agree that it is a good idea to have type names begin with Upper case letters (seems to be a common practice). But
should
is a bit strong. \$\endgroup\$Loki Astari– Loki Astari2014年05月03日 23:25:02 +00:00Commented May 3, 2014 at 23:25 -
2\$\begingroup\$ Disagree on removing the variable
duration
. The compiler will remove all wasted space so the code will still be optimal (ie no different than your version) with the variable. But it helps in reading the code (and code is for the human to read so make it easy on us humans and leave the variables in to make it easier to read). Also when debugging it makes it easier to check the result when you have it stored in a variable. \$\endgroup\$Loki Astari– Loki Astari2014年05月03日 23:27:07 +00:00Commented May 3, 2014 at 23:27 -
\$\begingroup\$ I kind of disagree on both:
duration
removal for the same reason as Loki, class name capitalization because you are free to choose your own code style (actually standard library classes are not capitalized, aren't they?) \$\endgroup\$pqnet– pqnet2014年08月06日 04:58:26 +00:00Commented Aug 6, 2014 at 4:58