为什么条件移动不易受分支预测失败的影响?

Mar*_*aux 72 optimization performance assembly cpu-architecture branch-prediction

在阅读了这篇文章后(在StackOverflow上回答)(在优化部分),我想知道为什么条件移动不容易受到分支预测失败的影响.我在一篇关于cond移动的文章中找到了(PDF由AMD提供).在那里,他们声称cond的性能优势.移动.但为什么会这样呢?我没有看到它.在评估ASM指令的时刻,前面的CMP指令的结果尚未知晓.

谢谢.

Pas*_*uoq 62

错误预测的分支机构很昂贵

如果事情进展顺利,现代处理器通常在每个周期执行一到三个指令(如果它没有停止等待这些指令的数据依赖性从先前的指令或从存储器到达).

上面的语句对于紧密循环来说非常好,但是这不应该让你失去一个额外的依赖,它可以阻止在循环到来时执行指令:对于要执行的指令,处理器必须已经开始获取和解码之前15-20个周期.

处理器遇到分支时应该怎么做?获取和解码两个目标都不会扩展(如果跟随更多分支,则必须并行获取指数数量的路径).因此处理器只推测性地获取和解码两个分支中的一个.

这就是为什么错误预测的分支是昂贵的:它们花费了15-20个周期,由于有效的指令管道,它们通常是不可见的.

有条件的举动永远不会非常昂贵

条件移动不需要预测,因此它永远不会有这种惩罚.它具有数据依赖性,与普通指令相同.实际上,条件移动比普通指令具有更多的数据依赖性,因为数据依赖性包括"条件真实"和"条件错误"情况.在有条件地移动r1到的指令之后r2,内容r2似乎依赖于之前r2和之前的值r1.良好预测的条件分支允许处理器推断出更准确的依赖性.但是,如果需要时间到达,数据依赖性通常需要一到两个周期才能到达.

请注意,从内存到寄存器的条件移动有时会是一个危险的赌注:如果条件是从内存中读取的值未分配给寄存器,则您等待内存什么都不做.但是指令集中提供的条件移动指令通常是寄存器来注册,防止程序员的这个错误.

  • @MartijnCourteaux一个典型的现代桌面处理器,其管道的所有阶段都能够处理大约3条指令,从而在最佳情况下实现3指令/周期吞吐量.解码级可以例如每个周期解码16个字节的指令:通常是3个指令.还有足够的执行单元在一个周期内处理三个**独立**指令.详情请访问http://www.agner.org/optimize/microarchitecture.pdf(顺便提一句,这是一个很好的参考). (4认同)

Mar*_*tin 44

这都是关于指令管道的.请记住,现代CPU在管道中运行其指令,当CPU可以预测执行流程时,这会显着提高性能.

CMOV

    add     eax, ebx
    cmp     eax, 0x10
    cmovne  ebx, ecx
    add     eax, ecx
Run Code Online (Sandbox Code Playgroud)

在评估ASM指令的时刻,前面的CMP指令的结果尚未知晓.

也许,但CPU仍然知道后面的指令cmov将被执行,而不管cmpcmov指令的结果如何.因此可以提前安全地提取/解码下一条指令,而分支不是这种情况.

下一条指令甚至可以在执行之前执行cmov(在我的示例中,这将是安全的)

    add     eax, ebx
    cmp     eax, 0x10
    je      .skip
    mov     ebx, ecx
.skip:
    add     eax, ecx
Run Code Online (Sandbox Code Playgroud)

在这种情况下,当CPU的解码器看到je .skip它必须选择是继续预取/解码指令1)来自下一条指令,还是2)来自跳转目标.CPU将猜测此前向条件分支不会发生,因此下一条指令mov ebx, ecx将进入管道.

几个周期之后,je .skip执行并执行分支.哦,废话!我们的管道现在拥有一些永远不应该执行的随机垃圾.CPU必须刷新所有缓存的指令并重新开始.skip:.

这是错误预测的分支的性能损失,cmov因为它不会改变执行流程,所以它永远不会发生.

  • 我可以弄清楚这可能是带有操作码,目标,源码的英特尔语法,但是如果你明确地提到你的汇编标准会很棒. (4认同)

Jes*_*ter 17

实际上结果可能还不知道,但如果其他情况允许(特别是依赖链),则cpu可以重新排序并执行后面的指令cmov.由于不涉及分支,因此无论如何都需要评估这些指令.

考虑这个例子:

cmoveq edx, eax
add ecx, ebx
mov eax, [ecx]
Run Code Online (Sandbox Code Playgroud)

后面的两条指令cmov不依赖于结果cmov,因此即使在cmov本身处于挂起状态时也可以执行它们(这称为乱序执行).即使它们无法执行,它们仍然可以被提取和解码.

分支版本可以是:

    jne skip
    mov edx, eax
skip:
    add ecx, ebx
    mov eax, [ecx]
Run Code Online (Sandbox Code Playgroud)

这里的问题是控制流正在改变,并且cpu不够聪明,以至于mov如果分支被错误预测为可能只是"插入"跳过的指令 - 而是它抛弃了它在分支之后所做的一切,并重新启动从头开始.这是罚款的来源.

  • 我可以弄清楚这可能是带有操作码,目标,源的Intel语法,但是如果您明确提及汇编标准,那就太好了。 (2认同)

Ols*_*ist 7

你应该阅读这些。使用Fog+Intel,只需搜索CMOV。

\n\n

Linus Torvald 在 2007 年左右对 CMOV 的批评
\n Agner Fog 对微架构的比较
\n Intel\xc2\xae 64 和 IA-32 架构优化参考手册

\n\n

简而言之,正确的预测是“免费的”,而条件分支错误预测可能会在 Haswell 上花费 14-20 个周期。然而,CMOV 从来都不是免费的。尽管如此,我仍然认为 CMOV 现在比托瓦兹咆哮时要好得多。没有一个答案能够在所有处理器上始终正确。

\n

  • 不,“cmov”仍然是数据依赖项,因此可以创建分支预测隐藏的循环携带依赖链。Intel Broadwell/Skylake 将其解码为单个 uop,而不是 2 个(Haswell 及更早版本),因此现在价格便宜一些。Sandybridge 和后来的 uop 缓存意味着多 uop 指令的解码吞吐量损失通常也不是一个因素。尽管如此,它并没有改变数据依赖和控制依赖之间的根本区别。另外,x86 `cmov` 仍然没有带有立即操作数的形式,所以 `x = x<3 ?x : 3` 的实现仍然很笨拙。 (3认同)