C++ use std::chrono to measure execution of member functions in a nice way

Viewed 1435

I want to optimize my application, especially the execution speed of certain functions.

Imagine there is a class with some member functions

class Test
{
public:
    Test();
    virtual ~Test();
    int init(int arg1, double arg2);

private:
    [...]

and in my constructor I call one of these methods

Test::Test()
{
    [...]
    int value = init(1, 1.2);
}

How can I measure the execution time of my method init(...) in a nice and clean way without breaking my program?

At the moment I use following code

Test::Test()
{
    [...]
    auto start = std::chrono::high_resolution_clock::now();

    int value = init(1, 1.2);

    auto stop = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> duration = stop - start;
    std::cout << duration.count() * 1000 << "ms\n";
}

It works as expected but I think it is pretty messy and I want to have a "cleaner" solution.

Is there a way to have some kind of function which takes a member function and other parameters like so

int value = countTime(function, arg1, arg2);

I don't know whether it is possible to pass the return value from function() to countTime() in order to don't interrupt the workflow of my code.

EDIT: This is my TimeMeasure class

namespace tools 
{
    class TimeMeasure 
    {
    public:
        TimeMeasure() 
        {
            m_start = std::chrono::high_resolution_clock::now();
        }

        virtual ~TimeMeasure()
        {
            m_stop = std::chrono::high_resolution_clock::now();
            std::chrono::duration<double, std::milli> duration = m_stop - m_start;
            std::cout << duration.count() << "ms\n";
        }

    public:
        typedef std::chrono::time_point<std::chrono::high_resolution_clock> HighResClock;

    private:
        HighResClock m_start;
        HighResClock m_stop;
    };

    template <typename T, typename F, typename... Args>
    auto measure(T *t, F &&fn, Args... args)
    {
        tools::TimeMeasure timeMeasure;
        return (t->*fn)(std::forward<Args>(args)...);
    }
}

and in my constructor Test() I use the function measure this way

Test()
{
    [...]
    tools::measure(this, Test::init, filepath);
}

int init(const std::string& filepath) const takes here a string to a file. So in my case it's just one argument

Unfortunately I get a invalid use of non-static member function 'int init(const string&) const' error

I would wonder if a constructor is not a member function. So why do I get this error?

EDIT 2:

According to OznOg's answer I just forgot to hand in a pointer to my function.

So this would be the correct function call

tools::measure(this, &Test::init, filepath);
3 Answers

You could create a class like:

struct MeasureTime {
    MeasureTime() : _start(std::chrono::high_resolution_clock::now()) {}

