为何调用clock_gettime时CLOCK_THREAD_CPUTIME_ID比其他时钟类型更耗时?
CLOCK_THREAD_CPUTIME_ID的clock_gettime调用比CLOCK_REALTIME慢? 当需要测量线程在CPU中的总耗时(用户态+内核态),我们会使用CLOCK_THREAD_CPUTIME_ID类型的clock_gettime调用:
struct timespec start, end; clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start); // 执行目标代码 clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end); const double elapsed = (end.tv_sec-start.tv_sec)*1e9 + (end.tv_nsec-start.tv_nsec);
为了测试该调用的最小耗时,编写了如下测试代码:
#include <iostream> #include <time.h> int main() { const int c_type[] = {CLOCK_REALTIME, CLOCK_MONOTONIC, CLOCK_THREAD_CPUTIME_ID}; struct timespec start, end; const size_t n_iter = 1024*1024; for(int i = 0; i < sizeof(c_type)/sizeof(int); ++i) { double accum = 0.0; for(int j = 0; j < n_iter; ++j) { clock_gettime(c_type[i], &start); clock_gettime(c_type[i], &end); const double elapsed = (end.tv_sec-start.tv_sec)*1e9 + (end.tv_nsec-start.tv_nsec); accum += elapsed; } std::cout << "[" << i << "] elapsed: " << accum/n_iter << std::endl; } }
在AMD Ryzen 9 9950X3D处理器、Ubuntu 24.04系统上的测试结果:
[0] elapsed: 19.8536 // CLOCK_REALTIME [1] elapsed: 19.8697 // CLOCK_MONOTONIC [2] elapsed: 88.3246 // CLOCK_THREAD_CPUTIME_ID
可见CLOCK_THREAD_CPUTIME_ID的调用耗时远高于另外两种时钟类型,原因主要有三点:
实现机制的本质差异:
CLOCK_REALTIME和CLOCK_MONOTONIC在现代Linux系统中,大多通过**vDSO(虚拟动态共享对象)**实现。vDSO允许用户态程序直接读取内核维护的时钟数据,完全不需要触发系统调用(syscall),因此调用开销极低。而CLOCK_THREAD_CPUTIME_ID需要获取当前线程专属的CPU时间统计,这部分数据由内核单独维护,必须通过系统调用陷入内核态才能读取,光是用户态/内核态切换的开销就远大于vDSO的直接读取。线程CPU时间的计算复杂度:线程的CPU时间不是简单的硬件寄存器值,内核需要为每个线程维护累加的用户态运行时间、内核态运行时间。每次调用
CLOCK_THREAD_CPUTIME_ID时,内核需要先将当前CPU的硬件计数器同步到线程的时间结构体中,再完成数据的读取和格式化返回,这个过程涉及内核内部的多步操作,比读取全局时钟要复杂得多。对rdtsc的误解:虽然rdtsc指令可以快速读取CPU周期,但线程的CPU时间不能直接用rdtsc值代替。线程可能被调度到不同CPU(不同CPU的TSC可能存在偏移),且线程休眠时不会累加CPU时间,因此内核必须维护一个线程专属的时间统计变量,而不是直接依赖rdtsc。即便某些实现会用rdtsc辅助计算,最终仍需要通过内核来确保时间的准确性,无法在用户态独立完成。
内容的提问来源于stack exchange,提问作者Emanuele

