[英]GHC optimization
下面的兩個Haskell函數似乎不同,索引變量是隱式還是顯式,但性能差異是兩個數量級。
此函數大約需要0.03秒來計算mfib 30:
let mfib = (map fib [0..] !!)
where
fib 0 = 0
fib 1 = 1
fib x = mfib (x-1) + mfib (x-2)
對於mfib 30,此功能大約需要3秒鍾:
let mfib i = map fib [0..] !! i
where
fib 0 = 0
fib 1 = 1
fib x = mfib (x-1) + mfib (x-2)
我猜它與GHC內聯規則有關,並且一直在嘗試添加內聯/無內置編譯指示以獲得匹配的性能。
編輯:我理解如何查找惰性列表可以用來記憶fib函數以及為什么傳統的fib定義非常慢。 我期待memoization在第二個函數以及第一個函數中工作,並且不理解為什么它不是。
在查看desugared代碼時,更容易理解這些差異,因此這里有兩個函數的部分脫落版本。
let mfib = let fib 0 = 0
fib 1 = 1
fib x = mfib (x-1) + mfib (x-2)
in (!!) (map fib [0..])
與
let mfib = \i ->
let fib 0 = 0
fib 1 = 1
fib x = mfib (x-1) + mfib (x-2)
in map fib [0..] !! i
請注意,在第二個程序中,表達式map fib [0..]
出現在\\i -> ...
,因此它(通常,沒有優化)將針對i
每個值進行評估。 請參閱GHC Haskell中何時自動記憶?
不,這與內聯無關。 區別在於mfib = (map fib [0..] !!)
沒有參數。 它當然仍然是一個函數,但是預先評估該函數不需要傳遞任何參數。 特別是,評估此mfib
將以一種可以為所有索引重用的方式生成fib
列表。
OTOH, mfib i = map fib [0..] !! i
mfib i = map fib [0..] !! i
意思是當你實際傳遞一個參數i
時,只會考慮整個where
塊。
如果您反復多次評估函數,則兩者僅相同。 不幸的是,對於第二個版本,函數自己的遞歸已經一次又一次地調用它! 所以mfib (x-1) + mfib (x-2)
然后需要完成mfib (x-1)
的整個工作, 然后再完成mfib (x-2)
的全部工作。 所以mfib n
花費兩倍以上的計算成本mfib (n-1)
因此mfib
∈O(2 N)。
這是非常浪費的,因為mfib (x-2)
中的大部分術語也已經在mfib (x-1)
並且可以簡單地重復使用。 嗯,這正是你的第一個版本所做的,因為它計算一次和所有索引的fib
列表,所以評估mfib (x-1)
已經完成了大部分工作,然后可以簡單地由mfib (x-2)
重新讀取mfib (x-2)
,將復雜度降低到多項式。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.