Daniel Lemire的benchmark代码展示了在X86
平台上,如何度量一段代码运行了多少时钟周期:
......
#define RDTSC_START(cycles) \
do { \
unsigned cyc_high, cyc_low; \
__asm volatile("cpuid\n\t" \
"rdtsc\n\t" \
"mov %%edx, %0\n\t" \
"mov %%eax, %1\n\t" \
: "=r"(cyc_high), "=r"(cyc_low)::"%rax", "%rbx", "%rcx", \
"%rdx"); \
(cycles) = ((uint64_t)cyc_high << 32) | cyc_low; \
} while (0)
#define RDTSC_FINAL(cycles) \
do { \
unsigned cyc_high, cyc_low; \
__asm volatile("rdtscp\n\t" \
"mov %%edx, %0\n\t" \
"mov %%eax, %1\n\t" \
"cpuid\n\t" \
: "=r"(cyc_high), "=r"(cyc_low)::"%rax", "%rbx", "%rcx", \
"%rdx"); \
(cycles) = ((uint64_t)cyc_high << 32) | cyc_low; \
} while (0)
......
RDTSC_START(cycles_start);
......
RDTSC_FINAL(cycles_final);
cycles_diff = (cycles_final - cycles_start);
......
这段代码其实参考自How to Benchmark Code Execution Times on Intel® IA-32 and IA-64 Instruction Set Architectures,原理如下:
(1)测量开始和结束时的cpuid
指令用来防止代码的乱序执行(out-of-order
),即保证cpuid
之前的指令不会调度到cpuid
之后执行,因此两个cpuid
指令之间只包含要度量的代码,没有掺杂其它的。
(2)rdtsc
和rdtscp
都是读取系统自启动以来的时钟周期数(cycles
,高32
位保存在edx
寄存器,低32
位保存在eax
寄存器),并且rdtscp
保证其之前的代码都已经完成。两次采样值相减就是我们需要的时钟周期数。
综上所述,通过cpuid
,rdtsc
和rdtscp
这3
条汇编指令,我们就可以计算出一段代码到底消耗了多少时钟周期。
P.S.,stackoverflow也有相关的讨论。