如果一個函式 (function) 的最後行為是呼叫函式,我們稱這個最後行為為 tail call。
舉個例子,考慮到以下程式碼,函式 A
的最後行為是執行函式 B
要注意的是, tail call 的最後行為只允許呼叫函式,所以以下的程式碼就不是 tail call,因為他的最後行為是進行 2 * 1 + B(data) 的運算,而非單純呼叫函式。
Tail-call optimization,每當函式呼叫時,我們需要建立 stack frame 來紀錄函式的參數以及 return address,tail-call optimization 最主要的目地是避免建立過多的 stack frame 而造成 stack overflow,所以呼叫函式時,只記錄參數,接著跳到函式執行處開始執行。
如果一個函式的最後行為是呼叫自己本身,那我們稱這個最後行為為 tail recursion。Tail-call optimization 最常見的使用情境就是優化 tail-recursion 的函式。我們只要將 tail call 所呼叫函式的參數記錄下來接著跳到 caller 函式所在位置,即可執行。以下用兩個例子來說明 Tail-call optimization。
第一個範例是階層計算,以下階層計算程式碼符合 tail recursion,因此可以以 Tail-call optimization 來優化
分別比較使用以及不是用 tail-call optimization 產生之指令,頻繁的使用 load 和 store 指令,操作 stack frame 中的參數或 return address,顯然優化後的程式碼少了這些指令來操作 stack frame,效率較佳。
第二個範例是計算 fibonacci number,以下程式碼是典型利用遞迴來計算 fibonacci number,但以下程式碼並不符合 tail recursion,因為最後行為是兩個函式的回傳值相加
以下是已經優化過的指令,可以發現不符合 tail-call 的函式(無法利用 tail-call optimization),即使優化過,仍然不可避免的必須對 stack frame 進行 load 和 store 操作。
為了可以成功利用 tail-call optimization 優化,我們進一步將這個計算函式改為符合 tail recursion 的版本再進行優化,程式碼如下:
以下是已經優化過的指令,可以發現,相較典型利用遞迴來計算 fibonacci number 的寫法,tail recursion 版本的 fibonacci number 計算函式帶入的參數較多,可讀性也較差,然而利用 tail-call optimization 優化後的指令,明顯簡潔許多,可看出 tail-call optimization 顯著的優化。