【问题标题】:Measuring execution time of a function in C++在 C++ 中测量函数的执行时间
【发布时间】:2023-03-09 09:53:02
【问题描述】:

我想知道某个函数在我的 C++ 程序中在 Linux 上执行所需的时间。之后,我想做一个速度比较。我看到了几个时间函数,但最终从 boost 中得到了这个。计时:

process_user_cpu_clock, captures user-CPU time spent by the current process

现在,我不清楚如果我使用上面的函数,我会得到唯一的 CPU 花费在那个函数上的时间吗?

其次,我找不到使用上述函数的任何示例。谁能帮我看看上面的功能怎么用?

P.S:现在,我使用 std::chrono::system_clock::now() 以秒为单位获取时间,但由于每次 CPU 负载不同,这给了我不同的结果。

【问题讨论】:

标签: c++ optimization profiling


【解决方案1】:

这是C++11中一个非常好用的方法。您必须使用来自<chrono> 标头的std::chrono::high_resolution_clock

像这样使用它:

#include <chrono>

/* Only needed for the sake of this example. */
#include <iostream>
#include <thread>
    
void long_operation()
{
    /* Simulating a long, heavy operation. */

    using namespace std::chrono_literals;
    std::this_thread::sleep_for(150ms);
}

int main()
{
    using std::chrono::high_resolution_clock;
    using std::chrono::duration_cast;
    using std::chrono::duration;
    using std::chrono::milliseconds;

    auto t1 = high_resolution_clock::now();
    long_operation();
    auto t2 = high_resolution_clock::now();

    /* Getting number of milliseconds as an integer. */
    auto ms_int = duration_cast<milliseconds>(t2 - t1);

    /* Getting number of milliseconds as a double. */
    duration<double, std::milli> ms_double = t2 - t1;

    std::cout << ms_int.count() << "ms\n";
    std::cout << ms_double.count() << "ms\n";
    return 0;
}

这将测量函数long_operation的持续时间。

可能的输出:

150ms
150.068ms

工作示例:https://godbolt.org/z/oe5cMd

【讨论】:

  • 没有。您的计算机的处理器可以使用更少或更多。 high_resolution_clock 将为您提供函数运行所需的物理时间和实时时间。因此,在您的第一次运行中,您的 CPU 使用率低于下一次运行。 “使用”是指其他应用程序的工作使用 CPU。
  • 是的,如果你需要平均时间,这是一个很好的方法。运行三轮,然后计算平均值。
  • 能否请您发布一般不使用“使用命名空间”的代码。它可以更轻松地查看来自何处的内容。
  • 这不应该是steady_clock吗? high_resolution_clock 不可能是一个非单调时钟吗?
  • 顺便说一句:我建议将 long long number 更改为 volatile long long number。否则,优化器可能会优化掉该循环,而您将获得零运行时间。
【解决方案2】:

这是一个测量作为参数传递的任何函数的执行时间的函数:

#include <chrono>
#include <utility>

typedef std::chrono::high_resolution_clock::time_point TimeVar;

#define duration(a) std::chrono::duration_cast<std::chrono::nanoseconds>(a).count()
#define timeNow() std::chrono::high_resolution_clock::now()

template<typename F, typename... Args>
double funcTime(F func, Args&&... args){
    TimeVar t1=timeNow();
    func(std::forward<Args>(args)...);
    return duration(timeNow()-t1);
}

示例用法:

#include <iostream>
#include <algorithm>

typedef std::string String;

//first test function doing something
int countCharInString(String s, char delim){
    int count=0;
    String::size_type pos = s.find_first_of(delim);
    while ((pos = s.find_first_of(delim, pos)) != String::npos){
        count++;pos++;
    }
    return count;
}

//second test function doing the same thing in different way
int countWithAlgorithm(String s, char delim){
    return std::count(s.begin(),s.end(),delim);
}


int main(){
    std::cout<<"norm: "<<funcTime(countCharInString,"precision=10",'=')<<"\n";
    std::cout<<"algo: "<<funcTime(countWithAlgorithm,"precision=10",'=');
    return 0;
}

输出:

norm: 15555
algo: 2976

