作为条件,它已经需要 16 个不同的操作码,仅用于 cmov r, r/m 形式,每个不同的 cc 条件对应一个,就像 jcc 和 setcc(当然,同义词共享一个操作码)。
因此,即使有另外 16 个0F xx 操作码的“空间”,当英特尔为 Pentium Pro 添加它时,花费所有编码空间可能也不值得。好吧,也许是一个符号扩展的 imm8 表格。这将占用其他新操作码的空间,例如英特尔可能已经开始设计或至少考虑在 P6 的 ISA 扩展完成时为 Pentium-MMX 和 Pentium III 设计的 MMX 和 SSE 指令。
当您需要cmov(通常有条件地将某些东西归零)时,大多数时候imm8 表单会很有用,但它不是必需的。 RISC 哲学(英特尔在 P61 中采用的理念)倾向于只提供一种方式,并在需要时让代码使用 mov-immediate 在另一个寄存器中创建一个常量。
乱序执行通常可以隐藏mov-immediate 的成本,将常量放入另一个寄存器。这样的指令独立于其他所有指令,只要在它被调度到的执行端口上有一个空闲周期就可以执行。 (然而,前端通常是一个真正的瓶颈,静态代码大小确实很重要,所以很遗憾它不是免费的。)
脚注 1:RISC 的想法对于 P6 微架构来说是一件大事,最引人注目的是将 x86 指令解码为 1 或更多微指令的革命性想法,用于其类似 RISC 的后端,允许输出例如,一条内存目标指令(加载/ALU/存储)的不同部分的按顺序执行。
但在较小的决策中也是如此,例如 P6 没有硬件支持来维持一条指令的微指令之间的 TLB 一致性。这就是为什么adc %reg, (mem) 需要比您在英特尔 CPU 上预期的更多的微指令。 Andy Glew(从事 P6 工作的英特尔架构师)解释说,在 Stack Overflow cmets(我在 this answer 中引用)中,包括说“我加入 P6 时是 RISC 支持者,我的态度是“让 SW(微代码)去做”。'
很容易看出这种态度如何扩展到 x86 ISA 设计,并且只提供了cmov 的最低限度的形式。 (几乎不需要 8 位;您始终可以移动整个寄存器,并且您 often want to avoid partial registers in high-performance code 无论如何,因为可能会出现停顿。这在 PPro 上比在后来的 P6(如 Core 2)上更昂贵。Sandybridge 家族进行了部分寄存器合并更便宜。)
但这纯粹是我对可能影响该设计决策的因素的猜测。
必须权衡添加晶体管以解码 cmov 的 imm8、imm32 和/或 r/m8 编码的成本(功率和芯片面积以及可实现的时钟速度)期望代码能够使用它而在现实世界中加速。以及未来使用更多操作码编码空间的成本。
除了编码空间的未来成本(让 MMX 和 SSE1 指令只有 2 字节的操作码)之外,英特尔可能会通过省略 cmov $sign_extended_imm8, %reg 来猜错,这实际上经常很有用。
它的使用较少,因为它仅在计算条件两边的结果并选择一个条件的成本较低时才有用,而不仅仅是分支并只做一个。它作为一种优化很有用,尤其是当编译器预计分支预测不佳时。 Purpose of cmove instruction in x86 assembly?
关于控制依赖(分支)与数据依赖(cmov)的更一般的 cpu 架构背景:difference between conditional instructions (cmov) and jump instructions
请参阅Conditional move (cmov) in GCC compiler re: 当 GCC 将 if-conversion 转换为无分支 asm 时。
如果你做错了 (gcc optimization flag -O3 makes code slower than -O2),使用 cmov 甚至会受到伤害,因为分支预测可以非常准确地预测(例如,在排序输入数据的特殊情况下)。
在具有更短/更窄管道和更小的乱序执行资源(因此错误预测的成本更低)的旧 CPU 上,CMOV 在更少的情况下有用。特别是在 Broadwell 之前的 Intel 上,它需要 2 微秒而不是 1 微秒。Linux Torvalds 解释了为什么它在很多常见情况下都很糟糕,早在 2007 年就在 Core 2 CPU 上进行了一些测试:https://yarchive.net/comp/linux/cmov.html
但是,如果您编写的代码根据条件从几个值中进行选择,那么编译器生成它的情况当然并不罕见。 Clang 的启发式倾向于使用比 GCC 更多的 cmov,即更积极的 if 转换到无分支。
请注意,setcc 也不会经常使用,除非您经常查看返回布尔值的非内联版本的函数。
我在我的 Arch Linux 桌面上反汇编了libperl.so(刚刚选择了一个随机的大型二进制文件),由 GCC 10.1.0 编译。在总共 377835 条指令中 (objdump -d | egrep ' +[0-9a-f]+:'| wc -l):
-
setcc出现1783次,经常在setxx a/setxx b/or a,b多条件下做一个分支。
-
cmovcc 出现了 1737 次。 objdump -drwC -Mintel /usr/lib/perl5/5.32/core_perl/CORE/libperl.so | egrep 'cmov[a-z]+ ' | wc