在阅读了这篇文章(关于StackOverflow的答案)(在优化部分)之后,我想知道为什么条件移动不容易受到分支预测失败的影响。我在一篇关于cond move的文章中找到了该文章(AMD撰写的PDF)。同样,他们声称cond的性能优势。动作。但是为什么呢?我没看到 在评估该ASM指令时,尚不清楚先前的CMP指令的结果。
在阅读了这篇文章(关于StackOverflow的答案)(在优化部分)之后,我想知道为什么条件移动不容易受到分支预测失败的影响。我在一篇关于cond move的文章中找到了该文章(AMD撰写的PDF)。同样,他们声称cond的性能优势。动作。但是为什么呢?我没看到 在评估该ASM指令时,尚不清楚先前的CMP指令的结果。
Answers:
如果情况进展顺利,现代处理器通常每个周期执行一到三个指令(如果它不会停顿等待这些指令的数据相关性从先前的指令或从内存到达)。
上面的语句在紧密循环中表现出令人惊讶的良好表现,但这不应该使您对一个附加的依赖项视而不见,后者可以阻止指令在其周期到来时执行:要执行一条指令,处理器必须已经开始获取和解码在15-20个周期之前。
处理器遇到分支时应该怎么做?提取和解码两个目标均无法扩展(如果跟随更多分支,则必须并行获取指数级的路径)。因此,处理器仅以推测方式获取和解码两个分支之一。
这就是为什么错误预测的分支很昂贵的原因:分支需要花费15-20个周期,由于有效的指令流水线,这些周期通常是不可见的。
有条件的移动不需要预测,因此它永远不会受到惩罚。它具有数据依赖性,与普通指令相同。实际上,条件移动比普通指令具有更多的数据相关性,因为数据相关性包括“条件为真”和“条件为假”两种情况。指令之后,有条件地移动r1
到r2
,内容r2
似乎依赖于前面两个值r2
和r1
。预测良好的条件分支允许处理器推断出更准确的依赖性。但是,如果数据依赖关系完全需要时间,则通常需要一两个周期才能到达。
请注意,从内存到寄存器的有条件移动有时是危险的选择:如果条件是从内存读取的值未分配给寄存器,则您在内存上等待的时间为空。但是,指令集中提供的条件移动指令通常是按寄存器注册的,从而避免了程序员的这种错误。
这全是关于指令流水线的。请记住,现代CPU在管道中运行其指令,当CPU可以预测执行流程时,这将显着提高性能。
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在2007年左右对CMOV的批评
Agner Fog对微体系结构的比较Intel®64
和IA-32体系结构优化参考手册
简短的回答,正确的预测是“免费的”,而条件分支的错误预测可能会在Haswell上花费14-20个周期。但是,CMOV永远不会免费。不过,我仍然认为CMOV比Torvalds抱怨时要好得多。在所有曾经回答过的处理器上,没有任何一种方法能够始终正确。
cmov
仍然是数据依赖项,因此可以创建分支预测将隐藏的循环承载的依赖项链。英特尔Broadwell / Skylake将其解码为单个uop,而不是2(Haswell和更早版本),因此现在价格便宜一些。Sandybridge和更高版本的uop缓存意味着多uop指令的解码吞吐量损失通常也不是一个因素。尽管如此,它并没有改变数据和控件依赖项之间的根本区别。同样,x86cmov
仍然没有带有立即操作数的形式,因此x = x<3 ? x : 3
实现起来仍然很笨拙。
cmp
/ swplt
。)无论如何,现代CPU通常不会从分支中产生气泡,而会因错误预测而产生气泡:stackoverflow.com/questions/11227809/…。但是,在高吞吐量代码中,正确预测的采用分支可能会减少解码/前端带宽。