条件指令(cmov)和跳转指令之间的区别

jig*_*uff 16 performance assembly instructions mov

我很困惑在何处使用cmov说明以及jump在汇编中使用说明的位置?

性能的角度来看:

  • 两者有什么不同?
  • 哪一个更好?

如果可能,请通过示例解释他们的不同之处.

Ira*_*ter 16

movcc是一种所谓的谓词指令.这是花哨的 - 说"这条指令在条件(谓词)下执行".

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

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

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

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

对于流水线执行,条件跳转比普通数据依赖更难,因为它们可以更改流经管道的指令流中的下一条指令.这称为控制依赖,而不是数据依赖(如add两个输入都是其他最近指令的输出).

分支预测变得非常好,因为大多数分支往往偏向于他们的方向.(大多数循环结束时的分支,通常会分支回到顶部).因此,大多数情况下,处理器不必退出错误预测的工作.

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

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

  cmp   ...
  jcc   $
  mov   register1, register2
$: ; continue here
  ...
  ; use register1
Run Code Online (Sandbox Code Playgroud)

如果分支预测器猜对了,那么无论分支走哪条,这段代码都很快.如果猜错了很多......哎哟.

因此有条件的移动指令.这是根据条件代码位有条件地移动数据的移动.我们可以改写上面的内容:

  cmp   ...
  movcc  register1, register2
$: ; continue here
  ...
  ; use register1
Run Code Online (Sandbox Code Playgroud)

现在我们没有分支指令,因此没有错误预测使处理器撤消所有工作.由于没有控制依赖性,因此需要获取和解码以下指令,而不管这些movcc行为是否为a movnop.管道可以保持完整而不预测条件并推测性地执行使用的指令register1.(你可以用这种方式构建一个CPU,但它会破坏目的movcc.)

movcc将控件依赖项转换为数据依赖项.CPU将其视为3输入数学指令,输入为EFLAGS及其两个"常规"输入(dest寄存器和源寄存器或存储器).在x86上,adccmovae(mov if CF==0)相同,就乱序执行如何跟踪依赖关系而言:输入是CF,以及两个GP寄存器.输出是目标寄存器.

为x86,还有cmovcc,jccsetcc为每个条件组合立方厘米指令.(setcc根据条件将目标设置为0或1.因此它对标志具有数据依赖性,并且没有其他输入依赖性.)

  • 任何理论都必须通过实验进行测试.当您使用两种变体(branch + mov vs cmov)实际测试代码时会发生什么?显然,有人已经做过:https://github.com/xiadz/cmov并且图表似乎指出分支必须99%的时间正确预测,否则cmov显然胜出. (6认同)
  • Linus http://yarchive.net/comp/linux/cmov.html对性能的有趣讨论 (3认同)
  • 当你拥有超级简单的分支时,这就是编译器不会因条件移动而疯狂的原因.它们需要被安排得恰到好处才能完美,如果它不能完美,编译器很难事先预测跳跃,以了解成本是否值得. (2认同)
  • 答案是在正确的轨道上,但令人困惑的事情,不幸的是非常错误 - 条件代码位的延迟与问题无关(它影响cmov和分支,并不会导致问题).问题是,当cpu执行分支时,它可能已经获取,解码并部分执行了许多后续指令 - 当跳转被错误预测时,这些必须被撤消并丢弃.这种开销可能与错过的几十条指令一样昂贵.条件移动不会遇到此问题,但可能需要额外的计算 (2认同)