List と ByteString の速度を比較する

List と ByteString を比較してみました。
以下のようなテキストファイルを読み込み、ファイル内に書かれた数字の文字列を数字に変換し、合計を算出するものです。

$ cat num.txt
127  69  76  70   1   1   1   0   0   0   0   0   0   0   0   0   2 
  0   3   0   1   0   0   0  64 158   4   8  52   0   0   0 212   0 
  6   0   0   0   0   0  52   0  32   0   9   0  40   0  31   0  28 
  0   6   0   0   0  52   0   0   0  52 128   4   8  52 128   4   8 
 32   1   0   0  32   1   0   0   5   0   0   0   4   0   0   0   3 
  0   0   0  84   1   0   0  84 129   4   8  84 129   4   8  19   0 
  0   0  19   0   0   0   4   0   0   0   1   0   0   0   1   0   0 
  0   0   0   0   0   0 128   4   8   0 128   4   8  40 178   5   0 
 40 178   5   0   5   0   0   0   0  16   0   0   1   0   0   0 224 
                   (snip)
  • List を使って集計。どちらもメモリをいっぱい使っている。
import Numeric

main = readFile "./num.txt" >>= print . sumFile
  where 
    sumFile     = sum .(map readDigit) . words
    readDigit s = (read s)::Integer

数字の要素は 625283 あります。

ghci> f<-readFile "./num.txt"
ghci> take 3 $ words f -- => ["127","69","76"]
ghci> length $ words f -- => 625283
$ time ./sum +RTS -K200M
52607898

real	0m46.554s
user	0m46.143s
sys	0m0.296s
  • ByteString を使ったもの
import Numeric
import qualified Data.ByteString.Lazy.Char8 as L

main = L.readFile "./num.txt" >>= print . sumFile
  where
    sumFile = sum . (map (toNumber.(L.readInteger))).(L.words)
    toNumber (Just (x,_)) = x
    toNumber Nothing      = 0
ghci> f <- L.readFile "./num.txt"
ghci> take 3 $ L.words f -- => [Chunk "127" Empty,Chunk "69" Empty,Chunk "76" Empty]
ghci> length $ L.words f -- => 625283
$ time ./sum +RTS -K200M
52607898

real	0m3.406s
user	0m3.276s
sys	0m0.108s

この例では圧倒的に ByteString が高速です。
正格版の Data.ByteString.Char8 にすると微妙ですが、さらに高速でした。

$ time ./sum +RTS -K200M
52607898

real	0m3.169s
user	0m3.036s
sys	0m0.132s

正格版の Data.ByteString.Char8 で L.readInteger を L.readIntにすると微妙に高速。

$ time ./sum +RTS -K200M
52607898

real	0m2.994s
user	0m2.924s
sys	0m0.072s

最後に遅延評価版の Data.ByteString.Lazy.Char8 で L.readInt の場合、信じられない遅さ。何これ?

import Numeric
import qualified Data.ByteString.Lazy.Char8 as L

main = L.readFile "./num.txt" >>= print . sumFile
  where
    sumFile = sum . (map (toNumber.(L.readInt))).(L.words)
    toNumber (Just (x,_)) = x
    toNumber Nothing      = 0

$ time ./sum +RTS -K200M
52607898

real	0m22.200s
user	0m22.089s
sys	0m0.112s

おまけ:Vectorも追加してみました。List に読み込んで fromList で変換していますので一手間多いのですが、結果は List と ByteString の中間くらい。

import Numeric
import qualified Data.Vector as V

main = readFile "./num.txt" >>= print . sumFile
  where 
    sumFile =  V.sum . V.map (\s->(read s)::Integer) . V.fromList . words

{-
$ time ./vector +RTS -K200M
52607898

real	0m21.279s
user	0m21.077s
sys	0m0.184s
-}
+---------------------------+-----------+----------------+
|  Type                     | Integer   |  Int           |
+---------------------------+-----------+----------------+
|Data.List                  | 0m46.554s | 0m46.157s      |
|Data.Vector                | 0m21.279s | 0m21.581s      |
|Data.ByteString.Lazy.Char8 | 0m3.406s  | 0m22.200s      |
|Data.ByteString.Char8      | 0m3.169s  | 0m2.994s       |
+---------------------------+-----------+----------------+