为什么 x86 只有一种形式的条件移动,而不是立即数或 8 位?
Why does x86 only have 1 form of conditional move, not immediate or 8-bit?
我注意到 Conditional Move 指令的可扩展性低于普通 mov
。比如不支持立即数,不支持寄存器的低字节。
出于好奇,为什么 Cmov
命令比一般的 mov
命令限制更多?例如,为什么不允许这样的事情:
mov , %rbx # allowed
cmovcc , %rbx # I suppose setcc %bl could be used for the '1' immediate case
附带说明一下,我注意到在使用 Compiler Explorer 时 cmovcc
的使用比 jmpcc
和 setcc
少得多。通常是这种情况吗?如果是,为什么它的使用频率低于其他条件?
作为条件,它已经需要 16 个不同的操作码,仅用于 cmov r, r/m
形式,每个不同的 cc
条件一个,就像 jcc
和 setcc
(同义词当然是共享操作码)。
因此,即使还有 16 个 0F xx
操作码的“空间”,当英特尔为 Pentium Pro 添加它时,花费所有编码 space 可能也不值得。好吧,也许是 sign-extended-imm8 表格。这会占用其他新操作码的空间,例如英特尔可能已经开始设计的 MMX 和 SSE 指令,或者至少在 P6 的 ISA 扩展完成时考虑用于 Pentium-MMX 和 Pentium III。
imm8 表格在大多数情况下当您完全想要 cmov
时很有用(通常有条件地将某些内容归零),但它不是 必需的 。 RISC 哲学(英特尔通过 P61 倾向于只提供一种方式,并让代码使用 mov-immediate 在需要时在另一个寄存器中创建常量。
Out-of-order exec 通常可以隐藏 mov
的成本——立即将常量放入另一个寄存器。这样的指令独立于其他所有指令,并且可以在其调度的执行端口上有空闲周期时立即执行。 (然而,front-end 通常是一个真正的瓶颈,静态 code-size 确实很重要,所以不幸的是它不是免费的。)
脚注 1:RISC 思想对于 P6 微体系结构来说是一件大事,最著名的是将 x86 指令解码为 1 或更多微指令的革命性思想 RISC-like back-end,允许 out-of-order 执行一条 memory-destination 指令的不同部分(加载/ALU/存储),例如。
但在较小的决策中也是如此,例如 P6 不支持跨一条指令的微指令保持 TLB 一致性的硬件支持。这就是为什么 adc %reg, (mem)
需要比您在 Intel CPUs 上预期更多的微指令。 Andy Glew(在 P6 上工作的英特尔架构师)在 Stack Overflow 评论(我在 this answer 中引用)中解释说,包括说 'I was a RISC proponent when I joined P6, and my attitude was "let SW (microcode) do it".'
很容易看出这种态度如何扩展到 x86 ISA 设计,并且只提供 cmov
的最低限度形式。 (几乎不需要 8 位;你总是可以移动整个寄存器,而且你 无论如何,因为可能出现停顿。这在 PPro 上比在后来的 P6(如 Core 2)上更昂贵。Sandybridge-family partial-register合并更便宜。)
但这纯粹是我对哪些因素可能影响该设计决策的推测。
添加晶体管以解码 imm8
、imm32
、and/or [=21] 的成本(功率和芯片面积,以及可实现的时钟速度) =] cmov 的编码必须权衡预期的 real-world 代码能够使用它的加速。 以及使用更多操作码编码的未来成本 space .
除了 coding-space 的未来成本(它让 MMX 和 SSE1 指令只有 2 字节的操作码),英特尔可能已经猜错了,省略了 cmov $sign_extended_imm8, %reg
这实际上是有用的相当频繁。
它使用较少,因为它仅在计算条件两边的结果和 select 一个条件的结果便宜时才有用,而不是仅仅分支并且只做一个。它作为一种优化很有用,尤其是当编译器期望分支预测不佳时。 Purpose of cmove instruction in x86 assembly?
关于控制依赖(分支)与数据依赖(cmov)的更一般的cpu-architecture背景:difference between conditional instructions (cmov) and jump instructions
参见 Conditional move (cmov) in GCC compiler 回复:当 GCC if-conversion 进入无分支汇编时。
使用 cmov
如果你做错了甚至会造成伤害 (),对于分支预测本可以非常准确地预测的情况(例如在排序输入数据的特殊情况下)。
在具有更短/更窄管道和更小 out-of-order 执行资源的旧 CPU 上(因此错误预测的成本更低),CMOV 在更少的情况下有用。特别是在 Broadwell 之前的 Intel 上,它需要 2 微指令而不是 1。Linux Torvalds 解释了为什么它在很多常见情况下都很糟糕,早在 2007 年就对 Core 2 CPU 进行了一些测试:https://yarchive.net/comp/linux/cmov.html
当然,编译器生成它的情况并不少见,但是,如果您根据条件编写 select 来自几个值的代码。 Clang 的启发式方法倾向于使用比 GCC 更多的 cmov,即更积极 if-conversion 到无分支。
请注意,setcc
也不会经常使用,除非您经常查看 non-inlined 版本的 return 布尔值的函数。
我在我的 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
我注意到 Conditional Move 指令的可扩展性低于普通 mov
。比如不支持立即数,不支持寄存器的低字节。
出于好奇,为什么 Cmov
命令比一般的 mov
命令限制更多?例如,为什么不允许这样的事情:
mov , %rbx # allowed
cmovcc , %rbx # I suppose setcc %bl could be used for the '1' immediate case
附带说明一下,我注意到在使用 Compiler Explorer 时 cmovcc
的使用比 jmpcc
和 setcc
少得多。通常是这种情况吗?如果是,为什么它的使用频率低于其他条件?
作为条件,它已经需要 16 个不同的操作码,仅用于 cmov r, r/m
形式,每个不同的 cc
条件一个,就像 jcc
和 setcc
(同义词当然是共享操作码)。
因此,即使还有 16 个 0F xx
操作码的“空间”,当英特尔为 Pentium Pro 添加它时,花费所有编码 space 可能也不值得。好吧,也许是 sign-extended-imm8 表格。这会占用其他新操作码的空间,例如英特尔可能已经开始设计的 MMX 和 SSE 指令,或者至少在 P6 的 ISA 扩展完成时考虑用于 Pentium-MMX 和 Pentium III。
imm8 表格在大多数情况下当您完全想要 cmov
时很有用(通常有条件地将某些内容归零),但它不是 必需的 。 RISC 哲学(英特尔通过 P61 倾向于只提供一种方式,并让代码使用 mov-immediate 在需要时在另一个寄存器中创建常量。
Out-of-order exec 通常可以隐藏 mov
的成本——立即将常量放入另一个寄存器。这样的指令独立于其他所有指令,并且可以在其调度的执行端口上有空闲周期时立即执行。 (然而,front-end 通常是一个真正的瓶颈,静态 code-size 确实很重要,所以不幸的是它不是免费的。)
脚注 1:RISC 思想对于 P6 微体系结构来说是一件大事,最著名的是将 x86 指令解码为 1 或更多微指令的革命性思想 RISC-like back-end,允许 out-of-order 执行一条 memory-destination 指令的不同部分(加载/ALU/存储),例如。
但在较小的决策中也是如此,例如 P6 不支持跨一条指令的微指令保持 TLB 一致性的硬件支持。这就是为什么 adc %reg, (mem)
需要比您在 Intel CPUs 上预期更多的微指令。 Andy Glew(在 P6 上工作的英特尔架构师)在 Stack Overflow 评论(我在 this answer 中引用)中解释说,包括说 'I was a RISC proponent when I joined P6, and my attitude was "let SW (microcode) do it".'
很容易看出这种态度如何扩展到 x86 ISA 设计,并且只提供 cmov
的最低限度形式。 (几乎不需要 8 位;你总是可以移动整个寄存器,而且你
但这纯粹是我对哪些因素可能影响该设计决策的推测。
添加晶体管以解码 imm8
、imm32
、and/or [=21] 的成本(功率和芯片面积,以及可实现的时钟速度) =] cmov 的编码必须权衡预期的 real-world 代码能够使用它的加速。 以及使用更多操作码编码的未来成本 space .
除了 coding-space 的未来成本(它让 MMX 和 SSE1 指令只有 2 字节的操作码),英特尔可能已经猜错了,省略了 cmov $sign_extended_imm8, %reg
这实际上是有用的相当频繁。
它使用较少,因为它仅在计算条件两边的结果和 select 一个条件的结果便宜时才有用,而不是仅仅分支并且只做一个。它作为一种优化很有用,尤其是当编译器期望分支预测不佳时。 Purpose of cmove instruction in x86 assembly?
关于控制依赖(分支)与数据依赖(cmov)的更一般的cpu-architecture背景:difference between conditional instructions (cmov) and jump instructions
参见 Conditional move (cmov) in GCC compiler 回复:当 GCC if-conversion 进入无分支汇编时。
使用 cmov
如果你做错了甚至会造成伤害 (
在具有更短/更窄管道和更小 out-of-order 执行资源的旧 CPU 上(因此错误预测的成本更低),CMOV 在更少的情况下有用。特别是在 Broadwell 之前的 Intel 上,它需要 2 微指令而不是 1。Linux Torvalds 解释了为什么它在很多常见情况下都很糟糕,早在 2007 年就对 Core 2 CPU 进行了一些测试:https://yarchive.net/comp/linux/cmov.html
当然,编译器生成它的情况并不少见,但是,如果您根据条件编写 select 来自几个值的代码。 Clang 的启发式方法倾向于使用比 GCC 更多的 cmov,即更积极 if-conversion 到无分支。
请注意,setcc
也不会经常使用,除非您经常查看 non-inlined 版本的 return 布尔值的函数。
我在我的 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