我在 Haskell 中编写了一些简单的字符计数例程,将统计信息存储在一个新的数据类型中:
data Stat = Stat {
stChars :: !Int,
stVowels :: !Int,
stPairEL :: !Int,
stWords :: !Int
}
我在成百上千个纯文本文件上运行它,每个文件大约 50K--100K。
tabulateFile :: FilePath -> IO Stat
tabulateFile path = do
putStrLn path
contents <- L.readFile path
return $! tabulateText ' ' contents defaultStat
我没有使用左折叠,而是使用原始递归,这样我就可以保留前一个字符。
tabulateText :: Char -> L.ByteString -> Stat -> Stat
tabulateText lastChr bs stat =
case U.uncons bs of
Nothing -> stat
Just (chr, newBs) ->
tabulateText lchr newBs (countChar lastChr lchr stat)
where lchr = toLower chr
{-# INLINE countChar #-}
countChar :: Char -> Char -> Stat -> Stat
countChar !lastChr !chr !(Stat stChars stVowels stPairEL stWords) =
Stat
(stChars + 1)
(stVowels + (countIf $ isVowel chr))
(stPairEL + (countIf (lastChr == 'e' && chr == 'l')))
(stWords + (countIf ((not $ isLetter lastChr) && isLetter chr)))
isVowel :: Char -> Bool
isVowel c = Set.member c vowels
vowels = Set.fromAscList ['a', 'e', 'i', 'o', 'u', ...] -- rest of vowels elided
现在,它的速度是 running 的两倍多cat * | wc
,但我的直觉告诉我,文件 I/O 应该远远超过所需的 CPU 时间。简单地使用cat * | wc
大约 20MB/s 的进程和热缓存,但使用我的 Haskell 程序(用 编译-O
)运行速度低于 10MB/s,即使经过一些基本优化也是如此。分析告诉我,大部分时间都花在了tabulateText
和countChar
.
有什么我可以在这里优化的东西吗?