【讨论】:

  • @RestlessC0bra :它是实现定义的,high_resolution_clock 可能是system_clock(挂钟)、steady_clock 或第三个独立时钟的别名。 See details here。 cpu时钟可以使用std::clock
  • 两个宏和一个全局 typedef - 其中没有一个是安全的单个按键 - 当然不是我称之为优雅的东西。另外传递一个函数对象并完美地分别转发参数有点矫枉过正(并且在重载函数的情况下甚至不方便),当您只需要将定时代码放入 lambda 时。但是,只要传递参数是可选的。
  • 这是违反有关宏命名的每一条准则的理由吗?你不给它们加前缀,不使用大写字母,你选择一个很常见的名字,它很可能与一些本地符号发生冲突,最重要的是:你为什么使用宏(而不是函数)?当我们这样做时:为什么你首先将持续时间返回为代表纳秒的双精度数?我们可能应该同意我们不同意。我最初的观点是:“这不是我所说的优雅代码”。
  • @MikeMB :好点子,把它作为标题肯定是个坏主意。虽然最后这只是一个示例,但如果您有复杂的需求,您必须考虑标准实践并相应地调整代码。例如,在编写代码时,当它位于我现在正在工作的 cpp 文件中时,我会很方便,但是当需要将它移到其他地方时,我会采取一切必要的步骤来使其健壮,这样我就不必再看一遍。而且我认为,每个不是完全菜鸟的程序员都会在时机成熟时广泛思考。希望,我澄清了我的观点:D。
  • @Jahid:谢谢。在这种情况下,请考虑我的 cmets 无效和无效。
【解决方案3】:

在 Scott Meyers 的书中,我找到了一个通用通用 lambda 表达式的示例,可用于测量函数执行时间。 (C++14)

auto timeFuncInvocation = 
    [](auto&& func, auto&&... params) {
        // get time before function invocation
        const auto& start = std::chrono::high_resolution_clock::now();
        // function invocation using perfect forwarding
        std::forward<decltype(func)>(func)(std::forward<decltype(params)>(params)...);
        // get time after function invocation
        const auto& stop = std::chrono::high_resolution_clock::now();
        return stop - start;
     };

问题是您只测量一次执行,因此结果可能会有很大差异。要获得可靠的结果,您应该测量大量执行。 根据 Andrei Alexandrescu 在 code::dive 2015 会议上的演讲 - 编写快速代码 I:

测量时间:tm = t + tq + tn + to

地点:

tm - 测量(观察)时间

t - 实际感兴趣的时间

tq - 量化噪声添加的时间

tn - 各种噪声源增加的时间

to - 开销时间(测量、循环、调用函数)

根据他在讲座后面所说的,你应该把这么多的执行次数作为你的结果。 我鼓励你看看他解释原因的讲座。

还有一个来自 google 的非常好的库 - https://github.com/google/benchmark。 这个库使用起来非常简单且功能强大。你可以在 youtube 上查看 Chandler Carruth 的一些讲座,他在实践中使用这个库。例如 CppCon 2017:Chandler Carruth “Going Nowhere Faster”;

示例用法:

#include <iostream>
#include <chrono>
#include <vector>
auto timeFuncInvocation = 
    [](auto&& func, auto&&... params) {
        // get time before function invocation
        const auto& start = high_resolution_clock::now();
        // function invocation using perfect forwarding
        for(auto i = 0; i < 100000/*largeNumber*/; ++i) {
            std::forward<decltype(func)>(func)(std::forward<decltype(params)>(params)...);
        }
        // get time after function invocation
        const auto& stop = high_resolution_clock::now();
        return (stop - start)/100000/*largeNumber*/;
     };

void f(std::vector<int>& vec) {
    vec.push_back(1);
}

void f2(std::vector<int>& vec) {
    vec.emplace_back(1);
}
int main()
{
    std::vector<int> vec;
    std::vector<int> vec2;
    std::cout << timeFuncInvocation(f, vec).count() << std::endl;
    std::cout << timeFuncInvocation(f2, vec2).count() << std::endl;
    std::vector<int> vec3;
    vec3.reserve(100000);
    std::vector<int> vec4;
    vec4.reserve(100000);
    std::cout << timeFuncInvocation(f, vec3).count() << std::endl;
    std::cout << timeFuncInvocation(f2, vec4).count() << std::endl;
    return 0;
}

