1. 程式人生 > >不要讓遞迴函式fuck大家的cpu

不要讓遞迴函式fuck大家的cpu

遞迴演算法是大學計算機課程裡面經常會講到的程式設計方法,因為採用這種方法寫出來的程式碼清晰易懂。

但是,在大多數程式設計規範裡面,會**嚴令禁止**使用遞迴函式,原因下面來詳細說明。

首先,由於邏輯錯誤,由直接或間接遞迴,造成遞迴呼叫無法結束(死遞迴),最後肯定會收到
一個"stack overflow"的宕機資訊。就暫且不論了。

下面要詳細討論的是,簡單的遞迴程式碼是如何fuck計算機執行時系統的。

這裡用計算Fibonacci(斐波納契)數列舉例。

由於在數學公式裡,Fibonacci數列就是一個遞迴定義:
        Fibonacci(0)=0
        Fibonacci
(1)=1 Fibonacci(n)=Fibonacci(n-1)+Fibonacci(n-2) (n>=2)
因而,最容易想到的就是用遞迴方法實現:
        func fibonacci(n uint64) uint64 {
            x := n
            if n > 1 {
                x = fibonacci(n-1) + fibonacci(n-2)
            }
            return x
        }
然而,這看似清晰簡單的遞迴函式,對計算機執行時系統而言,卻是毀滅性的災難。

由於每次函式呼叫,編譯器都會幫我們向執行棧push引數和返回地址,以便被呼叫函式可以
正確收到呼叫引數和返回。由於正常的函式呼叫層次一般不會太深,而且由於可能會有多線
程或協程(如:goroutine),為了節約資源,所以每一個執行單元的執行時棧一般不會設計
得無限大。

然而這看似簡單的遞迴呼叫,卻是用可能會無限大的引數N,讓執行時系統實現呼叫層次為
2N(N-1 + N-2)的函式呼叫佇列。這種情況,是任何設計靈活的執行時系統都無法承受
的計算模式。

由於go語言要實現goroutine,設計了可動態增長的執行時棧,但是在N=50的時候,以上
程式碼已經不能在可以忍受的時間內給出計算結果。

其實,所有看似只能用遞迴原語寫出的程式碼,都是可以通過一種可以被稱為stack的資料結構
替代,從而用for迴圈實現相同的邏輯。
以下是兩種用for迴圈實現計算Fibonacci函式的方法:
        func fibonacci2(n uint64) uint64 {
            f0, f1 := uint64(0), uint64(1)
            for i := uint64(2); i <= n; i++ {
                f0, f1 = f1, f0+f1
            }
            return f1
        }
        func fibonacci3(n uint64) uint64 {
            var stack Stack
            for
j := n - 1; j >= 2; j-- { stack.push(j) } f0, f1 := uint64(0), uint64(1) for _, ok := stack.pop(); ok; _, ok = stack.pop() { f0, f1 = f1, f0+f1 } return f1 }
這種通過普通for迴圈實現遞迴函式的方法,由於不需要增加函式呼叫層次,所以對N的大小
可以沒有任何限制。
實測N=10億,以上函式仍然可以在1ns內給出計算結果(當然已經被uint64截斷)。
以下是N=45的時候,以上三個函式的Benchmark結果:
        go test -test.bench=".*"
        N = 45
        testing: warning: no tests to run
        Benchmark_Recursive-4                  1        10018573100 ns/op
        Benchmark_Loop-4                2000000000               0.00 ns/op
        Benchmark_Stack-4               2000000000               0.00 ns/op
        PASS
        ok      github.com/vipally/glab/lab2    10.154s
可以顯見,遞迴函式對於計算機系統來說,該是怎樣的災難。
所以,有經驗的技術經理會在專案組裡嚴令禁止使用遞迴函式是明智的。
因此,不要貪圖一時方便,讓悄悄躲在某個角落的遞迴呼叫fuck大家的CPU。

我將以上測試程式碼放在這裡:
https://github.com/vipally/glab/blob/master/lab2/lab2_test.go
歡迎指教。