现代CPU中的小分支

Stringer 7 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

Peter Cordes.. 6

在任何x86 CPU中,对于分支距离较短没有特殊情况。即使是无条件jmp的下一条指令(通常为nop),也需要正确的分支预测才能有效地进行处理。如果连续放置足够多的这些,则会耗尽BTB条目,从而使性能下降。 慢速jmp指令

取/解码只是一个小问题;是的,同一高速缓存行中的一个非常短的分支仍将在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进行快照,从而可以在执行检测到后立即有效回滚到分支未命中,同时仍然允许在执行期间继续执行较早指令的无序执行在此之前,分支未命中必须回退到退休状态。)


一些用于非x86 ISA的CPU(例如,我认为是PowerPC)已经进行了尝试,将正好跳过1条指令的分支转为谓词(数据相关性),而不是推测过去。例如,非谓语指令集体系结构的动态吊床谓词讨论了这一思想,甚至决定是否基于每个分支谓词。如果您的分支预测历史记录表明该分支的预测较差,则对其进行预测可能会很好。(一个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,因此在未采用的情况下cmov,前端的uops也更少。在采用的情况下,即使正确预测,采用的分支也会在管道中引入气泡,具体取决于代码的吞吐量如何:级之间的队列是否可以吸收它。)

对于CMOV比分支慢的情况,请参见gcc优化标志-O3使代码比-O2慢,因为引入数据依赖性很差。