编辑: 当然,您始终需要记住,您的编译器可以优化或不优化某些内容。在这种情况下,像 perf 这样的工具会很有用。

【讨论】:

  • 有趣——在这里使用 lambda 比函数模板有什么好处?
  • 主要区别在于它是一个可调用对象,但实际上您可以使用可变参数模板和 std::result_of_t 获得非常相似的东西。
  • @KrzysztofSommerfeld 如何为函数方法执行此操作,当我传递计时(Object.Method1)时,它返回错误“非标准语法;使用'&'创建指向成员的指针”
  • timeFuncInvocation([&objectName](auto&&... args){ objectName.methodName(std::forward(args)...); }, arg1, arg2,. ..);或在 objectName 之前省略并签名(然后您将拥有该对象的副本)
【解决方案4】:

简单的程序来查找一个函数执行所花费的时间。

#include <iostream>
#include <ctime> // time_t
#include <cstdio>

void function()
{
     for(long int i=0;i<1000000000;i++)
     {
        // do nothing
     }
}

int main()
{

time_t begin,end; // time_t is a datatype to store time values.

time (&begin); // note time before execution
function();
time (&end); // note time after execution

double difference = difftime (end,begin);
printf ("time taken for function() %.2lf seconds.\n", difference );

return 0;
}

【讨论】:

  • 非常不准确,只显示秒,但没有毫秒
  • 您应该使用类似clock_gettime 的东西并在struct timespec 结果中处理结果。但这是一种 C 解决方案,而不是 C++ 解决方案。
【解决方案5】:

旧 C++ 或 C 的简单方法:

#include <time.h> // includes clock_t and CLOCKS_PER_SEC

int main() {

    clock_t start, end;

    start = clock();
    // ...code to measure...
    end = clock();

    double duration_sec = double(end-start)/CLOCKS_PER_SEC;
    return 0;
}

以秒为单位的计时精度为1.0/CLOCKS_PER_SEC

【讨论】:

  • 这是不可移植的。它测量 Linux 上的处理器时间和 Windows 上的时钟时间。
  • 开始和结束时间始终相同,尽管我在 Win64/Visual Studio 17 下添加了 512 个元素的数组.....
  • 我不确定是什么原因造成的,但如果您使用的是 C++,那么最好切换到标准的 &lt;chrono&gt; 方法。
【解决方案6】:

这是一个出色的仅标头类模板,用于测量函数或任何代码块的运行时间:

#ifndef EXECUTION_TIMER_H
#define EXECUTION_TIMER_H

template<class Resolution = std::chrono::milliseconds>
class ExecutionTimer {
public:
    using Clock = std::conditional_t<std::chrono::high_resolution_clock::is_steady,
                                     std::chrono::high_resolution_clock,
                                     std::chrono::steady_clock>;
private:
    const Clock::time_point mStart = Clock::now();

public:
    ExecutionTimer() = default;
    ~ExecutionTimer() {
        const auto end = Clock::now();
        std::ostringstream strStream;
        strStream << "Destructor Elapsed: "
                  << std::chrono::duration_cast<Resolution>( end - mStart ).count()
                  << std::endl;
        std::cout << strStream.str() << std::endl;
    }    

    inline void stop() {
        const auto end = Clock::now();
        std::ostringstream strStream;
        strStream << "Stop Elapsed: "
                  << std::chrono::duration_cast<Resolution>(end - mStart).count()
                  << std::endl;
        std::cout << strStream.str() << std::endl;
    }

}; // ExecutionTimer

#endif // EXECUTION_TIMER_H

以下是它的一些用途:

int main() {
    { // empty scope to display ExecutionTimer's destructor's message
         // displayed in milliseconds
         ExecutionTimer<std::chrono::milliseconds> timer;

         // function or code block here

         timer.stop();

    } 

    { // same as above
        ExecutionTimer<std::chrono::microseconds> timer;

        // code block here...

        timer.stop();
    }

    {  // same as above
       ExecutionTimer<std::chrono::nanoseconds> timer;

       // code block here...

       timer.stop();

    }

    {  // same as above
       ExecutionTimer<std::chrono::seconds> timer;

       // code block here...

       timer.stop();

    }              

    return 0;
}

