如何使用最少的指令数复制寄存器并执行`x * 4 + constant`

问题描述 投票:-1回答:1

我是x86汇编的新手。例如,以下指令:将ESP的内容乘以4,然后添加0x11233344,并将结果存储在EDI中。

如何用最少的指令数在x86汇编中表示此指令?

push esp
mov edi, 4
mul edi
add edi, 0x11233344
assembly x86 micro-optimization
1个回答
2
投票

您的asm没有任何意义(push esp复制到内存,而不是其他寄存器),并且mul edi写入EDX:EAX而不是edi。它会EDX:EAX = EAX * src_operand。阅读手册:https://www.felixcloutier.com/x86/MUL.html。更好的方法是改用imul,除非您实际上需要完整的32x32 => 64位乘法的上半部分输出。

[此外,除非您确切地知道自己在做什么,否则不要使用堆栈指针寄存器ESP来保存临时值(例如,您在用户空间中,并且确保没有信号处理程序可以异步使用堆栈” 。)堆栈指针* 4 +大常数是not普通程序会做的事情。


[通常,您可以在一条LEA指令中执行此操作,但是ESP是唯一在x86地址模式下不能作为索引的寄存器。请参见rbp not allowed as SIB base?(索引是寻址模式的一部分,可以应用2位移位计数,即比例因子)。

思考我们最好的选择仍然是将ESP复制到EDI,然后使用LEA:

 mov  edi, esp
 lea  edi, [edi * 4 + 0x11223344]

或者您可以使用LEA复制并添加,然后然后左移,因为我们添加的值的低位具有两个零(即,是4的倍数)。因此,我们可以将其右移2而不丢失任何位。

SHIFTED_ADD_CONSTANT equ 0x11223344 >> 2

  lea    edi, [esp + SHIFTED_ADD_CONSTANT]
  shl    edi, 2

左移之前的加法会产生进位到前2位,但是我们将把这些位移出,所以这里没有关系。

这也是2 oups,并且更多在AMD Bulldozer系列CPU上有效(GP整数mov不需要消除运动,并且缩放索引会花费LEA额外的延迟周期)。 Zen具有消除运动的功能,但我认为LEA延迟仍然相同,因此两个版本的延迟均为2个周期。甚至“复杂” LEA在Zen上的吞吐量为2 /时钟,对于简单LEA(任何ALU端口)的吞吐量为4 /时钟。

但是less在Intel IvyBridge和更高版本的CPU上效率很高,在这些CPU上mov可以以零延迟(消除移动)运行,并且[edi*4 + disp32]寻址模式仍然是快速的2分量LEA。因此,在具有消除运动功能的Intel CPU上,第一个版本是2个前端uops,1个用于执行单元的未融合域uop,并且只有1个延迟周期。

另一个2指令选项是使用较慢的imul而不是快速移位。 (寻址模式使用移位:即使将其写为* 1 / 2 / 4 / 8,也被编码在机器代码的2位移位计数字段中。)>

  imul  edi, esp, 4       ; this is dumb, don't use mul/imul for powers of 2.
  add   edi, 0x11223344

imul在现代x86 CPU上具有3个周期的延迟,这是相当不错的,但在像奔腾3这样的旧CPU上却较慢。对于mov + LEA,它仍然不如1或2个周期的延迟,并且imul在端口更少。

© www.soinside.com 2019 - 2024. All rights reserved.