Linux 下获取纳秒级进程 CPU 时间必须用 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, ...),它精确统计所有线程的用户态与内核态实际 CPU 执行时间,而 clock() 和 std::chrono::steady_clock 返回的是墙上时间,不反映真实 CPU 占用。

直接用 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, ...),这是 Linux 上唯一能精确到纳秒、且只统计进程实际 CPU 占用时间的标准方式。Windows 需用 GetThreadTimes 手动换算,精度低、开销大,不推荐用于高频采样。
为什么 clock() 和 std::chrono::steady_clock 都不行
clock() 在多数实现中只是对 CLOCK_MONOTONIC 的粗略封装,返回的是墙上时间(wall-clock time),包含休眠、等待 I/O、被调度器抢占等所有耗时;std::chrono::steady_clock 同样是单调时钟,和 CPU 实际执行无关。性能监控要的是“这个进程到底在 CPU 上跑了多久”,不是“从开始到现在过了几秒”。
常见错误现象:
– 用 steady_clock 测一个 sleep(1) 前后差值,得到 ≈1s —— 这根本不是 CPU 时间
– 多线程下用 clock() 累加各线程结果,结果远超实际 wall time,且不可比
-
CLOCK_PROCESS_CPUTIME_ID是 per-process 的,自动累加所有线程的用户态 + 内核态 CPU 时间 - 它不随系统负载、休眠、锁竞争波动,只反映真实 CPU 指令执行时长
- 调用开销约 20–50ns(现代 x86_64),远低于
getrusage或/proc/self/stat解析
Linux 下正确获取纳秒级进程 CPU 时间
必须用 clock_gettime 并传入 CLOCK_PROCESS_CPUTIME_ID,不能用 CLOCK_THREAD_CPUTIME_ID(那是单线程的,多线程需自己聚合)。
立即学习“C++免费学习笔记(深入)”;
#include <time.h>
struct timespec ts;
if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts) == 0) {
uint64_t nanos = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
}
- 返回值必须检查:失败时
errno可能是EINVAL(内核不支持,极罕见)或EFAULT(ts指针非法) -
tv_nsec范围是 [0, 999999999],相减时注意借位,建议统一转成uint64_t纳秒再运算 - 不要在信号处理函数里调用 ——
clock_gettime是异步信号安全的,但某些旧 glibc 版本有 bug,稳妥起见避开
Windows 上的替代方案与陷阱
Windows 没有等价于 CLOCK_PROCESS_CPUTIME_ID 的轻量接口。GetProcessTimes 返回的是 FILETIME(100ns 单位),但它是快照式调用,每次都要系统遍历线程、累加 TEB 中的计数器,开销比 Linux 高一个数量级(~500ns+),且可能因线程刚创建/退出而漏计。
- FILETIME 是 64 位整数,但低位是 100ns,转纳秒要乘 100,别写成 *1000
-
GetProcessTimes对当前进程调用时,lpCreationTime无意义,lpExitTime为 0,只取lpKernelTime和lpUserTime - 两个 FILETIME 相减后,必须先转为 64 位整数再计算,否则高位截断导致负值
- 若需跨平台,建议 Linux 用
clock_gettime,Windows 降级用GetProcessTimes,但采样频率应 ≤100Hz,避免反向拖慢目标进程
真正难的不是调用哪个函数,而是理解“CPU 时间”本身在不同调度策略(如 SCHED_FIFO vs CFS)、cgroup 限制、超线程共享物理核心等场景下的语义漂移 —— 这些不会改变 API 行为,但会让两次测量间的增量失去可比性。监控时务必记录 cgroup path、调度策略、逻辑 CPU 绑定状态。



















