最可靠方式是用 std::chrono::steady_clock,它单调稳定、跨平台、不依赖系统 api;linux 下底层调用 clock_gettime(clock_monotonic),windows 下基于 queryperformancecounter,精度通常达 10–15 纳秒。

用 std::chrono 获取纳秒级时间差
最可靠、跨平台、且不依赖系统 API 的方式是使用 C++11 起标准库的 std::chrono。它能直接访问高精度时钟(通常是硬件计数器),精度可达纳秒级,但实际分辨率取决于系统——Linux/Windows 下通常为 10–15 纳秒左右。
关键不是“能不能到纳秒”,而是“两次读取之间的时间差是否稳定、单调、无跳变”。std::chrono::high_resolution_clock 满足这点,而 system_clock 可能因 NTP 调整跳变,steady_clock 更保守但保证单调,多数场景推荐后者。
实操建议:
- 用
std::chrono::steady_clock测量运行时间,避免系统时间被修改干扰 - 起始和结束时间必须用同一时钟类型,否则
duration_cast可能静默出错 - 别用
time_since_epoch().count()做绝对时间比较,只用差值 - 示例:
auto start = std::chrono::steady_clock::now(); // ... your code ... auto end = std::chrono::steady_clock::now(); auto us = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
Windows 下误用 GetTickCount64 导致 16ms 误差
GetTickCount64 返回的是系统启动以来的毫秒数,但默认系统定时器分辨率只有 15.625ms(即 1000/64 Hz)。哪怕你调用两次只隔几微秒,结果也可能完全一样,或突变 16ms——这不是函数 bug,是 Windows 内核节拍(tick)机制决定的。
立即学习“C++免费学习笔记(深入)”;
除非显式调用 timeBeginPeriod(1) 提升系统分辨率(需管理员权限,且影响全局电源管理),否则它不适合测短于 10ms 的代码段。
常见错误现象:
- 循环中反复测同一小段代码,
GetTickCount64返回值不变,算出耗时为 0 - 多次运行结果在 0 / 16 / 32 ms 间跳变,无法反映真实开销
- 与
std::chrono结果相差一个数量级,误以为自己代码“突然变快”
Linux 下 clock_gettime(CLOCK_MONOTONIC) 是 chrono 底层实现
glibc 的 std::chrono::steady_clock 在 Linux 上基本就是对 clock_gettime(CLOCK_MONOTONIC) 的封装。它绕过系统时间、不受 adjtimex 影响,且分辨率通常优于 gettimeofday(后者基于 CLOCK_REALTIME,可能跳变)。
如果你写 C 风格代码或需要极致控制,可以直接调用:
struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, &start); // ... code ... clock_gettime(CLOCK_MONOTONIC, &end); long long ns = (end.tv_sec - start.tv_sec) * 1000000000LL + (end.tv_nsec - start.tv_nsec);
注意点:
-
CLOCK_MONOTONIC_RAW更接近硬件计数器,但可能有漂移;一般用CLOCK_MONOTONIC即可 - 别用
CLOCK_PROCESS_CPUTIME_ID测“CPU 时间”——它统计线程实际占用 CPU 的时间,若代码大量等待 I/O 或被调度抢占,结果会远小于墙钟时间 - 返回值是
timespec,计算纳秒差时注意tv_nsec可能借位(end.tv_nsec ),上面示例已处理
重复测量时,编译器优化会让 chrono 测出 0ns
如果被测代码不含 volatile 操作、无副作用、且结果未被使用,现代编译器(如 GCC -O2、Clang)可能直接优化掉整段逻辑——此时 std::chrono 测出来的就是“空循环开销”,甚至 0。
这不是计时器不准,是你没让编译器“相信这段代码必须执行”。解决方法:
- 把关键计算结果写入
volatile变量(简单但略重) - 用
asm volatile("" ::: "memory")插入编译器屏障(轻量,阻止重排和消除) - 将结果输出到 stdout / stderr(IO 有副作用,编译器不敢删)
- 更严谨的做法:用
do { /* code */ } while(0)包裹,并确保每次运行都依赖前次输出(如累加到全局变量)
没有这些防护,你看到的“12ns”可能只是时钟读取指令本身的延迟,而不是你写的算法耗时。
真正难的不是拿到两个时间戳,而是确保中间那段代码没被优化、没被调度打断、也没被缓存效应扭曲。高精度计时器只是尺子,尺子再准,量的不是你想量的东西,就没意义。











