Search code examples
c++templatesprofilingrvalue-reference

Timing a class function in C++


A previous post, Timing in an elegant way in C, showed a neat method for profiling using a wrapper function. I am trying to use one of the profiler to profile my class functions.

#include <cmath>
#include <string>
#include <chrono>
#include <algorithm>
#include <iostream>

template<typename Duration = std::chrono::microseconds,
         typename F,
         typename ... Args>
typename Duration::rep profile(F&& fun,  Args&&... args) {
  const auto beg = std::chrono::high_resolution_clock::now();
  std::forward<F>(fun)(std::forward<Args>(args)...);
  const auto end = std::chrono::high_resolution_clock::now();
  return std::chrono::duration_cast<Duration>(end - beg).count();
}

The profiler works for a normal function, but I am struggling to pass a class function to the profiler.

#include "Profiler.h"

int main()
{
    Config projConfig = Config(projConfigDir);
    std::string imagePath = projConfig.GetTIFF();
    
    ImageVector images_vec = Image::LoadImagesVec(imagePath);
    Detector detector = Detector(&projConfig);

    auto time = profile<std::chrono::seconds>(&Detector::DetectImages, detector, images_vec);
    //detector.DetectImages(images_vec); // if ran without profiler
    std::string _detectTime("DetectImages time elapsed: " + std::to_string(time));
    Logger::Info(_detectTime.c_str());
}

I am unable to compile the code. I got the following error message.

term does not evaluate to a function taking 2 arguments

Because I cannot pass pointer to a bounded function to the profiler, I tried passing in the function, the object instance to call the function and the function's arguments (not sure if this is the correct way). But I suspect that the profiler is not implemented to handle class methods. If so, how should I modify the profiler so that it can accept class functions?


Solution

  • You can use std::bind to create a callable object for invoking a class method on a class object.

    Then you can pass this callable to your profile function as you would pass any function/lambda.
    Note that using std::bind supports also fixing one or more of the method parameters.
    Using std::placeholders (as you can see below) allows to specify them only when invoking the binded callable object.

    See the example below:

    #include <chrono>
    #include <iostream>
    #include <functional>
    #include <thread>
    #include <string>
    
    template<typename Duration = std::chrono::microseconds,
             typename F,
             typename ... Args>
        typename Duration::rep profile(F&& fun, Args&&... args) 
    {
        const auto beg = std::chrono::high_resolution_clock::now();
        std::forward<F>(fun)(std::forward<Args>(args)...);
        const auto end = std::chrono::high_resolution_clock::now();
        return std::chrono::duration_cast<Duration>(end - beg).count();
    }
    
    struct MyClass
    {
        void Run(std::string const & s, double d)
        {
            std::cout << "My id: " << id << ",  MyClass::Run(" << s << ", " << d << ")" << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
        int id{ 333 };
    };
    
    int main()
    {
        MyClass c;
    
        // Run without profiling:
        c.Run("without", 2.3);
    
        // Run  with profiling:
        auto f = std::bind(&MyClass::Run, &c, std::placeholders::_1, std::placeholders::_2);
        auto time = profile<std::chrono::milliseconds>(f, "with", 23);
        std::cout << "time: " << time << std::endl;
        return 0;
    }
    

    Output:

    My id: 333,  MyClass::Run(without, 2.3)
    My id: 333,  MyClass::Run(with, 23)
    time: 109