计时代码是一门艺术,其中一个艺术是确保编译器不会优化您的代码。对于标准库函数,编译器可能很清楚它是什么/做什么,并且能够在编译时评估一个常量。在您的示例中,调用 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.c 和 timer.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.cpp 和 tm31.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.c 和 tm43.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.c 和 tm61.c
此代码使用fabs() 而不是sqrt() 或cbrt()。它仍然是一个函数调用,但它可能是内联的。它显式调用从int 到double 的转换;如果没有这种转换,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() 给出了循环开销和从int 到double 的转换所用时间的适度近似值。
- 当
cbrt()的结果没有被使用时,编译器会消除循环。
- 使用 C++ 编译器编译时,问题中的代码会完全删除循环,只留下对
time() 的调用进行测量。 clk_elapsed_us() 打印的结果是执行 clk_start() 和 clk_stop() 之间的代码所花费的时间,以微秒为单位 - 0.000004 是 4 微秒的经过时间。该值标记在ns 中,因为当循环执行十亿次时,以秒为单位的经过时间也代表一个循环的以纳秒为单位的时间——一秒中有十亿纳秒。
-
timecmd报的时间与程序报的时间一致。启动进程(fork() 和 exec())和进程中的 I/O 开销包括在 timecmd 报告的时间中。
- 虽然未显示,但
clang 和 clang++(而不是 GCC 9.3.0)的时序非常相似,尽管 cbrt() 代码每次迭代需要大约 7.5 ns 而不是 6.7 ns。其他的时间差异基本上是噪音。
数字后缀都是 2 位素数。除了将不同的程序分开之外,它们没有其他意义。