RDTSC 返回的值不能直接当纳秒用,因其读取的是依赖CPU频率的周期计数器,动态调频会导致相同计数值对应不同实际时间;即使支持constant_tsc,也需RDTSCP配合cpuid序列化才能准确测量。

为什么 RDTSC 返回的值不能直接当纳秒用
RDTSC 读的是 CPU 的内部周期计数器(TSC),不是时间。它的增量取决于当前 CPU 频率——如果开启了睿频、P-state 动态调频或节能模式,同一段代码在不同运行时刻可能对应完全不同的实际耗时。现代 x86-64 处理器大多支持 RDTSCP(带序列化)和恒定速率 TSC(invariant TSC),但默认仍不能假设“1 cycle = 1 ns”。
- 检查是否启用恒定 TSC:
cat /proc/cpuinfo | grep tsc,若含constant_tsc才可考虑频率换算 - 即使有
constant_tsc,也要用cpuid+RDTSCP防止指令乱序干扰测量边界 - 单纯
RDTSC指令在 GCC 内联汇编中不带内存屏障,前后代码可能被重排,测出的值毫无意义
如何用内联汇编安全读取 TSC(GCC/Clang)
必须用 RDTSCP 替代 RDTSC,并配合 cpuid 强制序列化,确保读数严格落在目标代码段前后。
uint64_t rdtscp_cycle() {
uint32_t lo, hi;
__asm__ volatile ("cpuid\n\t"
"rdtscp\n\t"
"mov %%rax, %0\n\t"
"mov %%rdx, %1\n\t"
"cpuid\n\t"
"xor %%rax, %%rax\n\t"
"xor %%rdx, %%rdx\n\t"
: "=r"(lo), "=r"(hi)
:
: "rax", "rdx", "rcx", "rbx");
return ((uint64_t)hi << 32) | lo;
}
-
cpuid是开销稍大但最可靠的序列化指令;xor清零寄存器是防止编译器误用残留值 - 不要只用
"rdtscp"而省略前后的cpuid—— 编译器仍可能把关键代码调度到rdtscp外面 - 返回值是无符号 64 位整数,别用
int64_t接收,否则高位溢出会变负
把 TSC 周期转成纳秒的靠谱做法
不能硬编码 “除以 2.4e9” 这类值。正确路径是:先用 clock_gettime(CLOCK_MONOTONIC, &ts) 获取高精度时间戳,再在循环中对齐多次 rdtscp_cycle() 采样,拟合出当前 TSC 频率。
- 单次测量误差常达 50–100 cycles,务必做至少 100 次采样后取中位数或剔除离群值
- Linux 下可通过
/sys/devices/system/clocksource/clocksource0/current_clocksource查看是否为tsc,是则内核已用 TSC 校准过CLOCK_MONOTONIC - 更简单但稍弱的替代:用
std::chrono::steady_clock::now()和rdtscp_cycle()同步采样一次,靠steady_clock的实现隐含 TSC 支持(glibc 2.30+ 默认如此)
Windows 上该用 __rdtscp 还是 QueryPerformanceCounter
直接用 QueryPerformanceCounter(QPC)。它在现代 Windows(Vista+)底层就绑定到 TSC(若硬件支持 invariant TSC),且自动处理跨核、节电、虚拟化等所有陷阱;而 __rdtscp 仍是裸硬件访问,你得自己做序列化、频率校准、跨 socket 一致性判断。
立即学习“C++免费学习笔记(深入)”;
- VC++ 中调用
__rdtscp必须加/arch:AVX或更高指令集开关,否则编译失败 - QPC 返回的是逻辑计数器值,用
QueryPerformanceFrequency拿到分母,二者相除即得秒级时间,精度通常优于 1 微秒 - 唯一要注意的是:某些老 BIOS 或虚拟机(如 VirtualBox 默认)禁用 TSC,此时 QPC 会 fallback 到 HPET,性能略低但结果依然可靠
pthread_setaffinity_np),要么改用 clock_gettime(CLOCK_MONOTONIC_RAW) 配合内核暴露的 TSC 校准参数。


















