This question already has answers here:
Why is a conditional move not vulnerable for Branch Prediction Failure?

(5个答案)


3年前关闭。





我很困惑在汇编中在哪里使用cmov指令以及在哪里使用jump指令?

从性能角度来看:


两者有什么区别?
哪一个更好?


如果可能,请举例说明它们的区别。

最佳答案

movcc是所谓的谓词指令。这就是“该指令在条件(谓词)下执行”的幻想。

在进行算术运算(尤其是比较指令)之后,包括x86在内的许多处理器都将条件代码位设置为指示运算结果的状态。

条件跳转指令检查条件码位的状态,如果为真,则跳转到指定的目标。

因为跳转是有条件的,并且处理器通常具有较深的流水线,所以当CPU遇到jmp指令时,条件码位可能实际上并未准备好让jmp指令处理。芯片设计人员可以简单地等待流水线耗尽(通常需要很多时钟周期),然后执行jmp,但这会使处理器变慢。

取而代之的是,大多数人选择使用分支预测算法,该算法可以预测条件跳转的方向。处理器然后可以提取,解码和执行(或不执行)预测的分支,并继续快速执行,前提条件是如果最终到达的条件代码位对于条件而言是错误的(分支错误预测),则处理器撤消分支之后所做的所有工作,然后重新执行沿另一条路径运行的程序。

有条件的跳转比常规的数据依赖性更难于流水线执行,因为条件跳转可以改变流水线中的指令流中的下一条指令。与数据依赖关系(例如add,其中两个输入都是其他最近指令的输出)相比,这称为control dependency

分支预测器的结果非常好,因为大多数分支趋向于对其方向产生偏见。 (通常,大多数循环末尾的分支将分支回到顶部)。因此,大多数时候,处理器不必退出错误预测的工作。

如果分支的方向高度不可预测,则处理器将在大约50%的时间内猜错,因此必须退出工作。这太贵了。

好吧,现在,人们经常会找到这样的代码:

  cmp   ...
  jcc   $
  mov   register1, register2
$: ; continue here
  ...
  ; use register1


如果分支预测器猜对了,那么无论分支走到哪条路,此代码都很快。如果它猜错很多...哎呀。

因此条件移动指令。此操作基于条件代码位有条件地移动数据。我们可以重写上面的内容:

  cmp   ...
  movcc  register1, register2
$: ; continue here
  ...
  ; use register1


现在我们没有分支指令,因此也没有使处理器撤消所有工作的错误预测。由于不存在控制依赖性,因此无论movcc是像mov还是nop的行为,都需要提取和解码以下指令。流水线可能会保持满状态,而无需预测条件并推测性地执行使用register1的指令。 (您可以用这种方式构建CPU,但会违反movcc的目的。)

movcc将控件依赖项转换为数据依赖项。 CPU完全像3输入数学指令一样对待它,输入是EFLAGS,它是两个“常规”输入(目标寄存器和源寄存器或内存)。在x86上,就无序执行如何跟踪依赖项而言,adccmovae相同(如果为CF==0,则为cmovcc相同):输入为CF,两个GP寄存器。输出是目标寄存器。

对于x86,每个条件组合cc都有jccsetccsetcc指令。 (根据条件将目标设置为0或1。因此,它对标志有数据依赖性,而没有其他输入依赖性。)

07-24 09:44
查看更多