尾遞歸能否起到優(yōu)化作用跟編譯器有關(guān)系,并不是用了尾遞歸就一定能起到優(yōu)化作用。
定義:函數(shù)里的最后一個動作是返回一個函數(shù)的調(diào)用結(jié)果的情形,即最后一步新調(diào)用的返回值直接被當前函數(shù)的返回結(jié)果。
假設有遞歸函數(shù) func,其最后的return語句有以下兩種寫法:
// 第一種
return x * func(n-1);
// 第二種(尾調(diào)用)
return func(n-1);
對于第一種情況,要求出func(n)必須求出func(n-1),也就是說在調(diào)用道最后一層之前,之前的調(diào)用必須壓棧存儲。這樣如果棧太深會導致StackOverflow。
若采用第二種尾調(diào)用的方式,由于 return 語句里函數(shù)調(diào)用結(jié)果即為本函數(shù)的調(diào)用結(jié)果,在執(zhí)行func(n-1)時可以(可以不是一定)不用將func(n)的棧楨保存起來,這樣就可以避免以上StackOverflow異常。
尾遞歸能否起到優(yōu)化作用跟編譯器有關(guān)
尾遞歸語法只是具備了優(yōu)化的條件,至于會不會優(yōu)化要看編譯器是不是真的會優(yōu)化,也就是說對于不同語言,甚至同一種語言不同配置結(jié)果都是不一樣的。
以C語言函數(shù)為例:
int tail_func(int n, int res){
if (n <= 1) return res;
return tail_func(n - 1, n * res);
}
不開啟編譯優(yōu)化 gcc -o tr func_tail.c 后得到如下匯編代碼
.LFB3:
pushq %rbp
.LCFI3:
movq %rsp, %rbp
.LCFI4:
subq $16, %rsp
.LCFI5:
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
cmpl $1, -4(%rbp)
jg .L4
movl -8(%rbp), %eax
movl %eax, -12(%rbp)
jmp .L3
.L4:
movl -8(%rbp), %eax
movl %eax, %esi
imull -4(%rbp), %esi
movl -4(%rbp), %edi
decl %edi
call tail_func
movl %eax, -12(%rbp)
.L3:
movl -12(%rbp), %eax
leave
ret
開啟編譯優(yōu)化后得到如下匯編代碼
tail_func:
.LFB13:
cmpl $1, %edi
jle .L8
.p2align 4,,7
.L9:
imull %edi, %esi
decl %edi
cmpl $1, %edi
jg .L9
.L8:
movl %esi, %eax
ret
注意第10行jg .L9,沒有將內(nèi)部函數(shù)當作一個調(diào)用,而是采用跳轉(zhuǎn)的方式,其本質(zhì)就是:內(nèi)部調(diào)用與本身用了同一個棧楨。這就是尾遞歸帶來的性能優(yōu)化
延伸-尾調(diào)用
尾遞歸之所以能帶來性能上的優(yōu)化,本質(zhì)上是因為采用了尾調(diào)用。也就是說只要是尾調(diào)用,都可以實現(xiàn)這種優(yōu)化,原理同上。