【问题标题】:How to measure the execution time of C math.h library functions?如何测量 C math.h 库函数的执行时间?
【发布时间】:2020-07-08 21:59:35
【问题描述】:

通过使用time.h 标头,我得到sqrt() 的执行时间为2 纳秒(在Linux 终端中使用gcc 命令)和44 纳秒(在Ubuntu 终端中使用g++ 命令)。谁能告诉我任何其他方法来测量math.h 库函数的执行时间?

下面是代码:

#include <time.h>
#include <stdio.h>
#include<math.h>

int main()
{

    time_t begin,end; // time_t is a datatype to store time values.
    time (&begin); // note time before execution
    for(int i=0;i<1000000000;i++)  //using for loop till 10^9 times to make the execution time in nanoseconds
    {
        cbrt(9999999);  // calling the cube root function from math library
    }
    time (&end); // note time after execution
    double difference = difftime (end,begin);
    printf ("time taken for function() %.2lf in Nanoseconds.\n", difference );
    printf(" cube root is :%f \t",cbrt(9999999));

    return 0;
}

输出:

by using **gcc**: time taken for function() 2.00 seconds.
                  cube root is :215.443462
by using **g++**: time taken for function() 44.00 in Nanoseconds.
                  cube root is:215.443462

Linux terminal result

提示符的长度:

$ g++ t1.c
$ ./a.out
time taken for function() 44.00 in Nanoseconds.
 cube root is :215.443462
$ gcc t1.c
$ ./a.out
time taken for function() 2.00 in Nanoseconds.
 cube root is :215.443462
$

【问题讨论】:

  • 编译器可以优化整个循环。您使用的是cbrt(),而不是sqrt(),因为您的spiel 声称。您每次都使用相同的值调用它——编译器可以在编译时对其进行评估。而且您对结果什么也不做,因此编译器可以丢弃该常量值。因此,您正在测量两次调用time() 所需的时间。而time() 通常(在 Linux 上,总是)有 1 秒的粒度。任何差异都以整秒为单位。您是否在两者上都使用相同的优化进行编译? gccg++ 的基本版本相同吗?衡量绩效很难!
  • 因此,您可能需要对从 1 到 10 亿的数字的立方根求和,然后在最后打印该总和。这可以防止编译器预先计算结果。请记住在编译器选项中包含-O3 或附近(至少-O)。我想既然你正在做十亿次循环,你可以声称以秒为单位的时间代表了一次迭代的纳秒数——我不同意这一点。但重要的是要确保编译器按照您的预期执行 - 调用函数十亿次。
  • 我注意到,当我使用我的默认选项编译您的代码(保存在 tm71.c - 并添加一些内容)时,GCC 9.3.0(可能还有更早的版本)会抱怨(给出一个错误,因为我告诉它):gcc -O3 -g -I../inc -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes tm71.c -o tm71 -L../lib -lsoq——tm71.c: In function ‘main’:——tm71.c:22:9: error: statement with no effect [-Werror=unused-value]——22 | cbrt(9999999);
  • 嘿乔纳森,我对 gcc 和 g++ 使用相同的版本 (7.5.0),但我得到不同的结果。您能否建议我一些查找数学库函数执行时间的方法?实际上,我自己做了一些数学库函数,我只是想将我的数学函数的执行时间与 math.h 库函数进行比较。

标签: c++ c time


【解决方案1】:

正如@Jonathan Leffler 所说,编译器可以优化您的 C/c++ 代码。如果 C 代码只是从 0 循环到 1000 而没有对计数器 i 做任何事情(我的意思是,没有打印它或在任何其他操作、索引等中使用中间值),编译器可能 甚至不创建对应于该循环的汇编代码。可能的算术运算甚至会被预先计算。对于下面的代码;

int foo(int x) {
    return x * 5;
}

int main() {
    int x = 3;
    int y = foo(x);
    ...
    ...
}

编译器只为函数foo生成两行汇编代码(编译器甚至可以绕过调用函数foo并生成内联指令)并不奇怪:

mov $15, %eax
; compiler will not bother multiplying 5 by 3
; but just move the pre-computed '15' to register
ret
; and then return

