现代CPU中的小分支

时间:2019-03-02 22:58:37

标签: performance x86-64 cpu-architecture avx branch-prediction

像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

1 个答案:

答案 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可以避免分支错误预测,但是以引入对clr8d的数据依赖为代价有关使用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