在 Linux C 语言编程中,计时是一个常见的需求,主要用于性能分析、测试代码执行时间等,Linux 提供了多种计时方法,主要可以分为两大类:
- 高精度计时:使用
clock_gettime(),这是目前推荐的方法,精度可达纳秒级,且不受进程调度影响。 - 传统计时:使用
clock()函数,精度较低(通常为毫秒级),且受系统负载影响。
下面我将详细介绍这两种方法,并提供完整的代码示例。
高精度计时:clock_gettime()
这是现代 Linux 系统中最常用、最可靠的计时方法,它属于 POSIX 标准,可以获取高精度的时间戳。
函数原型
#include <time.h> int clock_gettime(clockid_t clk_id, struct timespec *tp);
参数说明
clk_id: 指定要获取哪种类型的时间,常用的有:CLOCK_REALTIME: 系统的实时时钟,与系统时间一致,可以被settime修改,适合测量绝对时间。CLOCK_MONOTONIC: 单调递增的时钟,不受系统时间修改的影响,非常适合用于测量一个事件的持续时间,因为它只会向前走。CLOCK_PROCESS_CPUTIME_ID: 当前进程在用户态和内核态消耗的 CPU 时间。CLOCK_THREAD_CPUTIME_ID: 当前线程在用户态和内核态消耗的 CPU 时间。
tp: 一个指向struct timespec结构体的指针,用于存储获取到的时间。
struct timespec 结构体
struct timespec {
time_t tv_sec; // 秒 (seconds)
long tv_nsec; // 纳秒 (nanoseconds)
};
计算时间差
为了计算一个代码块的执行时间,你需要分别在代码块前后调用 clock_gettime(),然后计算两个时间戳的差值。
差值计算函数:
#include <math.h>
// 计算两个 timespec 结构的差值,结果单位为秒
double diff_timespec(const struct timespec *start, const struct timespec *end) {
double diff_sec = end->tv_sec - start->tv_sec;
double diff_nsec = end->tv_nsec - start->tv_nsec;
return diff_sec + diff_nsec / 1000000000.0;
}
完整示例
这个例子演示如何使用 CLOCK_MONOTONIC 来测量一个循环的执行时间。
#include <stdio.h>
#include <time.h>
#include <math.h> // 用于 floor 函数
// 计算时间差函数
double diff_timespec(const struct timespec *start, const struct timespec *end) {
double diff_sec = end->tv_sec - start->tv_sec;
double diff_nsec = end->tv_nsec - start->tv_nsec;
return diff_sec + diff_nsec / 1000000000.0;
}
int main() {
struct timespec start, end;
double elapsed_time;
// 获取开始时间
// 使用 CLOCK_MONOTONIC 确保时间单调递增,不受系统时间影响
if (clock_gettime(CLOCK_MONOTONIC, &start) == -1) {
perror("clock_gettime");
return 1;
}
// --- 要计时的代码块 ---
printf("开始执行一个耗时任务...\n");
for (volatile int i = 0; i < 100000000; ++i) {
// 一个空循环,模拟耗时操作
// volatile 关键字防止编译器优化掉这个循环
}
printf("任务执行完毕,\n");
// --- 计时代码块结束 ---
// 获取结束时间
if (clock_gettime(CLOCK_MONOTONIC, &end) == -1) {
perror("clock_gettime");
return 1;
}
// 计算并打印耗时
elapsed_time = diff_timespec(&start, &end);
printf("使用 clock_gettime(CLOCK_MONOTONIC) 计时:\n");
printf("执行时间: %.9f 秒\n", elapsed_time);
printf("执行时间: %.3f 毫秒\n", elapsed_time * 1000);
printf("执行时间: %.0f 微秒\n", elapsed_time * 1000000);
return 0;
}
编译与运行:
gcc -o timing timing.c -lm ./timing
输出示例:
开始执行一个耗时任务...
任务执行完毕。
使用 clock_gettime(CLOCK_MONOTONIC) 计时:
执行时间: 0.045123456 秒
执行时间: 45.123 毫秒
执行时间: 45123 微秒
传统计时:clock()
clock() 是 C 标准库 <time.h> 中的函数,非常古老和普遍,但在现代高精度应用中已不推荐。
函数原型
#include <time.h> clock_t clock(void);
返回值
- 返回自程序启动以来,处理器消耗的时间,单位是“时钟滴答数”(clock ticks)。
- 要将其转换为秒,需要除以
CLOCKS_PER_SEC宏,这个宏表示每秒有多少个时钟滴答。
特点
- 精度较低:精度取决于
CLOCKS_PER_SEC的值,通常是毫秒级(1000 Hz)。 - 受系统负载影响:它测量的是 CPU 时间,而不是挂钟时间,如果你的进程因为等待 I/O 或其他原因被阻塞,
clock()返回的时间不会增加,这对于测量程序响应时间是不准确的。 - 可能溢出:
clock_t通常是long或long long类型,对于长时间运行的程序,可能会溢出。
完整示例
#include <stdio.h>
#include <time.h>
int main() {
clock_t start, end;
double cpu_time_used;
// 获取开始时间
start = clock();
// --- 要计时的代码块 ---
printf("开始执行一个耗时任务...\n");
for (volatile int i = 0; i < 100000000; ++i) {
// 空循环
}
printf("任务执行完毕,\n");
// --- 计时代码块结束 ---
// 获取结束时间
end = clock();
// 计算耗时 (end - start) 得到滴答数,再除以每秒滴答数
cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
printf("使用 clock() 计时:\n");
printf("CPU 执行时间: %f 秒\n", cpu_time_used);
return 0;
}
编译与运行:
gcc -o timing_legacy timing_legacy.c ./timing_legacy
输出示例:
开始执行一个耗时任务...
任务执行完毕。
使用 clock() 计时:
CPU 执行时间: 0.045123 秒
其他计时方法
gettimeofday()
这是一个过时的、非标准的系统调用,但在很多旧代码中仍然可见,它提供微秒级精度。
#include <sys/time.h>
struct timeval {
time_t tv_sec; // 秒
suseconds_t tv_usec; // 微秒
};
为什么不推荐?
- 不是 POSIX 标准,是 BSD 特有的。
- 精度(微秒)低于
clock_gettime()(纳秒)。 struct timeval使用suseconds_t(通常是 32 位),在约 70 分钟后会发生溢出,而struct timespec使用long(64位),几乎不会溢出。
time() 和 difftime()
这两个函数仅用于获取日历时间,精度为秒,完全不适用于性能测量。
#include <time.h> time_t time(time_t *t); double difftime(time_t time1, time_t time0);
总结与选择
| 方法 | 精度 | 推荐用途 | 优点 | 缺点 |
|---|---|---|---|---|
clock_gettime() |
纳秒 | 性能分析、测量耗时 | 高精度、标准 POSIX、单调可靠、不易溢出 | 需要链接 -lrt (在极少数旧系统上) |
clock() |
毫秒 | 快速原型、旧代码兼容 | C标准库,无需额外链接 | 精度低、测量CPU时间而非挂钟时间、可能溢出 |
gettimeofday() |
微秒 | 不推荐的新项目 | 微秒级精度 | 非标准、可能溢出、精度低于 clock_gettime |
time() |
秒 | 获取当前日期/时间 | 简单 | 精度太低,无法用于计时 |
在 Linux C 语言中进行计时,强烈推荐使用 clock_gettime(),它提供了最高的精度、最好的可靠性和最标准的接口。
- 如果你想测量一个程序或代码块的总耗时(包括阻塞时间),请使用
CLOCK_MONOTONIC。 - 如果你想测量一个进程或线程的 CPU 占用时间,请使用
CLOCK_PROCESS_CPUTIME_ID或CLOCK_THREAD_CPUTIME_ID。
