Real World Haskell 4.関数プログラミング 4.6 ループをどのように考えるか
末尾再帰が何がそんなに騒ぐほどのことなのか
関数型言語はループがないので再帰関数を使用しますが、再帰適用ごとにメモリを割り当てるのは明らかに不利。
関数型言語では末尾再帰が使われているかどうかを検出し、末尾再帰最適化(TCO)を行なう。
-- フィボナッチ数を再帰で求める fibo 0 = 1 fibo 1 = 1 fibo n = fibo (n - 1) + fibo (n - 2) main = putStrLn $ show $ fibo 30
./recursion +RTS -s recursion.exe +RTS -s 1346269 151,314,228 bytes allocated in the heap 119,308 bytes copied during GC 3,096 bytes maximum residency (1 sample(s)) 16,064 bytes maximum slop 1 MB total memory in use (0 MB lost due to fragmentation) Generation 0: 288 collections, 0 parallel, 0.00s, 0.00s elapsed Generation 1: 1 collections, 0 parallel, 0.00s, 0.00s elapsed INIT time 0.00s ( 0.00s elapsed) MUT time 0.27s ( 0.27s elapsed) GC time 0.00s ( 0.00s elapsed) EXIT time 0.00s ( 0.00s elapsed) Total time 0.27s ( 0.27s elapsed) %GC time 0.0% (0.7% elapsed) Alloc rate 569,653,564 bytes per MUT second Productivity 100.0% of total user, 97.8% of total elapsed
-- フィボナッチ数を末尾再帰で求める fibo n = fibo' n 1 0 fibo' 0 a _ = a fibo' n a b = fibo' (n - 1) (a + b) a main = putStrLn $ show $ fibo 30
./tailCall +RTS -s tailCall.exe +RTS -s 1346269 8,712 bytes allocated in the heap 348 bytes copied during GC 1,452 bytes maximum residency (1 sample(s)) 10,836 bytes maximum slop 1 MB total memory in use (0 MB lost due to fragmentation) Generation 0: 0 collections, 0 parallel, 0.00s, 0.00s elapsed Generation 1: 1 collections, 0 parallel, 0.00s, 0.00s elapsed INIT time 0.02s ( 0.00s elapsed) MUT time 0.00s ( 0.00s elapsed) GC time 0.00s ( 0.00s elapsed) EXIT time 0.00s ( 0.00s elapsed) Total time 0.02s ( 0.00s elapsed) %GC time 0.0% (0.0% elapsed) Alloc rate 557,568 bytes per MUT second Productivity 0.0% of total user, 0.0% of total elapsed
フィボナッチは二重再帰なので呼ばれるごとに二ヶ所で再帰するので1,2,4,8,16と爆発的に再帰する。
1 から N までの和を求める方法ではさほどの差はなかった。
+RTS -s オプションでheapやらが出力されますが、意味はよく分ってません。数字が大きいのはなにかいっぱいメモリを使ったり、GCしているっぽい。