    ~MeasureTime() {
        auto stop = std::chrono::high_resolution_clock::now();
        std::chrono::duration<double> duration = stop - _start;
        std::cout << duration.count() * 1000 << "ms\n";
    }
private:
    std::chrono::time_point<std::chrono::high_resolution_clock>  _start;
};

and simply use it in your code:

Test::Test()
{
    MeasureTime mt;
    [...]
    { //or even this for just the init call
    MeasureTime mt2;
    int value = init(1, 1.2);
    }
}

IMHO it is less intrusive than what you proposed.

If you really want a function, you may try a wrapper like:

template <class T, class F, class... Args>
auto MeasureTimeFn(T *t, F &&fn, Args&&... args) {
    MeasureTime timer;
     return (t->*fn)(std::forward<Args>(args)...);
}

And call it like:

int value = MeasureTimeFn(this, &Test::init, 1, 1.2);

but not sure it is really much better.

You can try to hide thing with a macro:

#define MEASURE(f, ...) \
  MeasureTimeFn(this, &std::remove_reference_t<decltype(*this)>::f, __VA_ARGS__)

this way you can write

int value = MEASURE(init, 1, 1.2);

what is quite like what you asked for, but only works inside member functions, with member functions (non static).

Anyway probably a good place to start with.

* EDIT* If you can modify inheritance of you class, you may try

template<class T>
struct MeasureTool {
    template <class F, class... Args>
    auto measure(F &&fn, Args&&... args) {
        tools::TimeMeasure timeMeasure;
        return (static_cast<T*>(this)->*fn)(std::forward<Args>(args)...);
    }
};

class Test : public MeasureTool<Test>
{
public:
    Test();
    virtual ~Test() {}
    int init(const std::string &filepath) { _path = filepath; return 0; }
    const auto &getPath() const { return _path; }
private:
    std::string _path;

};

Test::Test()
{
    std::string filepath("/some/where");
    int value = measure(&Test::init, filepath);
    measure(&Test::getPath);
}

And, this time, seems to match your very first requirement (but is quite intrusive...)

now, it's all in your hands :)

I use always Boost.Timer.

#include <boost/timer/timer.hpp>

...
boost::timer::cpu_timer timer;

for(int i = 0; i < 1000; ++i)
  funct();

std::cout << timer.format();

It shows the wall, the user, and the system time.

You can use such implementation:

template<typename Fn, typename... Args>
typename std::enable_if<!std::is_void<typename std::result_of<Fn&&(Args...)>::type>::value, 
    std::tuple<std::chrono::duration<double>, typename std::result_of<Fn&&(Args...)>::type>
>::type
countTime(Fn&& fn, Args&&... args) {
    auto start = std::chrono::high_resolution_clock::now();
    auto fnret = std::forward<Fn>(fn)(std::forward<Args>(args)...);
    auto stop = std::chrono::high_resolution_clock::now();
    return std::make_tuple(stop - start, fnret);
}

template<typename Fn, typename... Args>
typename std::enable_if<std::is_void<typename std::result_of<Fn&&(Args...)>::type>::value, 
    std::tuple<std::chrono::duration<double>>
>::type
countTime(Fn&& fn, Args&&... args) {
    auto start = std::chrono::high_resolution_clock::now();
    std::forward<Fn>(fn)(std::forward<Args>(args)...);
    auto stop = std::chrono::high_resolution_clock::now();
    return std::make_tuple(stop - start);
}

template<typename R, class C, typename... Args>
typename std::enable_if<!std::is_same<R, void>::value,
    std::tuple<std::chrono::duration<double>, R>
>::type
countTime(R (C::*fn)(Args...), C& obj, Args&&... args) {
    auto start = std::chrono::high_resolution_clock::now();
    auto fnret = (obj.*fn)(std::forward<Args>(args)...);
    auto stop = std::chrono::high_resolution_clock::now();
    return std::make_tuple(stop - start, fnret);
}

template<class C, typename... Args>
std::tuple<std::chrono::duration<double>>
countTime(void (C::*fn)(Args...), C& obj, Args&&... args) {
    auto start = std::chrono::high_resolution_clock::now();
    (obj.*fn)(std::forward<Args>(args)...);
    auto stop = std::chrono::high_resolution_clock::now();
    return std::make_tuple(stop - start);
}

This will return a tuple with one member duration in case of void (*)(...) functions and a tuple with duration and return type in case of functions returning smth. It works with std::bind and member functions. Probably the overload may be merged into one or two implementations, but i have no idea how to do that.

Example call:

auto ret = countTime([](int a) -> int { 
    std::this_thread::sleep_for(std::chrono::milliseconds(a)); 
    return a * 2;
}, 10);
std::cout << "function executed in: " <<
    std::chrono::duration_cast<std::chrono::milliseconds>(std::get<0>(ret)).count() <<
    " milliseconds." << std::endl;
std::cout << "function returned: " << std::get<1>(ret) << std::endl;

Simpler example call:

auto ret = countTime(&Test::init, *this, 1, 1.2);
int value = std::get<1>(ret);

std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(std::get<0>(ret)).count() << "ms" << std::endl;

Simple simpler example call, assuming int init(int, int) function:

auto ret = countTime(init, 1, 1.2);
int value = std::get<1>(ret);

std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(std::get<0>(ret)).count() << "ms" << std::endl;

Live version available at onlinegdb.

Related