为什么这个简单的文本分析程序如此之慢?

时间:2012-04-02 14:33:36

标签: haskell

这是我的计算行数和单词的代码:

import System.IO
import Data.List
main = do
        hSetBinaryMode stdin True
        interact $ (\(w,l)->"line:"++(show l)++"\nwords:"++(show w)++"\n")
                   . foldl' (\(w,l) r-> w `seq` l `seq` (w+length r ,succ l) ) (0,0)
                   . lines

在大约100兆字节的文件上运行大约需要10秒钟。我将它与Lua(9s),awk(20s)和wc -l -c(0.6s)中的类似程序进行了比较。

为什么这段代码这么慢?可能是什么问题?

1 个答案:

答案 0 :(得分:15)

在Haskell中,已知String的I / O速度低于快速。通常必须将从句柄读取的字节转换为Unicode代码点,然后根据这些代码点构建链接列表。这是导致大量分配的大量工作。在这种情况下,转换为代码点有点简单,因为您将stdin设置为二进制模式,但链接字符列表的构造仍然需要很长时间。

另一个小因素是您的行计数使用Integer,但这很小,只有在I / O达到最高速度时才起到重要作用。

如果您需要快速I / O,则必须使用更适合的类型。一种可能性是使用ByteString,例如

import Data.List
import qualified Data.ByteString.Lazy.Char8 as C
main = do
        txt <- C.getContents
        putStrLn $ (\(w,l)->"line:"++(show l)++"\nwords:"++(show w)++"\n"). foldl' (\(w,l) r-> w `seq` l `seq` (w+C.length r ,succ l) ) (0,0) . C.lines $ txt

在我的盒子上以0.12秒的速度处理94MB文件(wc -l -c需要0.06秒),而使用String的原始文件花费了4.4秒。它可以进一步优化,

{-# LANGUAGE BangPatterns #-}
import Data.List
import qualified Data.ByteString.Lazy.Char8 as C
main = do
        txt <- C.getContents
        putStrLn $ (\(w,l)->"line:"++(show l)++"\nwords:"++(show w)++"\n"). loop 0 0 . C.lines $ txt

loop :: Int -> Int -> [C.ByteString] -> (Int,Int)
loop !w !l (ln:lns) = loop (w + fromIntegral (C.length ln)) (l+1) lns
loop w l _ = (w,l)

仅需0.08秒,这足以让我停止优化(String版本的类似更改会将时间降低到3.6秒)。