阅读 this post (answer on StackOverflow)(在优化部分)后,我想知道为什么条件移动不易受到分支预测失败的影响。我在 an article on cond moves here (PDF by AMD) 上找到的。同样在那里,他们声称 cond 的性能优势。移动。但这是为什么呢?我没看到。在评估该 ASM 指令时,尚不知道前面的 CMP 指令的结果。
错误预测的分支很昂贵
如果一切顺利的话,现代处理器通常每个周期执行 1 到 3 条指令(如果它没有停止等待这些指令从先前指令或内存到达的数据依赖关系)。
上面的语句非常适用于紧密循环,但这不应该让您忽视一个额外的依赖关系,它可以阻止指令在其循环到来时被执行:对于要执行的指令,处理器必须已经开始获取和解码它在 15-20 个周期之前。
处理器遇到分支应该怎么做?获取和解码两个目标不会扩展(如果有更多分支,则必须并行获取指数数量的路径)。因此,推测性地,处理器仅获取和解码两个分支之一。
这就是错误预测分支代价高昂的原因:它们花费了 15-20 个周期,而这些周期通常是不可见的,因为高效的指令流水线。
有条件的搬家从来都不是很贵
有条件的移动不需要预测,所以它永远不会有这个惩罚。它具有数据依赖性,与普通指令相同。事实上,条件移动比普通指令有更多的数据依赖,因为数据依赖包括“条件真”和“条件假”两种情况。在有条件地将 r1
移动到 r2
的指令之后,r2
的内容似乎取决于 r2
的先前值和 r1
。良好预测的条件分支允许处理器推断更准确的依赖关系。但是数据依赖关系通常需要一到两个周期才能到达,如果它们需要时间来到达的话。
请注意,从内存到寄存器的有条件移动有时会是一个危险的赌注:如果条件是从内存读取的值没有分配给寄存器,那么您在内存上等待是徒劳的。但是指令集中提供的条件移动指令通常是寄存器到寄存器,防止程序员出现这种错误。
这都是关于 instruction pipeline 的。请记住,现代 CPU 在流水线中运行它们的指令,当 CPU 可预测执行流程时,这会显着提升性能。
cmov
add eax, ebx
cmp eax, 0x10
cmovne ebx, ecx
add eax, ecx
在评估该 ASM 指令时,尚不知道前面的 CMP 指令的结果。
也许吧,但是 CPU 仍然知道 cmov
之后的指令将在之后立即执行,而不管 cmp
和 cmov
指令的结果如何。因此,可以提前安全地获取/解码下一条指令,而分支并非如此。
下一条指令甚至可以在 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
永远不会发生这种情况,因为它不会改变执行流程。
实际上结果可能还不知道,但如果其他情况允许(特别是依赖链),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
指令 - 相反,它会丢弃它在分支之后所做的一切,并从头开始。这就是惩罚的来源。
你应该阅读这些。使用 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
仍然难以实现。
我有这张来自 [Peter Puschner et al.] 幻灯片的插图,它解释了它如何转换为单路径代码,并加快执行速度。
https://i.stack.imgur.com/NOlPs.png
cmp
/ swplt
,如果它有一个交换 / 交换指令。)无论如何,现代 CPU 通常不会有来自采取分支的气泡,它们有来自 错误预测< /i>:stackoverflow.com/questions/11227809/…。但是,在高吞吐量代码中,正确预测的分支可以减少一些解码/前端带宽。
不定期副业成功案例分享