IT練習ノート

IT関連で調べたこと(実際は嵌ったこと)を書いています。

ghciを使ってざっくり性能測定

カジュアルにghci上で性能測定をしようと思ったのですが、少し工夫が必要なようです。

ghciでは:set +sすることで性能を測ることができます。。

Haskell function execution time - Stack Overflow

ghci上での計測なので、表示する処理も計測に含まれしまいます。

Prelude> replicate 9999 1
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
途中省略
,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
(0.42 secs, 35,127,944 bytes)

表示する処理自体は除いて計測をしたいのですが、ghci上で表示させないことはできないようです。(そもそも、interactiveに処理結果を見ながら実行していくためのだからと思います。)

https://stackoverflow.com/questions/32926805/disable-printing-of-io-results-in-ghci

表示結果を無理やり無視すると、そもそも計測したい処理が、遅延評価のため、実行されません。

Prelude> (\x -> return ()) $ replicate 9999 1
(0.00 secs, 92,352 bytes)

強制的に評価をさせるために、deepseqパッケージを使います。

deepseq: Deep evaluation of data structures

Prelude> import Control.DeepSeq
(0.00 secs, 0 bytes)
Prelude Control.DeepSeq> deepseq (replicate 9999 1) (return ())
(0.13 secs, 648,528 bytes)

これで、おおよそ、評価したい処理の性能を見ることができる(はず)。

実際に性能を比較したかった処理は、リストを2つに分割する実装でした。型としては[a] -> ([a], [a])です。

一つはlengthを使って2つに分割します。これは、コード上リストを2回走査することになります。

Prelude Control.DeepSeq> splitHalf' xs = splitAt (length xs `div` 2) xs
(0.00 secs, 0 bytes)

もう一つは、リストの走査を1回で行う実装です。

Prelude Control.DeepSeq> first f (a,b) = (f a, b)
(0.00 secs, 0 bytes)
Prelude Control.DeepSeq> :{
Prelude Control.DeepSeq| 
Prelude Control.DeepSeq| splitHalf :: [a] -> ([a],[a])
Prelude Control.DeepSeq| splitHalf xs = go xs xs
Prelude Control.DeepSeq|   where
Prelude Control.DeepSeq|     go (y:ys) (_:_:zs) = first (y:) (go ys zs)
Prelude Control.DeepSeq|     go ys _ = ([],ys)
Prelude Control.DeepSeq| :}
(0.01 secs, 0 bytes)

2つを比較すると、1回の走査のほうが処理が速いと思ったのですが、結果は逆でした。

Prelude Control.DeepSeq> deepseq (splitHalf'  $ replicate 9999999 'a') (return ())
(1.58 secs, 1,200,091,064 bytes)
Prelude Control.DeepSeq> deepseq (splitHalf  $ replicate 9999999 'a') (return ())
(7.39 secs, 1,732,534,400 bytes)