你的第一段代码(导致标题问题)存在缺陷,因为它用EAX、EBX、ECX和EDX中的cpuid
结果覆盖了rdtsc
和rdtscp
结果。
使用lfence
代替cpuid
; 在英特尔处理器上自始至终,在启用Spectre缓解的AMD处理器上,lfence
将序列化指令流,从而使用rdtsc
实现您想要的效果。
请记住,RDTSC计数参考周期,而不是核心时钟周期。 获取CPU周期计数? 了解有关RDTSC的更多信息。
在您的测量间隔内,您没有cpuid
或lfence
。但是您确实在测量间隔内拥有rdtscp
本身。连续的rdtscp
并不快速,如果您没有预热CPU,则64个参考周期听起来完全合理。空闲时钟速度通常比参考周期慢得多; 1个参考周期等于或接近“贴纸”频率,例如英特尔CPU上的最大非Turbo持续频率,例如Skylake CPU上的4008 MHz。
这不是衡量单个指令时间的方法
重要的是另一条指令可以使用结果之前的延迟,而不是直到它完全从乱序后端退役的延迟。 RDTSC可用于计时相对变化,即一个加载或存储指令需要多长时间,但开销意味着您无法获得良好的绝对时间。
您可以尝试减去测量开销。例如clflush通过C函数使缓存行无效。请参见后续内容:使用时间戳计数器和clock_gettime进行缓存丢失和使用时间戳计数器进行内存延迟测量。
这是我通常用来分析短指令块的延迟或吞吐量(以及融合和未融合域)的工具。根据需要调整使用方式,可以像这里一样限制延迟,或者如果只想测试吞吐量,则不需要。例如,使用具有足够不同寄存器的
%rep
块来隐藏延迟,或在短块后使用
pxor xmm3, xmm3
打破依赖链,让乱序执行发挥其魔力。(只要不在前端瓶颈)。您可能希望使用NASM的smartalign包或使用YASM,以避免ALIGN指令的大量单字节NOP指令。在64位模式下,即使始终支持长NOP,NASM默认也会使用非常愚蠢的NOP。
global _start
_start:
mov ecx, 1000000000
align 32
.loop:
times 4 addsd xmm4, xmm3
dec ecx
jnz .loop
mov eax, 231
xor edi, edi
syscall
使用类似以下的一行命令运行它,将其链接到静态可执行文件并使用perf stat
进行性能分析,每次更改源代码时都可以向上箭头重新运行:
(实际上,我将nasm+ld + 可选反汇编放入一个名为asm-link
的shell脚本中,以节省在不进行性能分析时的输入。反汇编确保您循环中的内容是您想要进行性能分析的内容,特别是如果您的代码中有一些%if
东西。还可以在测试头脑中的理论时向上滚动以查看终端上的输出。)
t=testloop; nasm -felf64 -g "$t.asm" && ld "$t.o" -o "$t" && objdump -drwC -Mintel "$t" &&
taskset -c 3 perf stat -etask-clock,context-switches,cpu-migrations,page-faults,cycles,branches,instructions,uops_issued.any,uops_executed.thread -r4 ./"$t"
i7-6700k在3.9GHz下的结果(当前perf
存在单位缩放显示错误的bug,已经在上游修复,但Arch Linux尚未更新):
Performance counter stats for './testloop' (4 runs):
4,106.09 msec task-clock
17 context-switches
0 cpu-migrations
2 page-faults
16,012,778,144 cycles
1,001,537,894 branches
6,008,071,198 instructions
5,013,366,769 uops_issued.any
5,013,217,655 uops_executed.thread
4.106283 +- 0.000536 seconds time elapsed ( +- 0.01% )
在我的i7-6700k(Skylake)上,
addsd
具有4个周期的延迟,0.5个周期的吞吐量。(即如果延迟不是瓶颈,则每个时钟周期为2)。请参见:
https://agner.org/optimize/,
https://uops.info/和
http://instlatx64.atw.hu/。
每个分支16个周期=每个4个addsd
链路16个周期= addsd
的4个周期延迟,甚至在包含一点启动开销和中断开销的测试中也能准确重现Agner Fog的4个周期延迟测量结果。
选择要记录的不同计数器。将
:u
添加到perf事件中,例如
instructions:u
仅计算用户空间指令,不包括在中断处理程序运行期间运行的任何指令。我通常不这样做,因此可以看到超时作为解释壁钟时间的一部分。但是,如果您这样做,
cycles:u
与
instructions:u
非常接近。
-r4
运行4次并平均,这对于查看运行之间的差异是否很大而不是从ECX中较高值获得一个平均值非常有用。
调整初始ECX值,使总时间约为0.1至1秒,通常已足够,特别是如果您的CPU非常快地达到最大睿频(例如具有硬件P状态和相当激进的energy_performance_preference的Skylake)或关闭睿频时的最大非睿频。
但是,这计算以核心时钟周期而不是参考时钟周期为单位,因此仍会在CPU频率更改时给出相同的结果。(+-一些噪音来自在过渡期间停止时钟。)