ChatGPT解决这个技术问题 Extra ChatGPT

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

阅读 this post (answer on StackOverflow)(在优化部分)后,我想知道为什么条件移动不易受到分支预测失败的影响。我在 an article on cond moves here (PDF by AMD) 上找到的。同样在那里,他们声称 cond 的性能优势。移动。但这是为什么呢?我没看到。在评估该 ASM 指令时,尚不知道前面的 CMP 指令的结果。

顺便说一句,您可能想知道,根据我在 Intel Core2 和 Core-i7 CPU 上的经验,cmov 并不总是性能上的胜利。在我的测试中,只要预测率高于大约 99%,分支本身就更好。这听起来可能很高,但在英特尔的分支预测器中很常见。特别是分支内部循环会发生这种情况:比如说一个迭代 1000 次的分支,在第 999 次它会做一些不同的事情。这种情况使用条件跳转总是比使用 cmov 更有效。
PDF 链接目前需要授权。
对于 C++ 编译器,它们是相同的:See attached image
@NikolaiTrandafil:这完全取决于您选择的编译器、您启用的编译标志和目标 ISA。
相关:Is CMOVcc considered a branching instruction? - 不,这是 ALU 选择操作。答案包括一些指向性能权衡细节的链接。

P
Pascal Cuoq

错误预测的分支很昂贵

如果一切顺利的话,现代处理器通常每个周期执行 1 到 3 条指令(如果它没有停止等待这些指令从先前指令或内存到达的数据依赖关系)。

上面的语句非常适用于紧密循环,但这不应该让您忽视一个额外的依赖关系,它可以阻止指令在其循环到来时被执行:对于要执行的指令,处理器必须已经开始获取和解码它在 15-20 个周期之前。

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

这就是错误预测分支代价高昂的原因:它们花费了 15-20 个周期,而这些周期通常是不可见的,因为高效的指令流水线。

有条件的搬家从来都不是很贵

有条件的移动不需要预测,所以它永远不会有这个惩罚。它具有数据依赖性,与普通指令相同。事实上,条件移动比普通指令有更多的数据依赖,因为数据依赖包括“条件真”和“条件假”两种情况。在有条件地将 r1 移动到 r2 的指令之后,r2 的内容似乎取决于 r2 的先前值和 r1。良好预测的条件分支允许处理器推断更准确的依赖关系。但是数据依赖关系通常需要一到两个周期才能到达,如果它们需要时间来到达的话。

请注意,从内存到寄存器的有条件移动有时会是一个危险的赌注:如果条件是从内存读取的值没有分配给寄存器,那么您在内存上等待是徒劳的。但是指令集中提供的条件移动指令通常是寄存器到寄存器,防止程序员出现这种错误。


我同意你写的所有东西(或者至少看起来我可以接受),除了第一个陈述。你能详细说明一个 CPU 将在每个周期执行三个 asm 指令吗?
@MartijnCourteaux 一个典型的现代桌面处理器的管道的所有阶段都能够处理大约 3 条指令,在最好的情况下导致 3 条指令/周期的吞吐量。例如,解码阶段每个周期可以解码 16 个字节的指令:通常是 3 条指令。还有足够的执行单元在一个周期内处理三个独立指令。 agner.org/optimize/microarchitecture.pdf 的详细信息(顺便说一句,这是一个很好的参考)。
@MartijnCourteaux 例如第 79 页:“管道其余部分的吞吐量通常为每个时钟周期 4 条指令”(但您几乎永远不会得到理论上的每个周期 4 条指令。即使是 3 条也只有在算法允许并且需要手动操作的情况下为特定处理器型号编写的、手动对齐的代码)
那么,它可以解码 4 条指令,但同时处理 2 条或 3 条指令,这取决于我们对算法的幸运程度?
如果它们适合 16 个字节,它只能解码 4 条指令,因此这取决于您对开始所需的指令长度有多幸运。如果它有所有必要的单元来执行它们,它最多只能执行 4 个(如果这是你的目标,这可能需要混合浮点和整数计算来实现),并且如果一个的输入不是输出其他。如果您真的有兴趣,可以看看这种技术来增加细粒度并行性,但我应该警告您,它在实践中很少能加快速度:en.wikipedia.org/wiki/Software_pipelining
A
Andrew Durward

这都是关于 instruction pipeline 的。请记住,现代 CPU 在流水线中运行它们的指令,当 CPU 可预测执行流程时,这会显着提升性能。

cmov

    add     eax, ebx
    cmp     eax, 0x10
    cmovne  ebx, ecx
    add     eax, ecx

在评估该 ASM 指令时,尚不知道前面的 CMP 指令的结果。

也许吧,但是 CPU 仍然知道 cmov 之后的指令将在之后立即执行,而不管 cmpcmov 指令的结果如何。因此,可以提前安全地获取/解码下一条指令,而分支并非如此。

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

分支

    add     eax, ebx
    cmp     eax, 0x10
    je      .skip
    mov     ebx, ecx
.skip:
    add     eax, ecx

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

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

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


我可以弄清楚这可能是带有操作码、目标、源代码的 Intel 语法,但如果您明确提及您的汇编标准,那就太好了。
J
Jester

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

考虑这个例子:

cmoveq edx, eax
add ecx, ebx
mov eax, [ecx]

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

分支版本可以是:

    jne skip
    mov edx, eax
skip:
    add ecx, ebx
    mov eax, [ecx]

这里的问题是控制流正在发生变化,并且 cpu 不够聪明,无法看到如果分支被错误预测为采用,它可以“插入”跳过的 mov 指令 - 相反,它会丢弃它在分支之后所做的一切,并从头开始。这就是惩罚的来源。


我可以弄清楚这可能是带有操作码、目标、源代码的 Intel 语法,但如果您明确提及您的汇编标准,那就太好了。
O
Olsonist

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

Linus Torvald's critique of CMOV circa 2007
Agner Fog's comparison of microarchitectures
Intel® 64 and IA-32 Architectures Optimization Reference Manual

简短的回答,正确的预测是“免费的”,而条件分支错误预测可能会在 Haswell 上花费 14-20 个周期。但是,CMOV 从来都不是免费的。我仍然认为 CMOV 现在比 Torvalds 咆哮时好多了。在所有处理器上,没有一个永远正确的答案。


不,cmov 仍然是数据依赖项,因此可以创建分支预测将隐藏的循环携带依赖项链。英特尔 Broadwell/Skylake 将其解码为单个 uop 而不是 2(Haswell 和更早版本),因此现在价格便宜一些。 Sandybridge 和更高版本的 uop 缓存意味着多 uop 指令的解码吞吐量损失通常也不是一个因素。尽管如此,它并没有改变数据依赖和控制依赖之间的根本区别。此外,x86 cmov 仍然没有带有立即操作数的表单,因此 x = x<3 ? x : 3 仍然难以实现。
另一个可能感兴趣的链接:gcc.gnu.org/bugzilla/show_bug.cgi?id=56309
C
COLD ICE

我有这张来自 [Peter Puschner et al.] 幻灯片的插图,它解释了它如何转换为单路径代码,并加快执行速度。

https://i.stack.imgur.com/NOlPs.png


compare-and-predicate-next 指令会很好,但真正的架构通常也需要 3 条指令来表示谓词序列。 (除了 ARM 32 位,它可以 cmp / swplt,如果它有一个交换 / 交换指令。)无论如何,现代 CPU 通常不会有来自采取分支的气泡,它们有来自 错误预测< /i>:stackoverflow.com/questions/11227809/…。但是,在高吞吐量代码中,正确预测的分支可以减少一些解码/前端带宽。