I'm porting a Python metrics library to C++. One API the Python library provides is a function decorator to easily record timing data on functions. By modifying the function definition to be
@timed('timing.foo')
def foo():
...
foo_result = foo()
is essentially turned into
start = time.time()
foo_result = foo()
post_metric_to_endpoint('timing.foo', time.time() - start)
In Function hooking in C++, we find this answer that wraps instances and places the burden of calling the timing function on the caller, which means we won't get performance data across the codebase (it's annoying to update in the first place, and easily forgotten later). Similarly, this answer to Timing in an elegant way in c++ requires altering the call sites. This other answer to that question provides a way of wrapping arbitrary blocks of code, which theoretically means we could indent the entire body of a function we want to time and nest it inside a scope. This is the closest I've found to what I want, but is pretty ugly, fairly intrusive, and I'm not sure of the performance impact.
Since this is meant to be used as a library, we can modify the source of the functions we want to time; in fact, this is preferable, as I'd like every call of that function to be timed. Most discussions seem to focus on ad-hoc profiling in development, whereas I'm trying to build a system that would be always active in a production environment.
Although C++ does not have explicit language support for decorators, it turns out that you can "emulate" them quite nicely using C++14 generic lambdas. Here is my take on it:
#include <iostream>
template<class T>
auto decorator(T&& func)
{
// we create a closure below
auto new_function = [func = std::forward<T>(func)](auto&&... args)
{
std::cout << "BEGIN decorating...\n";
auto result = func(std::forward<decltype(args)>(args)...);
std::cout << "END decorating\n";
return result;
};
return new_function;
}
int f(int x)
{
std::cout << x * x << '\n';
return x * x;
}
auto decorated = decorator(f);
auto double_decorated = decorator(decorator(f));
int main()
{
decorated(5);
double_decorated(10);
}
Live on Coliru
Of course inside the decorator you can add whatever you want (including timing etc), above is just a minimal example.
If it looks too daunting you can ignore the mumbo-jumbo std::forward
and C++14 generalized lambda captures and simply have
#include <iostream>
template<class T>
auto decorator(T func)
{
// we create a closure below
auto new_function = [func](auto... args)
{
std::cout << "BEGIN decorating...\n";
auto result = func(args...);
std::cout << "END decorating\n";
return result;
};
return new_function;
}
int f(int x)
{
std::cout << x * x << '\n';
return x * x;
}
auto decorated = decorator(f);
auto double_decorated = decorator(decorator(f));
int main()
{
decorated(5);
double_decorated(10);
}
C++ doesn't have Pythonic decorators, but it has deterministic destruction. To time a function, create a class that captures a timestamp at construction time, and reports at destruction. Then declare an instance as the first line in the function:
class Timed
{
//...
}
void foo()
{
Timed t_;
//Do the rest of work
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With