【讨论】:

    【解决方案2】:

    如何测量c math.h库函数的执行时间?

    C 编译器通常被允许分析众所周知的标准库函数,并用215.443462... 替换像cbrt(9999999); 这样的修复代码。此外,由于在循环中删除函数不会影响代码的函数,因此可以优化该循环。

    使用volatile 可以防止大部分情况发生,因为编译器不能假设在替换、删除函数时没有影响。

    for(int i=0;i<1000000000;i++) {
        // cbrt(9999999);
        volatile double x = 9999999.0;
        volatile double y = cbrt(x);
    }
    

    time() 的粒度通常只有 1 秒,如果十亿次循环仅导致几秒钟,请考虑更多循环。


    下面的代码可以用来计算循环开销。

    time_t begin,middle,end;
    time (&begin);
    for(int i=0;i<1000000000;i++) {
        volatile double x = 9999999.0;
        volatile double y = x;
    }
    time (&middle);
    for(int i=0;i<1000000000;i++) {
        volatile double x = 9999999.0;
        volatile double y = cbrt(x);
    }
    time (&end);
    double difference = difftime(end,middle) - difftime(middle,begin);
    

    【讨论】:

      【解决方案3】:

      计时代码是一门艺术,其中一个艺术是确保编译器不会优化您的代码。对于标准库函数,编译器可能很清楚它是什么/做什么,并且能够在编译时评估一个常量。在您的示例中,调用 cbrt(9999999); 提供了两个优化机会。 cbrt() 的值可以在编译时计算,因为参数是一个常量。其次,没有使用返回值,标准函数没有副作用,编译器可以干脆放弃。您可以通过捕获结果来避免这些问题(例如,通过计算从 0 到 10 亿(减一)的立方根的总和并在计时码之后打印该值。

      tm97.c

      当我编译你的代码时,去掉了 cmets,我得到了:

      $ cat tm97.c
      #include <time.h>
      #include <stdio.h>
      #include <math.h>
      
      int main(void)
      {
          time_t begin, end;
          time(&begin);
          for (int i = 0; i < 1000000000; i++)
          {
              cbrt(9999999);
          }
          time(&end);
          double difference = difftime(end, begin);
          printf("time taken for function() %.2lf in Nanoseconds.\n", difference );
          printf(" cube root is :%f \t", cbrt(9999999));
      
          return 0;
      }
      $ make tm97
      gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes         tm97.c -o tm97 -L../lib -lsoq 
      tm97.c: In function ‘main’:
      tm97.c:11:9: error: statement with no effect [-Werror=unused-value]
         11 |         cbrt(9999999);
            |         ^~~~
      cc1: all warnings being treated as errors
      rmk: error code 1
      $
      

      我在运行 macOS Mojave 10.14.6 和 XCode 11.3.1 (11C504) 和 GCC 9.3.0 的 2017 MacBook Pro 上使用 GCC 9.3.0 — XCode 11.4 需要 Catalina 10.15.2,但工作尚未完成围绕组织对此的支持,然而。有趣的是,当g++ 编译相同的代码时,它编译时没有警告(错误):

      $ ln -s tm97.c tm89.cpp
      make tm89 SXXFLAGS=-std=c++17 CXX=g++
      g++ -O3 -g  -I../inc -std=c++17 -Wall -Wextra -Werror -L../lib tm89.cpp -lsoq -o tm89
      $
      

      我经常使用我在 GitHub 上的 SOQ(堆栈溢出问题)存储库中提供的一些计时代码,作为 src/libsoq 子目录中的文件 timer.ctimer.h。该代码仅在我的库中编译为 C 代码,因此我创建了一个简单的包装头 timer2.h,以便下面的程序可以使用 #include "timer2.h",并且它可以在 C 和 C++ 编译中正常工作:

      #ifndef TIMER2_H_INCLUDED
      #define TIMER2_H_INCLUDED
      
      #ifdef __cplusplus
      extern "C" {
      #endif
      #include "timer.h"
      #ifdef __cplusplus
      }
      #endif
      
      #endif /* TIMER2_H_INCLUDED */
      

      tm29.cpptm31.c

      此代码使用sqrt() 函数进行测试。它累加平方根的总和。它使用来自timer.h/timer.c 的时序代码围绕您的时序代码——键入Clock 和函数clk_init()clk_start()clk_stop()clk_elapsed_us() 来评估从时钟已启动和上次停止。

      源代码可以由 C 编译器或 C++ 编译器编译。

      #include <time.h>
      #include <stdio.h>
      #include <math.h>
      #include "timer2.h"
      
      int main(void)
      {
          time_t begin, end;
          double sum = 0.0;
          int i;
          Clock clk;
          clk_init(&clk);
          clk_start(&clk);
          time(&begin);
          for (i = 0; i < 1000000000; i++)
          {
              sum += sqrt(i);
          }
          time(&end);
          clk_stop(&clk);
          double difference = difftime(end, begin);
          char buffer[32];
          printf("Time taken for sqrt() is %.2lf nanoseconds (%s ns).\n",
                 difference, clk_elapsed_us(&clk, buffer, sizeof(buffer)));
          printf("Sum of square roots from 0 to %d is: %f\n", i, sum);
      
          return 0;
      }
      

      tm41.ctm43.cpp

      这段代码与前面的代码几乎相同,但测试的函数是cbrt()(立方根)函数。

      #include <time.h>
      #include <stdio.h>
      #include <math.h>
      #include "timer2.h"
      
      int main(void)
      {
          time_t begin, end;
          double sum = 0.0;
          int i;
          Clock clk;
          clk_init(&clk);
          clk_start(&clk);
          time(&begin);
          for (i = 0; i < 1000000000; i++)
          {
              sum += cbrt(i);
          }
          time(&end);
          clk_stop(&clk);
          double difference = difftime(end, begin);
          char buffer[32];
          printf("Time taken for cbrt() is %.2lf nanoseconds (%s ns).\n",
                 difference, clk_elapsed_us(&clk, buffer, sizeof(buffer)));
          printf("Sum of cube roots from 0 to %d is: %f\n", i, sum);
      
          return 0;
      }
      

      tm59.ctm61.c

      此代码使用fabs() 而不是sqrt()cbrt()。它仍然是一个函数调用,但它可能是内联的。它显式调用从intdouble 的转换;如果没有这种转换,GCC 会抱怨它应该使用整数 abs() 函数。

      #include <time.h>
      #include <stdio.h>
      #include <math.h>
      #include "timer2.h"
      
      int main(void)
      {
          time_t begin, end;
          double sum = 0.0;
          int i;
          Clock clk;
          clk_init(&clk);
          clk_start(&clk);
          time(&begin);
          for (i = 0; i < 1000000000; i++)
          {
              sum += fabs((double)i);
          }
          time(&end);
          clk_stop(&clk);
          double difference = difftime(end, begin);
          char buffer[32];
          printf("Time taken for fabs() is %.2lf nanoseconds (%s ns).\n",
                 difference, clk_elapsed_us(&clk, buffer, sizeof(buffer)));
          printf("Sum of absolute values from 0 to %d is: %f\n", i, sum);
      
          return 0;
      }
      

      tm73.cpp

      此文件也将原始代码与我的时序包装器代码一起使用。 C 版本不编译 - C++ 版本可以:

      #include <time.h>
      #include <stdio.h>
      #include <math.h>
      #include "timer2.h"
      
      int main(void)
      {
          time_t begin, end;
          Clock clk;
          clk_init(&clk);
          clk_start(&clk);
          time(&begin);
          for (int i = 0; i < 1000000000; i++)
          {
              cbrt(9999999);
          }
          time(&end);
          clk_stop(&clk);
          double difference = difftime(end, begin);
          char buffer[32];
          printf("Time taken for cbrt() is %.2lf nanoseconds (%s ns).\n",
                 difference, clk_elapsed_us(&clk, buffer, sizeof(buffer)));
          printf("Cube root is: %f\n", cbrt(9999999));
      
          return 0;
      }
      

      时间

      使用命令timecmd 报告程序的开始和停止时间、PID,以及内置在各种命令中的时序代码(它是time 命令主题的变体),我得到了以下结果。 (rmk 只是make 的替代实现。)

      $ for prog in tm29 tm31 tm41 tm43 tm59 tm61 tm73
      > do rmk $prog && timecmd -ur -- $prog
      > done
      g++ -O3 -g -I../inc -std=c++11 -Wall -Wextra -Werror tm29.cpp -o tm29 -L../lib -lsoq 
      2020-03-28 08:47:50.040227 [PID 19076] tm29
      Time taken for sqrt() is 1.00 nanoseconds (1.700296 ns).
      Sum of square roots from 0 to 1000000000 is: 21081851051977.781250
      2020-03-28 08:47:51.747494 [PID 19076; status 0x0000]  -  1.707267s  -  tm29
      gcc -O3 -g -I../inc -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes tm31.c -o tm31 -L../lib -lsoq 
      2020-03-28 08:47:52.056021 [PID 19088] tm31
      Time taken for sqrt() is 1.00 nanoseconds (1.679867 ns).
      Sum of square roots from 0 to 1000000000 is: 21081851051977.781250
      2020-03-28 08:47:53.742383 [PID 19088; status 0x0000]  -  1.686362s  -  tm31
      gcc -O3 -g -I../inc -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes tm41.c -o tm41 -L../lib -lsoq 
      2020-03-28 08:47:53.908285 [PID 19099] tm41
      Time taken for cbrt() is 7.00 nanoseconds (6.697999 ns).
      Sum of cube roots from 0 to 1000000000 is: 749999999499.628418
      2020-03-28 08:48:00.613357 [PID 19099; status 0x0000]  -  6.705072s  -  tm41
      g++ -O3 -g -I../inc  -std=c++11 -Wall -Wextra -Werror tm43.cpp -o tm43 -L../lib -lsoq 
      2020-03-28 08:48:00.817975 [PID 19110] tm43
      Time taken for cbrt() is 7.00 nanoseconds (6.614539 ns).
      Sum of cube roots from 0 to 1000000000 is: 749999999499.628418
      2020-03-28 08:48:07.438298 [PID 19110; status 0x0000]  -  6.620323s  -  tm43
      gcc -O3 -g -I../inc -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes tm59.c -o tm59 -L../lib -lsoq 
      2020-03-28 08:48:07.598344 [PID 19121] tm59
      Time taken for fabs() is 1.00 nanoseconds (1.114822 ns).
      Sum of absolute values from 0 to 1000000000 is: 499999999067108992.000000
      2020-03-28 08:48:08.718672 [PID 19121; status 0x0000]  -  1.120328s  -  tm59
      g++ -O3 -g  -I../inc -std=c++11 -Wall -Wextra -Werror tm61.cpp -o tm61 -L../lib -lsoq 
      2020-03-28 08:48:08.918745 [PID 19132] tm61
      Time taken for fabs() is 2.00 nanoseconds (1.117780 ns).
      Sum of absolute values from 0 to 1000000000 is: 499999999067108992.000000
      2020-03-28 08:48:10.042134 [PID 19132; status 0x0000]  -  1.123389s  -  tm61
      g++ -O3 -g  -I../inc -std=c++11 -Wall -Wextra -Werror tm73.cpp -o tm73 -L../lib -lsoq 
      2020-03-28 08:48:10.236899 [PID 19143] tm73
      Time taken for cbrt() is 0.00 nanoseconds (0.000004 ns).
      Cube root is: 215.443462
      2020-03-28 08:48:10.242322 [PID 19143; status 0x0000]  -  0.005423s  -  tm73
      $
      

      我已经多次运行这些程序;上面的时间代表了我每次得到的东西。可以得出一些结论:

      • sqrt() (1.7 ns) 比 cbrt() (6.7 ns) 快。
      • fabs() (1.1 ns) 比 sqrt() (1.7 ns) 快。
      • 但是,fabs() 给出了循环开销和从intdouble 的转换所用时间的适度近似值。
      • cbrt()的结果没有被使用时,编译器会消除循环。
      • 使用 C++ 编译器编译时,问题中的代码会完全删除循环,只留下对 time() 的调用进行测量。 clk_elapsed_us() 打印的结果是执行 clk_start()clk_stop() 之间的代码所花费的时间,以微秒为单位 - 0.000004 是 4 微秒的经过时间。该值标记在ns 中,因为当循环执行十亿次时,以秒为单位的经过时间也代表一个循环的以纳秒为单位的时间——一秒中有十亿纳秒。
      • timecmd报的时间与程序报的时间一致。启动进程(fork()exec())和进程中的 I/O 开销包括在 timecmd 报告的时间中。
      • 虽然未显示,但 clangclang++(而不是 GCC 9.3.0)的时序非常相似,尽管 cbrt() 代码每次迭代需要大约 7.5 ns 而不是 6.7 ns。其他的时间差异基本上是噪音。

      数字后缀都是 2 位素数。除了将不同的程序分开之外,它们没有其他意义。

      【讨论】:

        猜你喜欢
        • 2014-10-04
        • 1970-01-01
        • 2023-03-09
        • 1970-01-01
        • 1970-01-01
        • 2014-01-23
        • 2022-07-05
        相关资源
        最近更新 更多