我正在学习x86汇编器以编写一个编译器。特别是,我正在使用各种简单的递归函数,并通过不同的编译器(OCaml,GCC等)提供它们以便更好地理解不同编译器生成的汇编程序的种类。gcc -O2对递归斐波那契函数做什么?
我有一个简单的递归整数斐波那契功能:
int fib(int x) { return (x < 2 ? x : fib(x-1)+fib(x-2)); }
我的手编汇编程序是这样的:
fib:
cmp eax, 2
jl fin
push eax
dec eax
call fib
push eax
mov eax, [esp+4]
add eax, -2
call fib
add eax, [esp]
add esp, 8
fin:
ret
使用gcc -O2
产生这种编译该函数英特尔语法汇编神秘的代码:
_fib:
push edi
push esi
push ebx
sub esp, 16
mov edi, DWORD PTR [esp+32]
cmp edi, 1
jle L4
mov ebx, edi
xor esi, esi
L3:
lea eax, [ebx-1]
mov DWORD PTR [esp], eax
call _fib
sub ebx, 2
add esi, eax
cmp ebx, 1
jg L3
and edi, 1
L2:
lea eax, [esi+edi]
add esp, 16
pop ebx
pop esi
pop edi
ret
L4:
xor esi, esi
jmp L2
所以我猜callin g约定为[esp+4]
,返回值为eax
。它开始时按edi
,esi
和ebx
。然后它声称另一个16字节的堆栈帧,足够4个临时整数。然后从[esp+32]
读取edi
,这是参数。如果参数是<=1
那么它跳转到L4
,其在返回到L2
之前将(?)esi
清零,其设置eax=esi+edi
,其仅为参数edi
。如果参数是>1
那么参数将被复制到ebx
中,并且在落入L3
之前为零esi
。在L3
中,它设置eax=ebx-1
,并在递归计算fib(n-1)
之前将结果(n-1)存储在堆栈帧中的esp
。将结果添加到esi
,ebx
设置为n-2
,并且如果返回L3
,则返回ebx>1
,否则在下降到L2
之前提取edi
的较低位。
为什么这个代码如此复杂(例如,有没有一个名称为已经完成,我没有看到?)?
递归调用fib(n-2)
似乎已被替换为在esi
中积累的循环,但该调用不在尾部位置,所以这是如何完成的?
and edi, 1
的用途是什么?
mov DWORD PTR [esp], eax
的用途是什么?
为什么堆栈帧如此之大?
您可以将此算法反汇编回C以使其更清晰吗?
我的初步印象是GCC生成很差的x86汇编。在这种情况下,超过2 ×更多性能相同的代码(这两个程序在1.6GHz Atom上fib 3.40s(对于fib(40))。这是公平的吗?
更长的x86代码不一定意味着WORSE x86代码。实际上,一些较短的序列比较长的序列效率更低,时间更长。在你分析两个版本之前,不要敲GCC版本。 – 2012-04-07 21:32:01
你比较过运行时间吗? – 2012-04-07 21:32:13
事实上,GCC在优化方面非常出色;)代码看起来不太好,但这是x86“糟糕”实现的错误:P – BlackBear 2012-04-07 21:32:35