rdtsc 最直接测量 CPU 周期,返回自上电以来的周期数,需用 unsigned long long 接收,配合 cpuid 或 lfence 串行化并取最小值以减少噪声;std::chrono 测的是挂钟时间,受调度、变频等影响,无法反映真实 CPU 周期。

用 rdtsc 读取时间戳计数器(TSC)最直接
在 x86/x64 上,rdtsc 指令能读取 CPU 的时间戳计数器,返回自上电以来的周期数(或按固定频率模拟的“参考周期”)。它快、无系统调用开销,适合粗粒度测量。但注意:rdtsc 返回的是 64 位值,需用 unsigned long long 接收;现代 CPU 可能启用 invariant TSC(频率恒定),但若未启用,不同核心或变频下数值不可比。
实操建议:
- 用内联汇编或
__rdtsc()内置函数(MSVC/GCC/Clang 都支持),避免手动写汇编出错 - 测量前加
cpuid(或_mm_lfence())串行化指令流,防止乱序执行干扰起止点 - 重复多次取最小值,减少中断、缓存抖动等噪声影响
- 不要测含系统调用、内存分配、锁竞争的代码——这些会把 CPU 周期“污染”成 wall-clock 时间
unsigned long long start = __rdtsc();
// your hot code here
unsigned long long end = __rdtsc();
printf("cycles: %llu\n", end - start);
为什么 std::chrono 不适合测 CPU 周期
std::chrono::high_resolution_clock 通常底层是 QueryPerformanceCounter(Windows)或 clock_gettime(CLOCK_MONOTONIC)(Linux),返回的是纳秒级挂钟时间,不是 CPU 周期。它受调度、休眠、频率缩放、跨核迁移等影响极大,同一段代码多次运行结果可能差几倍——这不是测量误差,而是它根本没在测你想要的东西。
常见错误现象:
立即学习“C++免费学习笔记(深入)”;
- 测一个空循环,结果每次输出差异巨大(比如 ±20%)
- 在笔记本上插电/电池模式切换后,数值系统性偏移
- 多线程环境下,不同线程测得的“周期数”无法横向比较
如果你真需要纳秒精度的 wall-clock 时间,std::chrono 没问题;但要 CPU 周期,它从语义上就不匹配。
Linux 下用 perf_event_open 更可靠但更重
当需要排除 TSC 不稳定、跨核不一致、或想统计特定事件(如 cache-misses、branch-mispredicts)时,perf_event_open 是更底层的选择。它通过内核 perf 子系统直接访问硬件性能计数器,支持 per-CPU 绑定、事件屏蔽、精确采样。
使用场景:
- CPU 频率动态变化频繁(如 Intel SpeedStep / AMD Cool'n'Quiet)
- 必须保证测量期间不被迁移到其他核心
- 需要同时获取周期数 + L1D cache miss 数 + 分支预测失败数
缺点也很明显:系统调用开销大、需 root 权限配置某些事件、代码复杂度高。简单周期测量没必要上这个。
关键参数示例:attr.type = PERF_TYPE_HARDWARE,attr.config = PERF_COUNT_HW_CPU_CYCLES,记得用 sched_setaffinity() 锁定 CPU 核心。
容易被忽略的陷阱:编译器优化和 CPU 乱序
哪怕用了 cpuid 和 rdtsc,如果被测代码太短,GCC/Clang 可能在编译期就把它整个优化掉——你测的其实是 0 个周期。而即使没优化,CPU 也可能把后续指令提前执行,导致 end 读得太早。
规避方法:
- 用
volatile变量或asm volatile("" ::: "rax","rbx","rcx","rdx")阻止编译器删代码 - 起始和结束都用
cpuid+rdtsc组合(cpuid是强序列化点) - 确保被测代码有可观察副作用(比如写入 volatile 全局变量),否则整个块可能被优化为无操作
- 禁用编译器优化(
-O0)只适用于调试;真实性能分析务必用-O2或-O3,并理解其影响
最麻烦的不是怎么读周期数,而是让 CPU 和编译器“老老实实按你想的顺序执行那几条指令”。这一步漏了,后面所有数字都是幻觉。


















