我在IvyBridge上。我发现jnz
的性能行为在内部循环和外部循环中不一致。
以下简单程序具有一个固定大小为16的内部循环:
global _start
_start:
mov rcx, 100000000
.loop_outer:
mov rax, 16
.loop_inner:
dec rax
jnz .loop_inner
dec rcx
jnz .loop_outer
xor edi, edi
mov eax, 60
syscall
perf
工具显示外部循环运行32c / iter。这表明jnz
需要2个周期才能完成。
然后我在Agner的指令表中进行搜索,条件跳转具有1-2个“倒数”,并带有注释“如果没有跳转则为快速”。
在这一点上,我开始相信上述行为是可以预料的。但是,为什么jnz
在外部循环中只需要1个周期即可完成?
如果我完全删除.loop_inner
部分,则外循环运行1c / iter。行为看起来不一致。
我在这里想念什么?
使用以下命令,上述程序的perf
结果:
perf stat -ecycles,branches,branch-misses,lsd.uops,uops_issued.any -r4 ./a.out
是:
3,215,921,579 cycles ( +- 0.11% ) (79.83%)
1,701,361,270 branches ( +- 0.02% ) (80.05%)
19,212 branch-misses # 0.00% of all branches ( +- 17.72% ) (80.09%)
31,052 lsd.uops ( +- 76.58% ) (80.09%)
1,803,009,428 uops_issued.any ( +- 0.08% ) (79.93%)
参考案例的perf
结果:
global _start
_start:
mov rcx, 100000000
.loop_outer:
mov rax, 16
dec rcx
jnz .loop_outer
xor edi, edi
mov eax, 60
syscall
是:
100,978,250 cycles ( +- 0.66% ) (75.75%)
100,606,742 branches ( +- 0.59% ) (75.74%)
1,825 branch-misses # 0.00% of all branches ( +- 13.15% ) (81.22%)
199,698,873 lsd.uops ( +- 0.07% ) (87.87%)
200,300,606 uops_issued.any ( +- 0.12% ) (79.42%)
因此,原因很明显:在嵌套情况下,LSD由于某种原因停止工作。减小内部循环的大小会稍微缓解缓慢性,但不能完全缓解。
在搜索英特尔的《优化手册》时,我发现如果循环包含“超过八个分支”,则LSD将无法工作。这以某种方式解释了行为。
答案 0 :(得分:3)
(部分答案/我在Hadi发表详细分析之前还没有写完文章的猜测;其中一些是从评论中继续的)
Agner的陈述“在uop缓存不是瓶颈的情况下,循环缓冲区没有可测量的作用……”是错误的吗?因为这肯定是可以测量的效果,并且uop缓存不是瓶颈,因为该缓存具有约1.5K的容量。
是的,Agner将其称为回送缓冲区。 他的声明是,将LSD添加到设计中不会加快任何代码的速度。但是,是的,对于非常紧密的循环,至少对于嵌套循环,这似乎是错误的。显然,SnB / IvB确实需要循环缓冲区来发出或执行1c / iter循环。除非微体系结构瓶颈是分支后要从uop缓存中获取uop,否则他的警告将解决此问题。
除了uop缓存未命中以外,在某些情况下读取uop缓存可能会成为瓶颈。例如如果由于对齐效果而导致uops打包得不太好,或者它们使用大的立即数和/或位移,而这需要花费额外的周期才能从uop缓存中读取。有关这些效果的更多详细信息,请参见Agner Fog's uarch guide的Sandybridge部分。您认为容量(如果包装得很好的话最多可达1.5k uops)是它可能变慢的唯一原因,这是非常错误的。
BTW,Skylake的微代码更新使LSD完全无法修复LSD,从而修复了部分寄存器合并错误erratum SKL150 1 ,实际上除了效果不大当一个微小的循环跨越32B边界并需要2条缓存行时。
但是Agner列出了JMP rel8/32
,并在HSW / SKL上将JCC吞吐量视为1-2个周期,而在IvB上仅为2个周期。因此,自IvB以来,除了LSD本身以外,关于已采取分支的某些事情可能已经加速了。
除了LSD之外,可能还有CPU的某些部分,这对于长时间运行的微小循环也有特殊的情况,这使它们在Haswell及以后的时钟上每时钟运行1次跳跃。我尚未测试过什么条件会导致HSW / SKL上的分支吞吐量达到1个周期与2个周期。还要注意,Agner在更新勘误码SKL150之前进行了测量。
脚注1 :请参见How exactly do partial registers on Haswell/Skylake perform? Writing AL seems to have a false dependency on RAX, and AH is inconsistent,并请注意,SKX和Kaby Lake附带了已经包含此代码的微码。它终于在Coffee Lake中重新启用,它修复了有问题的硬连线逻辑,因此可以安全地再次启用LSD。 https://en.wikichip.org/wiki/intel/microarchitectures/coffee_lake#Key_changes_from_Kaby_Lake