由于类是一个模板,我们可以很容易地指定我们希望如何测量和显示我们的时间。这是一个非常方便的工具类模板,用于进行基准标记,并且非常易于使用。

【讨论】:

  • 就个人而言,不需要stop() 成员函数,因为析构函数会为您停止计时器。
  • @Casey 类的设计不一定需要停止功能,但它是有特定原因的。在test code 启动计时器之前创建对象时的默认构造。然后在您的test code 之后,您明确地使用计时器对象并调用它的停止方法。当您想stop 计时器时,您必须手动调用它。该类不带任何参数。此外,如果您按照我所展示的那样使用此类,您会发现调用 obj.stop 和它的 destructor 之间的时间间隔很短。
  • @Casey ...这也允许在同一个范围内拥有多个计时器对象,并不是真的需要它,而是另一种可行的选择。
  • 这个例子不能以呈现的形式编译。该错误与“运算符
  • @Celdor 你需要适当的包含吗?比如&lt;chrono&gt;?
【解决方案7】:
#include <iostream>
#include <chrono>

void function()
{
    // code here;
}

int main()
{
    auto t1 = std::chrono::high_resolution_clock::now();
    function();
    auto t2 = std::chrono::high_resolution_clock::now();

    auto duration = std::chrono::duration_cast<std::chrono::microseconds>( t2 - t1 ).count();

    std::cout << duration<<"/n";
    return 0;
}

这对我有用。


注意:

high_resolution_clock 在不同的标准库实现中的实现不一致,应避免使用它。它通常只是std::chrono::steady_clockstd::chrono::system_clock 的别名,但具体取决于库或配置。当它是system_clock 时,它不是单调的(例如,时间可以倒退)。

例如,gcc 的libstdc++system_clock,MSVC 为steady_clock,clang 的libc++ 取决于配置。

通常应该直接使用std::chrono::steady_clockstd::chrono::system_clock 而不是std::chrono::high_resolution_clock:使用steady_clock 测量持续时间,使用system_clock 测量挂钟时间。

