这是我的计算行数和单词的代码:
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)中的类似程序进行了比较。
为什么这段代码这么慢?可能是什么问题?
答案 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秒)。