2

I want to measure the time of a function execution.

I could use something like this:

using namespace std::chrono;
auto start = steady_clock::now();
// process
auto end = duration<double>(steady_clock::now() - start).count();

but I it seems pretty non-DRY to me. So I created a small function to do it:

template <typename Function, typename... Args>
auto measure_time(Function func, Args&&... args)
{
    using namespace std::chrono;
    auto start = steady_clock::now();
    func(std::forward<Args>(args)...);   // handle lvalues and rvalues
    return duration<double>(steady_clock::now() - start).count();
}

and I call it as such:

measure_time(func, arg1, arg2, arg3);       // for common functions
measure_time([](){func(arg1,arg2, arg3););  // for member or template functions

This works fine for me, but it does come with some drawbacks:

  1. it is not clear to me how it be conveniently changed to also retrieve the return value of func (which, of course, can also be void)?
  2. this clearly contradicts with the one-thing-per-function rule
  3. the readability of the code is significantly undermined:
    important_function(arg1, arg2);                // reads well
    measure_time(important_function, arg1, arg2);  // measure_time steals the spotlight 
    

Are there any guidelines to face these issues?


Update:

I forgot to mention that after the execution of the function I need to store the time elapsed to a container.


Update 2:

after @puio 's answer, I ended up with this:

using namespace std::chrono;
template <typename Container>
class Timer{
   public:
    Timer(Container& _c) : c(_c) {}
    ~Timer() {
        c.push_back(duration<float>(steady_clock::now() - start).count());
    }
   private:
    time_point<steady_clock> start{steady_clock::now()};
    Container& c;
};

Usage:

auto mc = MyContainer{};
...
{
    auto t = Timer<MyContainer>(mc);
    // things to measure
}
// mc.back() is the elapsed time

DRY and clean :)

  • 4
    Benchmarking is not trivial better use a library like this: [Google benchmark](https://github.com/google/benchmark). The optimizer can totally stand in your way. Maybe your function gets never called if the return value is not used and so on – Thrasher Aug 27 '20 at 10:40
  • I guess you could have the type of the return in there too, but makes it looks even stranger. – Carlos Aug 27 '20 at 10:41
  • For small benchmarks you can also use [quick bench](https://quick-bench.com/) – Thrasher Aug 27 '20 at 10:42
  • @Thrasher Noted. But in this case I want to store the execution times in a container and display them to the user at a GUI. I am not worried about anything being optimized out because the measured functions are triggered by the user. So this is not benchmarking per se. – Stelios Daveas Aug 27 '20 at 10:47
  • you can look at the implementation of [`std::invoke`](https://en.cppreference.com/w/cpp/utility/functional/invoke) to get some inspiration. Its probably much more generic than what you need – 463035818_is_not_an_ai Aug 27 '20 at 10:52
  • @idclev463035818 even if this work, doesn't it still "hide" the `func` inside `std::invoke`? – Stelios Daveas Aug 27 '20 at 12:01
  • sorry for not being clear, I was refering to 1. how to retrieve the return value. didnt suggest to use `std::invoke` but its implementation does already most of what you want – 463035818_is_not_an_ai Aug 27 '20 at 12:06
  • 1
    Beware, `vector::push_back()` can throw, which will crash if happens within a destructor (which is `noexcept` by default). – rustyx Aug 27 '20 at 12:16
  • @rustyx Thanks for the clarification! So I should declare the destructor as nonxcept(false)? Seems a bit fishy. – Stelios Daveas Aug 27 '20 at 14:43

3 Answers3

2

Instantiate this class in a function and it will print the time when the object goes out of scope.

class Timer {
 private:
  std::chrono::steady_clock::time_point begin;
  std::string_view func_name;

 public:
  Timer(std::string_view func)
  {
    func_name = func;
    begin = std::chrono::steady_clock::now();
  }
  ~Timer()
  {
    const std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
    std::cout << func_name << " Time: "
              << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count()
              << " microseconds " << std::endl;
  }
};

Usage:

void slow_func(){
  Timer timer{__func__};
  // ...
  // Destruct automatically.
}

The chrono functions taken from here since I keep forgetting it.


But I need to store the time elapsed in a container

I imagine it is std::vector, pass it by non-const reference (as a return value) to the Constructor, store the reference in private access, and push_back the time to the vector in the destructor.

A public member function to do so, or calling the destructor explicitly with the said std::vector just makes it cumbersome. Also, callers may miss using that function.

puio
  • 1,208
  • 6
  • 19
1

If recording the time is not part of the main goal of your program. You can use GCC -finstrument-functions option and provide a definition for the functions:

struct Record {
   std::chrono::time_point<std::chrono::steady_clock> start, stop;
};
std::map<void *, Record> container;

void __cyg_profile_func_enter (void *this_fn,
                               void *call_site) {
    auto &record = container[this_fn];
    auto start = std::chrono::steady_clock::now();
    // do something with start
    record.start = start;
}

void __cyg_profile_func_exit  (void *this_fn,
                               void *call_site) {
    auto stop = std::chrono::steady_clock::now();
    // do something with stop
    container[this_fn].stop = stop;
}
Jorge Bellon
  • 2,901
  • 15
  • 25
1

... it is not clear to me how it be conveniently changed to also retrieve the return value of func

Another possible solution is to return a tuple of the duration and (optionally) the function's return value, when it's not void.

Something like this (C++17):

using Clock = std::chrono::high_resolution_clock;

template <typename Function, typename... Args>
auto measure_time(const Function& function, Args&&... args) {
    auto start = Clock::now();

    if constexpr (std::is_same_v<std::invoke_result_t<Function, Args...>, void>) {
        std::invoke(function, std::forward<Args>(args)...);
        return std::make_tuple<Clock::duration>(Clock::now() - start);
    }
    else {
        std::tuple<Clock::duration, std::invoke_result_t<Function, Args...>> res{0, std::invoke(function, std::forward<Args>(args)...) };
        std::get<0>(res) = Clock::now() - start;
        return res;
    }
}

Use as:

using namespace std::chrono_literals;

int main() {
    auto [dur, res] = measure_time(&some_func, some_args);
    std::cout << "some_func: " << res << ", time: " << dur / 1us << "us\n";
}

Notes:

  • By creating the tuple together with the invoke result, we benefit from RVO (the result will be stored at the call site, never copied/moved).

  • std::invoke allows invoking pointer-to-member functions.

rustyx
  • 80,671
  • 25
  • 200
  • 267