像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
指令的cl
和r8d
的数据依赖。 英特尔 CPU 不要尝试为您执行此操作。
(在Broadwell和后来的Intel上,cmov只有1 uop,低于2。 CMP/JCC 是 1 UOP,mov
本身也是 1 UOP,所以在未采取的情况下cmov
前端的 UOPS 也更少。 在给定的情况下,即使预测正确,一个被采用的分支也可能在管道中引入气泡,这取决于代码的吞吐量有多高:阶段之间的队列是否可以吸收它。
请参阅 gcc 优化标志 -O3 使代码比 -O2 慢,适用于 CMOV 比分支慢的情况,因为引入数据依赖关系是不好的。