这是一段不够优化的代码,因为你使用了
-O0
(快速编译,跳过大部分优化步骤)。传统的堆栈帧设置/清理只是噪音。参数在返回地址的正上方处于堆栈上,即在函数进入时在
4(%esp)
位置。(另请参见
如何从GCC / clang汇编输出中去除“噪音”?)
令人惊讶的是,编译器使用3条指令通过移位和加法来进行乘法运算,而不是使用
imull $34, 4(%esp), %eax
/
ret
,除非针对旧CPU进行调整。2条指令是现代gcc和clang默认调优的截止值。例如,请参见
如何使用两条连续的leal指令将寄存器乘以37?
但是,可以使用LEA的2条指令来实现这个目标(不包括用于复制寄存器的
mov
);代码膨胀是因为你没有进行优化编译(或者你调优的是旧的CPU,在那里可能有一些原因避免使用LEA)。
我认为您一定使用了gcc进行编译;使用其他编译器禁用优化时,总是使用
imul
来乘以非2次幂。但是我在Godbolt编译器资源管理器中找不到完全给出您的代码的gcc版本+选项。我没有尝试每种可能的组合。MSVC 19.10
-O2
使用与您的代码相同的算法,包括两次加载
a
。
使用gcc5.5进行编译(这是最新的gcc,即使在
-O0
下,也不仅仅使用
imul
),我们得到类似于您的代码的东西,但并不完全相同。(以不同的顺序执行相同的操作,并且不会两次从内存加载
a
)。
# gcc5.5 -m32 -xc -O0 -fverbose-asm -Wall
func:
pushl %ebp #
movl %esp, %ebp #, # make a stack frame
movl 8(%ebp), %eax # a, tmp89 # load a from the stack, first arg is at EBP+8
addl %eax, %eax # tmp91 # a*2
movl %eax, %edx # tmp90, tmp92
sall $4, %edx #, tmp92 # a*2 << 4 = a*32
addl %edx, %eax # tmp92, D.1807 # a*2 + a*32
popl %ebp # # clean up the stack frame
ret
在Godbolt编译器浏览器上
# gcc5.5 -m32 -O3. Also clang7.0 -m32 -O3 emits the same code
func:
movl 4(%esp), %eax # a, a # load a from the stack
movl %eax, %edx # a, tmp93 # copy it to edx
sall $5, %edx #, tmp93 # edx = a<<5 = a*32
leal (%edx,%eax,2), %eax # eax = edx + eax*2 = a*32 + a*2 = a*34
ret # with a*34 in EAX, the return-value reg in this calling convention
使用gcc 6.x或更高版本,我们可以获得这个高效的汇编代码:带有内存源的imul
-立即数只解码为现代英特尔CPU上的单个微融合uop,并且自Core2以来,整数乘法在英特尔和AMD Ryzen上只有3个周期的延迟。(https://agner.org/optimize/)。
func:
imull $34, 4(%esp), %eax
ret
但是使用
-mtune=pentium3
,我们没有得到LEA指令。看起来这是一次未成功的优化。在Pentium 3 / Pentium-M上,LEA指令的延迟只有1个时钟周期。
# gcc8.2 -O3 -mtune=pentium3 -m32 -xc -fverbose-asm -Wall
func:
movl 4(%esp), %edx # a, a
movl %edx, %eax # a, tmp91
sall $4, %eax #, tmp91 # a*16
addl %edx, %eax # a, tmp92 # a*16 + a = a*17
addl %eax, %eax # tmp93 # a*16 * 2 = a*34
ret
这与您的代码相同,但使用 reg-reg mov
而不是从堆栈重新加载来将 a
添加到移位结果中。
a
无关,只是设置了栈帧。8(%ebp)
表示的是a
。 - Jester