x86-64 下处理 32 位数值时,应选择多宽的寄存器?
结论
只要数值结果和后续行为都符合要求,通常优先使用 32 位操作数。它往往能缩短指令编码。对于普通寄存器加法和逻辑运算,现代主流 x86-64 处理器上的执行性能通常与 64 位形式相同。功耗和发热是否改善,需要在目标机器上测量。
具体收益取决于改动了哪些指令,以及它们是否处于性能瓶颈。不能仅凭“3/4 的指令可以改成 32 位”推算出固定的性能或功耗收益。
先确认两种写法确实等价
写入 eax 时,rax 的高 32 位会同时清零。这是指令本身的行为,无须额外执行零扩展指令。而写入 ax 或 al 会保留寄存器的其他位。参见 Intel 架构手册 Volume 1。
对于题目中的加法,如果两个输入的高 32 位均为零,而且数学意义上的和小于 2^32,以下两条指令会得到相同的完整 rax 数值:
; Intel syntax,两条指令是二选一的替代写法
add eax, edx
add rax, rdx
不过,数值相同还不足以保证程序行为相同。ADD 按操作数宽度设置标志位。例如,0x7fffffff + 1 在两种宽度下都能得到 0x0000000080000000,但 32 位形式的 SF 和 OF 为 1,64 位形式的这两个标志为 0。如果后续指令读取这些标志,就需要重新判断是否等价。Intel 的 ADD 指令定义说明了操作数宽度及标志位规则。
也不要把加法的结论直接套到所有指令上:移位计数的掩码、旋转宽度、乘除法的隐含操作数,以及有符号数的扩展要求,都可能随宽度改变。内存操作数从 64 位改成 32 位,还会改变实际读写的字节数。
代码大小:32 位形式经常更短
在传统 x86-64 编码中,普通整数指令通常默认使用 32 位操作数,64 位形式需要设置 REX.W。下面是这些寄存器加法的一种合法编码:
add eax, edx ; 01 D0 —— 2 字节
add rax, rdx ; 48 01 D0 —— 3 字节
add r8d, r9d ; 45 01 C8 —— 3 字节
add r8, r9 ; 4D 01 C8 —— 3 字节
前两条指令相差一个 REX 前缀字节。后两条都需要 REX 前缀来编码扩展寄存器,只是前缀中的位不同,长度相同。编码规则可查阅 Intel 指令手册中的 REX 和 ADD 定义。
涉及内存寻址时,基址或索引寄存器也可能使 REX 前缀成为必需。因此,改成 32 位不一定会少一个字节,需要查看实际生成的机器码。
较短的代码可能减少指令缓存和取指带宽的压力。但如果前端已经能高效供给热点代码,少几个字节未必能缩短运行时间。
执行性能:普通加法通常相同,其他指令要分别查
在许多现代 Intel 和 AMD 微架构上,寄存器形式的普通 ADD、AND、OR 等指令,32 位和 64 位形式通常具有相同的延迟与吞吐率。操作数宽度减半,并不意味着运算时间也减半。
乘法和除法需要单独检查。在一些处理器上,64 位整数除法明显比 32 位形式昂贵;部分处理器的乘法也存在宽度差异。具体情况应根据目标 CPU 查询指令数据,例如 Agner Fog 的指令测量表,并结合实际输入验证。
对于题目中的 add eax, edx 与 add rax, rdx,如果循环受加法依赖链或执行单元吞吐量限制,换成较短的编码后,速度仍可能基本相同。如果循环受取指或解码限制,代码缩小才更有机会带来收益。
功耗和发热:不能仅根据位宽判断
选择 32 位寄存器,不能证明处理器内部只有一半电路参与工作,也不能据此推导功耗减半。结果取决于执行单元设计、数据、频率,以及代码的实际瓶颈。
对于所有核心持续满负载的场景,应分别观察:
- 吞吐量:每秒完成多少实际工作。
- 持续功率:稳定运行时消耗多少瓦。
- 单位工作能耗:完成同样工作量消耗多少焦耳。
更快的版本可能在相近功率下完成更多工作;温度还会受散热和频率策略影响。仅比较温度,无法可靠判断哪种写法更节能。
如何为这段代码作出选择
- 逐条确认可替换指令的数值范围、标志位用途和隐含操作数,先保证语义成立。
- 默认选用满足需求的 32 位形式。需要完整 64 位结果、地址或有符号扩展语义时,保留相应的 64 位操作。
- 查看反汇编,确认哪些指令确实缩短了,以及是否引入了额外的转换或数据搬运。反汇编只能验证编码变化,不能证明性能收益。
- 在目标 CPU 上比较两个版本,保持输入、线程数和编译选项一致,重复测量真实热点。
- 按实际部署方式使用所有可用核心运行,待功率和温度稳定后,再比较吞吐量及单位工作能耗。
备注:内容仅供参考。
