最可靠获取纯用户态CPU时间的方法是用clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts)得总时间,再减去内核态时间;Linux可用/proc/self/stat第14字段,macOS需fallback到getrusage,Windows用GetProcessTimes。

用 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts) 获取总 CPU 时间,再减去内核态时间
POSIX 没有直接提供「纯用户态进程 CPU 时间」的标准 API。但 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts) 返回的是用户态 + 内核态的总和,而纯用户态时间 = 总 CPU 时间 − 内核态时间。这个减法是目前最可靠、精度最高(纳秒级)的做法。
常见错误是误以为 CLOCK_MONOTONIC 或 std::chrono::high_resolution_clock 能反映 CPU 时间——它们测的是墙上时间,线程 sleep 1 秒也会计入,完全不相关。
- 必须链接
-lrt(尤其在旧版 glibc 或静态链接时) - 调用后检查返回值:
clock_gettime失败时返回-1,errno可能为EINVAL(内核不支持该 clock ID)或EFAULT(ts指针非法) - macOS 不支持
CLOCK_PROCESS_CPUTIME_ID,需 fallback 到getrusage(RUSAGE_SELF, &ru)的ru.ru_utime
从 /proc/self/stat 解析第 14 字段 utime(Linux 专属)
/proc/self/stat 是 Linux 下唯一稳定获取纯用户态 jiffies 的方式。第 14 字段(索引从 1 开始)是 utime,表示进程在用户态消耗的时钟滴答数。它必须换算成秒:除以 sysconf(_SC_CLK_TCK),不能硬编码为 100。
容易踩的坑:
立即学习“C++免费学习笔记(深入)”;
- 字段位置易错:comm 字段(第 2 字段)被括号包围且可能含空格,用
std::ifstream >>读取时会把一个带空格的命令名拆成多个 token,导致后续字段索引偏移;utime实际是第 14 个字段,但用>>读出的 vector 中可能是第 13 或 14 个元素 - 没检查
fields.size() >= 14就访问fields[13],触发越界崩溃 - 忽略
sysconf(_SC_CLK_TCK),在某些容器或高精度内核(HZ=1000)下结果偏差 10 倍
用 getrusage(RUSAGE_SELF, &ru) 便携但精度低
getrusage(RUSAGE_SELF, &ru) 的 ru.ru_utime 成员直接给出用户态时间(struct timeval),跨 Linux/macOS/BSD 都可用,无需解析文本,适合需要兼容性的场景。
但它只有微秒级精度(tv_usec 最大 999999),且在某些容器或 cgroup 限制环境下,值可能滞后或清零。务必检查函数返回值:失败时 ru.ru_utime 未定义。
- 包含头文件:
#include <sys/resource.h> - 计算秒数:
double user_sec = ru.ru_utime.tv_sec + ru.ru_utime.tv_usec * 1e-6; - 该值不含子进程;若要含子进程,改用
RUSAGE_CHILDREN
Windows 上只能用 GetProcessTimes 并手动分离
Windows 没有等价于 /proc/self/stat 的轻量接口。GetProcessTimes 返回的 lpUserTime 字段就是用户态 CPU 时间(单位:100 纳秒),语义最接近 Linux 的 utime。
关键点:
- 必须用
GetCurrentProcess()获取伪句柄,不能用OpenProcess打开自己(权限问题) -
FILETIME是 64 位结构,需转成ULARGE_INTEGER或用(((int64_t)ft.dwHighDateTime) << 32) | ft.dwLowDateTime得到 100 纳秒总数,再除以 10000 得毫秒 - 该值只统计当前进程,不含子进程;也不含已退出线程的历史时间
真正难的不是选哪个函数,而是意识到:用户态时间 ≠ 代码执行时间——系统调用陷入内核后返回用户空间前的那段“准备动作”,可能被归入内核态;而缺页异常处理中的部分逻辑,也可能被计入用户态。测量值永远是内核调度器视角下的统计快照,不是源码行级别的精确切片。


















