記憶體快取適合快速複用結果的場景。在遞迴求解過程中,原問題與子問題的求解思路一致,快取其上一步結果,能極大提高 使用遞迴的計算效能。本文以斐波那契數列求解,涉及go陣列,反射,函式變數,變長引數等技能點,適合新手入門。
需求分析
- 計算效能通常需要時間開銷,用到time包
- 在求解已知變數指向的函式名,採用反射reflect包
斐波那契數列的前幾個數字是這樣的:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233...
透過這些數字的排列規律總結出對應的計算公式:
F(1) = 0
F(2) = 1
...
F(n) = F(n-1) + F(n-2)
反射獲取函式名
func GetFunctionName(i interface{}, seps ...rune) string {
// 獲取函式名
fn := runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
// 用seps用進行分割
fields := strings.FieldsFunc(fn, func(sep rune) bool {
for _, s := range seps {
if sep == s {
return true
}
}
return false
})
if size := len(fields); size > 0 {
return fields[size-1]
}
return ""
}
時間開銷
引數f為函式變數,其簽名是一個斐波那契數列函式 func(int) int
func spend_time(n int, f func(int) int) {
start := time.Now()
num := f(n)
// time.Since方法與下面等價
// end := time.Now()
// consume := end.Sub(start).Seconds()
consume = time.Since(start)
fmt.Printf("Running function is: %s\n", GetFunctionName(f))
fmt.Printf("The %dth number of fibonacci sequence is %d\n", n, num)
fmt.Printf("It takes %f seconds to calculate the number\n\n", consume)
}
遞迴求解
下面為不使用快取和使用快取中間結果來求斐波那契數列求解的兩個函式
func no_cache(n int) int {
if n == 1 {
return 0
}
if n == 2 {
return 1
}
return no_cache(n-1) + no_cache(n-2)
}
func fibonacci(n int) int {
if n == 1 {
return 0
}
if n == 2 {
return 1
}
index := n - 1
// 讀取快取
if fibs[index] != 0 {
return fibs[index]
}
num := fibonacci(n-1) + fibonacci(n-2)
// 快取子問題的計算結果,避免重複計算
fibs[index] = num
return num
}
入口主體
package main
import (
"fmt"
"reflect"
"runtime"
"strings"
"time"
)
const MAX = 100
var fibs [MAX]int
func main() {
spend_time(30, no_cache)
spend_time(30, fibonacci)
}
效果比對
Running function is: main.no_cache
The 30th number of fibonacci sequence is 514229
It takes 0.007108 seconds to calculate the number
Running function is: main.fibonacci
The 30th number of fibonacci sequence is 514229
It takes 0.000001 seconds to calculate the number
結論
大量的重複遞迴計算堆積,最終導致程式執行緩慢
透過快取中間計算結果來避免重複計算,對遞迴程式效能有指數級的提高
本作品採用《CC 協議》,轉載必須註明作者和本文連結