为什么有条件的移动不容易出现分支预测失败?
在阅读这篇文章(在StackOverflow的答案) (在优化部分),我想知道为什么有条件的移动不容易分支预测失败。 我在这里find一篇有关cond动作的文章(AMD的PDF) 。 在那里,他们声称cond的性能优势。 移动。 但为什么呢? 我不明白 在评估ASM指令的那一刻,前面CMP指令的结果还不知道。
谢谢。
错误预测的分支是昂贵的
如果事情顺利的话,现代处理器通常在每个周期执行一到三条指令(如果它不停止等待这些指令从先前的指令或从存储器到达的数据相关性)。
上面的语句对于严格的循环来说意外的好,但是这不应该让你看到一个附加的依赖关系,它可能会阻止一个指令在其周期到来时被执行:对于要执行的指令,处理器必须已经开始读取和解码它在15-20个周期之前。
处理器在遇到分支时应该做些什么? 对两个目标进行读取和解码不会缩放(如果有更多分支,则需要并行获取指数数量的path)。 所以处理器只能猜测地提取和解码两个分支之一。
这就是错误预测的分支很昂贵的原因:它们花费了15-20个周期,因为高效的指令stream水线通常是不可见的。
有条件的举动永远不会很昂贵
有条件的移动不需要预测,所以它永远不会有这个惩罚。 它具有与普通指令相同的数据依赖性。 实际上,条件移动比普通指令具有更多的数据依赖性,因为数据依赖包括“条件为真”和“条件错误”两种情况。 在有条件地将r1
移动到r2
, r2
的内容似乎取决于r2
的前一个值和r1
。 预测良好的条件分支允许处理器推断更准确的依赖关系。 但是,如果数据依赖性需要一段时间才能到达,那么通常需要一到两个周期才能到达。
请注意,从内存到寄存器的有条件的移动有时是一个危险的赌注:如果条件是从内存中读取的值没有被分配给寄存器,那么你已经在内存上等待了。 但是在指令集中提供的条件移动指令通常是注册寄存器,防止了程序员的这个错误。
这完全是关于指令pipe道的 。 请记住,现代CPU在stream水线中运行指令,当执行stream程可由CPU预测时,会显着提高性能。
CMOV
add eax, ebx cmp eax, 0x10 cmovne ebx, ecx add eax, ecx
在评估ASM指令的那一刻,前面CMP指令的结果还不知道。
也许,但是CPU仍然知道cmov
后面的cmov
会立即执行,而不pipecmp
和cmov
指令的结果如何。 因此下一条指令可以提前被安全地提取/解码,而分支则不是这样。
下一条指令甚至可以在cmov
之前执行(在我的例子中这将是安全的)
科
add eax, ebx cmp eax, 0x10 je .skip mov ebx, ecx .skip: add eax, ecx
在这种情况下,当CPU的解码器看到je .skip
,必须select是否继续1)来自下一条指令的预取/解码指令,或2)跳转目标。 CPU会猜测这个前向条件分支不会发生,所以下一条指令mov ebx, ecx
将进入stream水线。
几个周期后, je .skip
被执行,分支被采取。 哦,废话! 我们的pipe道现在拥有一些永远不应该被执行的随机垃圾。 CPU必须刷新所有caching的指令,并从.skip:
重新开始。
这是错误预测分支的性能损失,因为它不会改变执行stream程,所以不会发生在cmov
。
事实上,结果可能还不知道,但是如果其他情况允许的话(尤其是依赖链),CPU可以重新sorting并执行cmov
之后的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]
这里的问题是控制stream程正在改变,而且cpu不够聪明,如果分支被误判为已采取,它可能只是“插入”已跳过的mov
指令 – 而是抛弃它在分支之后做的所有事情,重新从头开始。 这是罚款的来源。
你应该阅读这些。 有了雾+英特尔,只需searchCMOV。
Linus Torvald对CMOV的批评大约在2007年
Agner Fog对微架构的比较
英特尔®64和IA-32体系结构优化参考手册
简而言之,正确的预测是“自由的”,而有条件分支预测失败可能花费Haswell的14-20个周期。 但是,CMOV从来不是免费的。 不过,我认为CMOV现在比Torvalds飙升的时候好多了。 在所有的处理器上,任何时候都没有一个正确的答案。