现代 CPU 中的小分支



像Kaby Lake这样的现代CPU如何处理小分支?(在下面的代码中,它是跳转到标签 LBB1_67)。据我所知,分支不会有害,因为跳转不如 16 字节块大小,即解码窗口的大小。

或者是否有可能由于某些宏操作融合而完全省略分支?

sbb     rdx, qword ptr [rbx - 8]
setb    r8b
setl    r9b
mov     rdi, qword ptr [rbx]
mov     rsi, qword ptr [rbx + 8]
vmovdqu xmm0, xmmword ptr [rbx + 16]
cmp     cl, 18
je      .LBB1_67
mov     r9d, r8d
.LBB1_67:                               #   in Loop: Header=BB1_63 Depth=1
vpcmpeqb        xmm0, xmm0, xmmword ptr [rbx - 16]
vpmovmskb       ecx, xmm0
cmp     ecx, 65535
sete    cl
cmp     rdi, qword ptr [rbx - 32]
sbb     rsi, qword ptr [rbx - 24]
setb    dl
and     dl, cl
or      dl, r9b

在任何 x86 CPU 中,短分支距离都没有特殊情况。 即使是对下一条指令的无条件jmp(在架构上是nop)也需要正确的分支预测才能得到有效处理;如果你连续放了足够多的那些,你就用完了BTB条目,性能就会断崖式下降。 慢速 jmp 指令

获取/解码只是一个小问题;是的,同一缓存行中非常短的分支仍然会在L1i和uop缓存中命中。 但是,解码器不太可能在特殊情况下进行预测的前向跳跃,并利用来自一个包含分支和目标的块的预解码指令边界查找。

当指令被解码为 uops 并馈送到前端时,寄存器值不可用;这些值仅在无序执行后端可用。

主要问题是,当.LBB1_67:执行指令后,架构状态会根据分支是否被采用而有所不同。 微架构状态也是如此(RAT = 寄存器分配表)。

也:

  • r9取决于sbb/setl结果(mov r9d, r8d没有运行)
  • r9取决于sbb/setb结果(mov r9d, r8d确实运行)

条件分支在计算机体系结构术语中称为"控制依赖关系"。 分支预测 + 推测执行避免将控件依赖关系转换为数据依赖关系。 如果未预测je,则setl结果(r9的旧值)将被mov覆盖,并且不再在任何地方使用。

在检测到je中的错误预测(实际上应该采取)后,无法从中恢复,尤其是在一般情况下。 当前的 x86 CPU 不会尝试寻找重新加入所采用路径的跌破路径或弄清楚它的作用。

如果cl长时间没有准备好,所以很长一段时间都没有发现错误预测,那么or dl, r9b之后的许多指令可能会使用错误的输入执行。 在一般情况下,可靠 + 有效恢复的唯一方法是放弃根据"错误"路径中的指令完成的所有工作。 例如,检测vpcmpeqb xmm0, [rbx - 16]仍然以任何一种方式运行都很难,而且不需要寻找。 (自 Sandybridge 以来,现代英特尔有一个分支顺序缓冲区 (BOB),它可以在分支上对 RAT 进行快照,允许在执行检测到分支未命中后立即有效地回滚到分支未命中,同时仍然允许在回滚期间继续对早期指令的无序执行。 在此之前,分支机构小姐必须回滚到退休状态。


一些用于某些非x86 ISA(例如我认为是PowerPC)的CPU已经尝试将跳过1条指令的分支向前转到预测(数据依赖性),而不是推测它们。 例如动态吊床谓词 对于非谓词指令集架构讨论了这个想法,甚至决定是否在每个分支的基础上谓词。 如果你的分支预测历史记录说这个分支预测不佳,那么预测它可能是好的。 (吊床分支是跳过一个或几个指令向前跳跃的分支。 在具有固定宽度指令字(如 RISC)的 ISA 上检测恰好 1 个指令案例是微不足道的,但在 x86 上很难。

在这种情况下,x86 具有cmovcc指令,即 ALU 选择操作,该操作根据标志条件生成两个输入之一。cmove r9d, r8d而不是cmp/je将使它免受分支错误预测的影响,但代价是引入对使用r9d指令的clr8d的数据依赖。 英特尔 CPU 不要尝试为您执行此操作。

(在Broadwell和后来的Intel上,cmov只有1 uop,低于2。 CMP/JCC 是 1 UOP,mov本身也是 1 UOP,所以在未采取的情况下cmov前端的 UOPS 也更少。 在给定的情况下,即使预测正确,一个被采用的分支也可能在管道中引入气泡,这取决于代码的吞吐量有多高:阶段之间的队列是否可以吸收它。

请参阅 gcc 优化标志 -O3 使代码比 -O2 慢,适用于 CMOV 比分支慢的情况,因为引入数据依赖关系是不好的。

相关内容

  • 没有找到相关文章

最新更新