像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
答案 0 :(得分:6)
在任何x86 CPU中,对于分支距离较短没有特殊情况。即使是对下一条指令的无条件jmp
(在架构上为nop),也需要正确的分支预测才能有效地进行处理;如果连续放置足够多的这些,则会耗尽BTB条目,从而使性能下降。 Slow jmp-instruction
获取/解码只是一个小问题;是的,同一高速缓存行中的一个非常短的分支仍将在L1i中命中,并且可能会进入uop高速缓存。但是,解码器不太可能将预测采取的前跳作为特殊情况,并利用包含分支和目标的一个块中的预解码指令边界发现。
当指令被解码为微指令并馈入前端时,寄存器值不可用;这些仅在无序执行后端中可用。
主要问题在于,执行.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以来,现代Intel拥有一个分支顺序缓冲区(BOB),可对分支上的RAT进行快照,从而可以在执行检测到后立即有效回滚到分支未命中,同时仍允许在早期< / em>指令以在回滚期间继续。在此之前,分支未命中必须回滚到退回状态。)
某些非x86 ISA的某些CPU(例如我认为的PowerPC)已经进行了实验,将正好跳过1条指令的分支转移为谓词(数据相关性),而不是对其进行推测。例如Dynamic Hammock Predication for Non-predicated Instruction Set Architectures讨论了这个想法,甚至决定是否每个分支都基于谓词。如果您的分支预测历史记录表明该分支的预测较差,则对其进行预测可能会很好。 (Hammock分支是向前跳转一条或几条指令的分支。在具有固定宽度指令字的ISA(如RISC)上,检测到正好是一种指令情况是微不足道的,但在x86上很难。)
在这种情况下,x86具有一条cmovcc
指令,这是一个ALU选择操作,根据标志条件产生两个输入之一。 cmove r9d, r8d
而不是cmp
/ je
可以避免分支错误预测,但是以引入对cl
和r8d
的数据依赖为代价有关使用r9d
的说明。英特尔CPU不会尝试为您这样做。
(在Broadwell和后来的Intel上,cmov仅为1 uop,低于2。cmp / jcc为1 uop,而mov
本身也是1 uop,因此在未采用的情况下{{1 }}对于前端的操作也更少。在采用的情况下,采用的分支即使在正确预测的情况下,也会在管道中引入气泡,具体取决于代码的吞吐量如何:级之间的队列是否可以吸收它。
有关引入数据依赖性不好的情况,CMOV比分支慢的情况,请参见gcc optimization flag -O3 makes code slower than -O2。