【讨论】:

    【解决方案8】:
    • 在 C++11 中是一个非常容易使用的方法。
    • 我们可以使用标头中的 std::chrono::high_resolution_clock
    • 我们可以编写一个方法,以可读性强的形式打印方法执行时间。

    例如,要找到 1 到 1 亿之间的所有质数,大约需要 1 分 40 秒。 所以执行时间打印为:

    Execution Time: 1 Minutes, 40 Seconds, 715 MicroSeconds, 715000 NanoSeconds
    

    代码在这里:

    #include <iostream>
    #include <chrono>
    
    using namespace std;
    using namespace std::chrono;
    
    typedef high_resolution_clock Clock;
    typedef Clock::time_point ClockTime;
    
    void findPrime(long n, string file);
    void printExecutionTime(ClockTime start_time, ClockTime end_time);
    
    int main()
    {
        long n = long(1E+8);  // N = 100 million
    
        ClockTime start_time = Clock::now();
    
        // Write all the prime numbers from 1 to N to the file "prime.txt"
        findPrime(n, "C:\\prime.txt"); 
    
        ClockTime end_time = Clock::now();
    
        printExecutionTime(start_time, end_time);
    }
    
    void printExecutionTime(ClockTime start_time, ClockTime end_time)
    {
        auto execution_time_ns = duration_cast<nanoseconds>(end_time - start_time).count();
        auto execution_time_ms = duration_cast<microseconds>(end_time - start_time).count();
        auto execution_time_sec = duration_cast<seconds>(end_time - start_time).count();
        auto execution_time_min = duration_cast<minutes>(end_time - start_time).count();
        auto execution_time_hour = duration_cast<hours>(end_time - start_time).count();
    
        cout << "\nExecution Time: ";
        if(execution_time_hour > 0)
        cout << "" << execution_time_hour << " Hours, ";
        if(execution_time_min > 0)
        cout << "" << execution_time_min % 60 << " Minutes, ";
        if(execution_time_sec > 0)
        cout << "" << execution_time_sec % 60 << " Seconds, ";
        if(execution_time_ms > 0)
        cout << "" << execution_time_ms % long(1E+3) << " MicroSeconds, ";
        if(execution_time_ns > 0)
        cout << "" << execution_time_ns % long(1E+6) << " NanoSeconds, ";
    }
    

    【讨论】:

      【解决方案9】:

      我建议使用steady_clock,它保证是单调的,不像high_resolution_clock

      #include <iostream>
      #include <chrono>
      
      using namespace std;
      
      unsigned int stopwatch()
      {
          static auto start_time = chrono::steady_clock::now();
      
          auto end_time = chrono::steady_clock::now();
          auto delta    = chrono::duration_cast<chrono::microseconds>(end_time - start_time);
      
          start_time = end_time;
      
          return delta.count();
      }
      
      int main() {
        stopwatch(); //Start stopwatch
        std::cout << "Hello World!\n";
        cout << stopwatch() << endl; //Time to execute last line
        for (int i=0; i<1000000; i++)
            string s = "ASDFAD";
        cout << stopwatch() << endl; //Time to execute for loop
      }
      

      输出:

      Hello World!
      62
      163514
      

      【讨论】:

        【解决方案10】:

        如果您想保证时间和代码行数,您可以将函数执行时间测量为一行宏:

        a) 实现上面已经建议的时间测量类(这是我的 android 实现):

        class MeasureExecutionTime{
        private:
            const std::chrono::steady_clock::time_point begin;
            const std::string caller;
        public:
            MeasureExecutionTime(const std::string& caller):caller(caller),begin(std::chrono::steady_clock::now()){}
            ~MeasureExecutionTime(){
                const auto duration=std::chrono::steady_clock::now()-begin;
                LOGD("ExecutionTime")<<"For "<<caller<<" is "<<std::chrono::duration_cast<std::chrono::milliseconds>(duration).count()<<"ms";
            }
        };
        

        b) 添加一个方便的宏,使用当前函数名作为 TAG(此处使用宏很重要,否则 __FUNCTION__ 将评估为 MeasureExecutionTime 而不是您要测量的函数

        #ifndef MEASURE_FUNCTION_EXECUTION_TIME
        #define MEASURE_FUNCTION_EXECUTION_TIME const MeasureExecutionTime measureExecutionTime(__FUNCTION__);
        #endif
        

        c) 在要测量的函数的开头编写宏。示例:

         void DecodeMJPEGtoANativeWindowBuffer(uvc_frame_t* frame_mjpeg,const ANativeWindow_Buffer& nativeWindowBuffer){
                MEASURE_FUNCTION_EXECUTION_TIME
                // Do some time-critical stuff 
        }
        

        这将导致以下输出:

        ExecutionTime: For DecodeMJPEGtoANativeWindowBuffer is 54ms
        

        请注意,这(与所有其他建议的解决方案一样)将测量函数被调用和返回之间的时间,而不是 CPU 执行函数的时间。但是,如果您不通过调用 sleep() 或类似方法对调度程序进行任何更改以暂停正在运行的代码,则两者之间没有区别。

        【讨论】:

          【解决方案11】:

          你可以有一个简单的类来进行这种测量。

          class duration_printer {
          public:
              duration_printer() : __start(std::chrono::high_resolution_clock::now()) {}
              ~duration_printer() {
                  using namespace std::chrono;
                  high_resolution_clock::time_point end = high_resolution_clock::now();
                  duration<double> dur = duration_cast<duration<double>>(end - __start);
                  std::cout << dur.count() << " seconds" << std::endl;
              }
          private:
              std::chrono::high_resolution_clock::time_point __start;
          };
          

          唯一需要做的就是在函数的开头创建一个对象

          void veryLongExecutingFunction() {
              duration_calculator dc;
              for(int i = 0; i < 100000; ++i) std::cout << "Hello world" << std::endl;
          }
          
          int main() {
              veryLongExecutingFunction();
              return 0;
          }
          

          就是这样。可以修改该类以满足您的要求。

          【讨论】:

            【解决方案12】:

            由于所提供的答案都不是非常准确或给出可重现的结果,我决定在我的代码中添加一个链接,该链接具有亚纳秒级精度和科学统计数据。

            请注意,这只适用于测量需要(非常)短时间运行(也就是几个时钟周期到几千个)的代码:如果它们运行时间过长以至于它们可能会被一些 - heh-中断,那么显然不可能给出可重复且准确的结果;其结果是测量永远不会完成:也就是说,它会继续测量,直到在统计上 99.9% 确定它有正确的答案,而当代码花费太长时间时,在运行其他进程的机器上永远不会发生这种情况。

            https://github.com/CarloWood/cwds/blob/master/benchmark.h#L40

            【讨论】:

              【解决方案13】:

              C++11 清理版 Jahid 的回应:

              #include <chrono>
              #include <thread>
              
              void long_operation(int ms)
              {
                  /* Simulating a long, heavy operation. */
                  std::this_thread::sleep_for(std::chrono::milliseconds(ms));
              }
              
              template<typename F, typename... Args>
              double funcTime(F func, Args&&... args){
                  std::chrono::high_resolution_clock::time_point t1 = 
                      std::chrono::high_resolution_clock::now();
                  func(std::forward<Args>(args)...);
                  return std::chrono::duration_cast<std::chrono::milliseconds>(
                      std::chrono::high_resolution_clock::now()-t1).count();
              }
              
              int main()
              {
                  std::cout<<"expect 150: "<<funcTime(long_operation,150)<<"\n";
              
                  return 0;
              }
              

              【讨论】:

                【解决方案14】:

                这是一个非常基本的计时器类,您可以根据需要对其进行扩展。我想要一些可以在代码中干净利落地使用的简单的东西。你可以通过这个链接在编码场搞砸它:http://tpcg.io/nd47hFqr

                class local_timer {
                    private:
                        std::chrono::_V2::system_clock::time_point start_time;
                        std::chrono::_V2::system_clock::time_point stop_time;
                        std::chrono::_V2::system_clock::time_point stop_time_temp;
                        std::chrono::microseconds most_recent_duration_usec_chrono;
                        double most_recent_duration_sec;
                    public:
                
                        local_timer() {
                
                        };
                
                        ~local_timer() {
                
                        };
                
                        void start() {
                            this->start_time = std::chrono::high_resolution_clock::now();
                        };
                
                        void stop() {
                            this->stop_time = std::chrono::high_resolution_clock::now();
                        };
                
                        double get_time_now() {
                            this->stop_time_temp = std::chrono::high_resolution_clock::now();
                            this->most_recent_duration_usec_chrono = std::chrono::duration_cast<std::chrono::microseconds>(stop_time_temp-start_time);
                            this->most_recent_duration_sec = (long double)most_recent_duration_usec_chrono.count()/1000000;
                            return this->most_recent_duration_sec;
                        };
                
                        double get_duration() {
                            this->most_recent_duration_usec_chrono = std::chrono::duration_cast<std::chrono::microseconds>(stop_time-start_time);
                            this->most_recent_duration_sec = (long double)most_recent_duration_usec_chrono.count()/1000000;
                            return this->most_recent_duration_sec;
                        };
                
                
                };
                

                这个存在的用途

                #include <iostream>
                #include "timer.hpp" //if kept in an hpp file in the same folder, can also before your main function
                
                int main() {
                    //create two timers
                    local_timer timer1 = local_timer();
                    local_timer timer2 = local_timer();
                    
                    //set start time for timer1
                    timer1.start();
                    //wait 1 second
                    while(timer1.get_time_now() < 1.0) {
                    }
                    //save time
                    timer1.stop();
                    //print time
                    std::cout << timer1.get_duration() << " seconds, timer 1\n" << std::endl;
                
                    timer2.start();
                    for(long int i = 0; i < 100000000; i++) {
                        //do something
                        if(i%1000000 == 0) { 
                            //return time since loop started
                            std::cout << timer2.get_time_now() << " seconds, timer 2\n"<< std::endl;
                        }
                        
                    }
                    return 0;
                }
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2011-09-09
                  • 2011-04-23
                  相关资源
                  最近更新 更多