这是非常糟糕的未优化代码,因为您使用 -O0 编译(编译速度快,跳过大多数优化通道)。传统的堆栈框架设置/清理只是噪音。 arg 位于返回地址正上方的堆栈中,即函数入口处的 4(%esp)。 (另见How to remove "noise" from GCC/clang assembly output?)
令人惊讶的是,编译器使用 3 条指令通过移位和加法进行乘法运算,而不是 imull $34, 4(%esp), %eax / ret,除非针对旧 CPU 进行调整。 2 条指令是现代 gcc 和 clang 及其默认调优的截止点。参见例如How to multiply a register by 37 using only 2 consecutive leal instructions in x86?
但是这个可以使用 LEA 用 2 条指令来完成(不包括 mov 复制寄存器);代码很臃肿,因为你编译时没有优化。 (或者您针对旧 CPU 进行了调优,可能出于某种原因避免使用 LEA。)
我想你一定为此使用了 gcc;禁用其他编译器的优化总是只使用 imul 乘以非 2 的幂。但是我在 Godbolt 编译器资源管理器上找不到 gcc 版本 + 选项,它可以准确地提供您的代码。我没有尝试所有可能的组合。 MSVC 19.10 -O2 使用与您的代码相同的算法,包括两次加载 a。
使用 gcc5.5(它是最新的 gcc,不仅使用 imul,甚至使用 -O0)编译,我们得到类似您的代码的东西,但不完全一样。 (以不同的顺序进行相同的操作,并且不会从内存中加载两次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
在the Godbolt compiler explorer:gcc5.5 -m32 -O3 -fverbose-asm 上使用相同的旧 GCC 版本编译优化,我们得到:
# 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 或更高版本,我们得到了这种高效的 asm:imul-immediate 与内存源在现代 Intel CPU 上仅解码为单个微融合 uop,并进行整数乘法英特尔自 Core2 以来只有 3 个周期延迟,AMD 自 Ryzen 以来只有 3 个周期延迟。 (https://agner.org/optimize/)。
# gcc6/7/8 -m32 -O3 default tuning
func:
imull $34, 4(%esp), %eax #, a, tmp89
ret
但是对于 -mtune=pentium3,我们奇怪地没有获得 LEA。这看起来像是错过了优化。 LEA 在 Pentium 3 / Pentium-M 上有 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 添加到移位结果中。