HOWTO · C
使用 C 语言中的 gettimeofday 函数
本文演示了如何在 C 语言中计算代码块中的耗时。
本页内容
测量一段 C 代码的经过时间时,优先使用搭配 CLOCK_MONOTONIC 的 clock_gettime:它测量时间间隔,不受墙上时钟调整影响。gettimeofday 是一种广泛可用的旧式选项,用来读取墙上时钟;clock 则测量进程 CPU 时间。下面的示例展示了如何计算时间差并检查调用结果;测得的时间取决于机器和构建方式。
使用 gettimeofday 测量经过时间
gettimeofday 在 <sys/time.h> 中声明,并会填充 struct timeval。它的 tv_sec 字段表示自 Epoch 起经过的秒数,tv_usec 是以微秒表示的小数部分。时区参数已过时,应传入 NULL。许多类 Unix 系统仍提供此 API,但由于墙上时钟可能被调整,它已是测量时间间隔的旧式选择。
示例在循环前后各记录一次读数,并将秒数差和微秒数差合并到 elapsed 中。volatile 累加器让循环结果可被观察到,因此优化编译器不会丢弃这部分工作。循环从 0 执行到 9,999,因此总和为 49,995,000,即使按 unsigned long 的最低 32 位范围也能容纳。你可以将循环替换为要测量的操作。输出包含工作计数和计算出的经过时间。下方的计时值仅供说明;它会因系统和构建方式而异。示例中未更改的输出标签仍为英文。
运行此示例:
#include <stdio.h>
#include <sys/time.h>
int main(void) {
struct timeval start, end;
volatile unsigned long total = 0;
if (gettimeofday(&start, NULL) != 0) {
return 1;
}
for (unsigned long i = 0; i < 10000UL; ++i) {
total += i;
}
if (gettimeofday(&end, NULL) != 0) {
return 1;
}
double elapsed = (double)(end.tv_sec - start.tv_sec)
+ (double)(end.tv_usec - start.tv_usec) / 1000000.0;
printf("gettimeofday elapsed: %.6f seconds (work=%lu)\n", elapsed, total);
return 0;
}
gettimeofday 成功时返回零,失败时返回非零值。在使用这两个结构体之前,应检查两次读取的结果。由于这是墙上时钟测量,如果测量期间系统时钟发生调整,计算出的差值可能受到影响。显示的小数位数只是输出格式,并不代表测量分辨率有保证。
使用 clock_gettime 测量经过时间
测量时间间隔时,使用 clock_gettime(CLOCK_MONOTONIC, ...)。CLOCK_MONOTONIC 单调递增,并不是日历时钟。CLOCK_REALTIME 表示墙上时间,可能会随系统时钟调整而变化,因此通常不适合测量时间间隔。该函数和 struct timespec 在 <time.h> 中声明;tv_sec 保存秒数,tv_nsec 保存纳秒数。纳秒单位并不保证纳秒级精度,也不保证实际时钟分辨率达到纳秒级。
这个完整示例会检查两次调用,并以秒为单位计算时间间隔。循环总和 49,995,000 可容纳在 unsigned long 的最低 32 位范围内;输出显示工作计数和计算出的经过时间。下方的计时值仅供说明,因为它会因系统和构建方式而异。对于需要 POSIX 声明 clock_gettime 的系统,特性测试宏会请求该声明。示例中未更改的输出标签仍为英文。
#define _POSIX_C_SOURCE 199309L
#include <stdio.h>
#include <time.h>
int main(void) {
struct timespec start, end;
volatile unsigned long total = 0;
if (clock_gettime(CLOCK_MONOTONIC, &start) != 0) {
return 1;
}
for (unsigned long i = 0; i < 10000UL; ++i) {
total += i;
}
if (clock_gettime(CLOCK_MONOTONIC, &end) != 0) {
return 1;
}
double elapsed = (double)(end.tv_sec - start.tv_sec)
+ (double)(end.tv_nsec - start.tv_nsec) / 1000000000.0;
printf("clock_gettime elapsed: %.6f seconds (work=%lu)\n", elapsed, total);
return 0;
}
即使结束读数的小数部分字段小于开始读数,这种字段相减方式仍然有效,因为秒数差会补上相应的整秒。若应用需要得到归一化的 timespec 结果,当小数部分差值为负时,应借一秒并加上一十亿纳秒。
使用 clock 测量 CPU 时间
<time.h> 中的 clock 报告当前进程使用的处理器时间,而不是墙上时钟经过的时间。它的结果是以时钟滴答为单位的 clock_t 计数;将差值除以 CLOCKS_PER_SEC,即可换算成秒。滴答的刻度和有效分辨率取决于具体实现,极短的操作可能测不出有意义的变化。返回值 (clock_t)-1 表示无法获取处理器时间。
示例会检查两次读数,并让循环结果可被观察到。循环总和 49,995,000 可容纳在 unsigned long 的最低 32 位范围内;输出包含工作计数和计算出的 CPU 时间。下方的计时值仅供说明,因为它会因系统和构建方式而异。示例中未更改的输出标签仍为英文。
#include <stdio.h>
#include <time.h>
int main(void) {
clock_t start = clock();
if (start == (clock_t)-1) {
return 1;
}
volatile unsigned long total = 0;
for (unsigned long i = 0; i < 10000UL; ++i) {
total += i;
}
clock_t end = clock();
if (end == (clock_t)-1) {
return 1;
}
double cpu_seconds = (double)(end - start) / CLOCKS_PER_SEC;
printf("clock CPU duration: %.6f seconds (work=%lu)\n", cpu_seconds, total);
return 0;
}
如果你想估算某项计算消耗了多少处理器时间,CPU 时间会很有用。它不包含休眠、网络、磁盘或其他阻塞操作所花费的全部等待时间。在多线程进程中,报告的进程 CPU 时间可能累计多个线程的用时,因此可能超过测量区间的墙上时间。
根据测量目标选择合适的时钟
根据你需要测量的量选择时钟:
- 对于包含等待和调度时间的持续时长,在可用时使用搭配
CLOCK_MONOTONIC的clock_gettime。 - 对于需要简单墙上时钟读数的旧式类 Unix 代码,可以使用
gettimeofday,但要注意墙上时间可能被调整。 - 对于进程消耗的 CPU 时间,使用
clock,并用CLOCKS_PER_SEC将滴答数换算为秒。
让开始和结束的调用尽量靠近要测量的工作;比较不同方案时,应多次进行测量。系统负载、编译器优化、硬件和被测操作都会影响计时结果。对于极短的工作,可以测量一批操作,或重复操作后除以重复次数;不要把打印的小数位数或纳秒字段当作精度保证。使用结果前检查每次计时调用,并确保被测工作会产生可观察的效果。这些方法测量的对象不同,只有时钟符合测量问题时,结果才适合比较。