为什么x86只有一种形式的条件移动,而不是立即数或8位?

问题描述

我注意到Conditional Move指令的扩展性不如正常的mov。例如,它不支持立即数,也不支持寄存器的低字节。

出于好奇,为什么Cmov命令比一般的mov命令更具限制性?例如,为什么为什么都不都允许这样的东西:

mov    $2,%rbx    # allowed
cmovcc $1,%rbx    # I suppose setcc %bl could be used for the '1' immediate case

作为旁注,我在使用Compiler Explorer时注意到cmovcc的使用量远少于jmpccsetcc。通常是这种情况吗?如果是,为什么使用频率比其他条件少?

解决方法

由于是有条件的,它仅需要cmov r,r/m形式的16种不同的操作码,就ccjcc的每种不同的setcc条件而言就需要一个操作码(同义词共享一个操作码,当然)。

因此,即使还有“空间”可容纳另外16个0F xx操作码,当英特尔将其添加到奔腾Pro中时,花所有这些编码空间可能也不值得。好吧,也许是一个符号扩展的imm8形式。那将为其他新的操作码腾出空间,例如Intel可能已经开始设计的MMX和SSE指令,或者至少在P6的ISA扩展定稿时至少考虑使用Pentium-MMX和Pentium III。

当您根本不需要cmov(通常有条件地将某些内容归零)时,大多数情况下,imm8格式会很有用,但这不是不必要。 RISC哲学(Intel曾在P6 1 中使用过)希望仅提供一种方法,并且如果需要的话,让代码使用mov-immediate在另一个寄存器中创建一个常量。

无序的exec经常可以隐藏立即mov的开销以将常量放入另一个寄存器。这样的指令与其他所有指令无关,并且可以在预定的执行端口上有空闲周期时立即执行。 (但是,前端通常是一个真正的瓶颈,静态代码大小确实很重要,因此不幸的是它不是免费的。)

脚注1 :对于P6微体系结构而言,RISC思想是一件大事,其中最引人注目的是将x86指令解码为1或更多微指令的革命性思想,因为其类似于RISC的后端,从而允许-例如,一条存储目标指令(装入/ ALU /存储)的不同部分的有序执行。

但是在较小的决策中,例如P6不支持在一条指令的微字之间保持TLB一致性的硬件支持。这就是adc %reg,(mem)需要比Intel CPU预期更多的uops的原因。 Andy Glew(致力于P6的英特尔架构师)解释说,在Stack Overflow注释中(我在this answer中引用了该注释),包括说“我加入P6时是RISC的支持者,我的态度是“让SW(微码)做到这一点。”

很容易看出这种态度如何扩展到x86 ISA设计,并且仅提供了cmov的最小形式。 (几乎不需要8位;您始终可以移动整个寄存器,而且由于可能出现停顿,因此您随时often want to avoid partial registers in high-performance code。在PPro上,其成本甚至比Core 2更高的P6更高。注册更便宜的合并。)

但这纯粹是我推测哪些因素可能影响该设计决策。


添加晶体管以对cmov的imm8imm32和/或r/m8编码进行解码的成本(功耗和管芯面积以及可实现的时钟速度)将达到必须权衡预期的实际使用代码的速度。以及未来使用更多的操作码编码空间的成本。

除了将来的编码空间成本(使MMX和SSE1指令仅具有2字节的操作码)之外,英特尔可能会通过省略cmov $sign_extended_imm8,%reg来猜错这一点,而实际上这是相当有用的。 / p>


它使用较少,因为它仅在便宜计算条件双方的结果并选择一个条件时才有用,而不仅仅是分支而只做一个。作为优化,它很有用,尤其是在编译器期望分支预测不佳时。 Purpose of cmove instruction in x86 assembly?

关于控制依赖性(分支)与数据依赖性(cmov)的更一般的cpu体系结构背景:difference between conditional instructions (cmov) and jump instructions

请参阅Conditional move (cmov) in GCC compiler回复:GCC将if转换为无分支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

但是,如果您编写基于条件从几个值中进行选择的代码,则看到编译器生成它肯定不是很罕见。与GCC相比,Clang的启发式方法倾向于使用更多的cmov,即,如果将其转换为无分支,则更具攻击性。


请注意,setcc也不会被大量使用,除非您经常查看返回布尔值的函数的非内联版本。

我在由GCC 10.1.0编译的Arch Linux桌面(只是选择了一个随机的大二进制文件)上反汇编了libperl.so。总共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

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...