Generic duration meter for any operator, function call and constructor

Viewed 77

I was using a templatized meter function (see below) to measure the elapsed time for functions. Then I also wanted to use it for constructors.

As far as I know there is no way of passing a type as a function parameter directly. So I came up with this workaround, passing it solely as the template parameter (minimal example):

template <typename T, typename ... P>
auto meter(T t, P ... p) {
    auto t1 = high_resolution_clock::now();
    t(p...);
    auto t2 = high_resolution_clock::now();
    auto dif = t2-t1;   
    return duration_cast<microseconds>(dif);
}

template <typename T, typename ... P>
auto meter(P ... p) {
    auto t1 = high_resolution_clock::now();
    auto t = T(p...);
    auto t2 = high_resolution_clock::now();
    auto dif = t2-t1;   
    return duration_cast<microseconds>(dif);
}

int main() {
    auto d = meter(g, 1.0, 20.0); //meter the function call g(1.0, 20.0)
    std::cout << "Ellapsed time: " << d.count() << " microseconds\n";
    d = meter(complex_obj{2}); //meter () operator of complex_obj, assuming complex_obj{int} is trivial;
    std::cout << "Ellapsed time: " << d.count() << " microseconds\n";
    d = meter<complex_obj>(); //meter constructor complex_obj();
    std::cout << "Ellapsed time: " << d.count() << " microseconds\n";
}

Trying this got me thinking. Is there a general/consistent way of rewriting this, as to apply to any kind of computation (not just constructor, but maybe even other operators like (obj1 < obj2)? I noticed, that I was already (accidentally) supporting the () operator of structs.

Sorry if this question became to broad, my main question is, if there is a way to unite the syntax of meter call, for functions and constructors alike.

2 Answers

You can wrap the code to be measured inside a lambda (since C++11):

#include <chrono>
#include <iostream>

template<class F>
auto meter(F&& f) {
  auto t1 = std::chrono::high_resolution_clock::now();
  f();//                                                <-- operator() of the lambda
  auto t2 = std::chrono::high_resolution_clock::now();
  auto dif = t2-t1;
  return std::chrono::duration_cast<std::chrono::microseconds>(dif);
}

void g(double x, double y) {
  std::cout << "g(" << x << ", " << y << ")\n";
}

int main() {
  double x = 1.0;
  auto d = meter([&] {
    // This comment is inside the *body* of the lambda.
    // Code of the {body} is executed upon `operator()`.
    g(x, 20.0);// note that you can use `x` here thanks to the capture-default `[&]`
  });
  std::cout << "time: " << d.count() << " ms\n";
}

However you encapsulate the actual function call, it'll probably be for the best if you make your meter function return the value returned by the function it's measuring to make it possible to chain calls - but still with the possibility to check how long time each individual call took afterwards. This makes it theoretically possible for RVO / copy elision to kick in and therefor not slow down the code as much. Example:

#include <chrono>
#include <iostream>
#include <thread> // for debug sleeps only

using namespace std::chrono;

template<typename D, typename F, typename... P>
auto meter(D& dur, F func, P&&... params) {
    auto start = high_resolution_clock::now();
    auto retval = func(std::forward<P>(params)...);
    // put duration in the duration reference
    dur = duration_cast<D>(high_resolution_clock::now() - start);
    // and return func()'s return value
    return retval;
}

namespace m {
double add(double a, double b) {
    std::this_thread::sleep_for(milliseconds(10));
    return a + b;
}
double sub(double a, double b) {
    std::this_thread::sleep_for(milliseconds(11));
    return a - b;
}
double mul(double a, double b) {
    std::this_thread::sleep_for(milliseconds(12));
    return a * b;
}
double div(double a, double b) {
    std::this_thread::sleep_for(milliseconds(13));
    return a / b;
}
} // namespace m

int main() {
    milliseconds Add, Sub, Mul, Div;

    // chaining calls for this calculation:
    // (1000 / (100 * (4.3 - (1.1+2.2))))
    auto result = meter(Div, m::div,
        1000.0, meter(Mul, m::mul,
            100.0, meter(Sub, m::sub,
                4.3, meter(Add, m::add,
                    1.1, 2.2)
                )
        )
    );
    std::cout << "Add: " << Add.count() << " ms.\n";
    std::cout << "Sub: " << Sub.count() << " ms.\n";
    std::cout << "Mul: " << Mul.count() << " ms.\n";
    std::cout << "Div: " << Div.count() << " ms.\n";
    std::cout << result << "\n";
}

Probable output:

Add: 10 ms.
Sub: 11 ms.
Mul: 12 ms.
Div: 13 ms.
10
Related