浅谈尾递归的优化
在 淺談尾調(diào)用和尾遞歸 這篇博文中,我談了什么是尾遞歸以及編譯器如何優(yōu)化尾遞歸。這篇文章,咱來個具體的例子,通過匯編代碼來看看優(yōu)化和不優(yōu)化的區(qū)別。
求階乘的尾遞歸寫法
// file_name : factorial.c #include <stdio.h>int factorial_tail(int n, int product_from_n) {if (n == 1)return product_from_n;elsereturn factorial_tail(n - 1, n * product_from_n); }int main(void) {int result = factorial_tail(5,1);printf("%d\n",result); }實驗思路是將上面的源碼編譯為普通版本和優(yōu)化版本,通過對比2個版本的匯編代碼來了解編譯器如何優(yōu)化尾遞歸。
編譯為2個版本
gcc -S factorial.c -o factorial.s #普通版本 gcc -S factorial.c -O2 -o factorial_O2.s #優(yōu)化版本注:我的gcc版本是 4.8.4
對比文件
普通版本(未優(yōu)化)
為了說明問題,我刪除了一些偽指令,并加了些注釋:
factorial_tail:pushq %rbpmovq %rsp, %rbpsubq $16, %rspmovl %edi, -4(%rbp) // 傳遞參數(shù) nmovl %esi, -8(%rbp) // 傳遞參數(shù) product_from_ncmpl $1, -4(%rbp) //把參數(shù)n和1比較jne .L2 //不相等就跳轉(zhuǎn)到.L2movl -8(%rbp), %eax //相等的話 eax = product_from_njmp .L3 //跳轉(zhuǎn)到.L3 .L2:movl -4(%rbp), %eax // eax = nimull -8(%rbp), %eax // eax = product_from_n*nmovl -4(%rbp), %edx // edx = nsubl $1, %edx // edx--;movl %eax, %esi //esi = eax = (product_from_n*n)movl %edx, %edi //edi = edx = (n-1)call factorial_tail //遞歸調(diào)用 .L3:leave //leave和ret用于返回main函數(shù)retmain:pushq %rbpmovq %rsp, %rbpsubq $16, %rspmovl $1, %esimovl $5, %edicall factorial_tail可以看到,編譯器就是把C代碼翻譯成匯編了,沒有做什么優(yōu)化。遞歸依然是遞歸。
優(yōu)化版本
我整理后的代碼如下:
factorial_tail:cmpl $1, %edi //把edi(edi=參數(shù)n)和1比較movl %esi, %eax // eax = esi =product_from_nje .L2 //if(edi==1),則跳到.L2 .L3:imull %edi, %eax // eax = (edi * eax)= (n * product_from_n)subl $1, %edi // edi--; (即n--;)cmpl $1, %edi // edi和1相比較,即n和1相比較jne .L3 // if(edi != 1) 則跳轉(zhuǎn)到.L3 .L2:rep ret //返回主調(diào)函數(shù),返回值在eax中由此可見,編譯器把遞歸轉(zhuǎn)化成循環(huán)了。從第5行到第9行是典型的循環(huán)結(jié)構(gòu)。
再多說一句,最后一行的rep指令是什么意思呢?我搜到的結(jié)果如下。
Some AMD processors have a one cycle pipeline stall when a “ret” instruction is the target of a conditional jump or is immediately preceded by a conditional jump. To avoid this, gcc generates “rep; ret” in those cases intead. Since the “rep” prefix means nothing with the “ret” instruction, this effectively becomes a two byte “ret” instruction, and that is sufficient to avoid the pipeline bubble.
總結(jié)
- 上一篇: 关于 Intel 8253/8254
- 下一篇: 我的世界java加入更多床_《我的世界》