优化Haskell文本处理

erj*_*ang 2 optimization haskell nlp

我正在Haskell中编写一些简单的字符计数例程,将统计信息存储在一个新的数据类型中:

data Stat = Stat {
    stChars    :: !Int,
    stVowels   :: !Int,
    stPairEL   :: !Int,
    stWords    :: !Int
}
Run Code Online (Sandbox Code Playgroud)

我正在运行数百或数千个纯文本文件,每个文件大约50K - 100K.

tabulateFile :: FilePath -> IO Stat
tabulateFile path = do
  putStrLn path
  contents <- L.readFile path
  return $! tabulateText ' ' contents defaultStat
Run Code Online (Sandbox Code Playgroud)

我没有使用fold-left,而是使用原始递归,因此我可以保留前一个字符.

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
Run Code Online (Sandbox Code Playgroud)

现在,它的运行速度是运行速度的两倍多cat * | wc,但我的直觉告诉我,文件I/O应该超过良好余量所需的CPU时间.使用cat * | wc热缓存大约使用大约20MB/s的进程,但使用我的Haskell程序(编译-O)以低于10MB/s的速度运行,即使经过一些基本的优化.剖析告诉我,大部分时间花在tabulateTextcountChar.

有什么我想念的东西,我可以在这里进行优化吗?

编辑:完成文件粘贴到http://hpaste.org/74638

Don*_*art 10

您应该提供导入,以便有人可以编译代码.但是,这里有几件看起来很可能:

  • 编译-O2 -funbox-strict-fields(获得严格字段的​​好处)
  • tabulateText应该严格lastChr,和stat
  • Set.member似乎是一种非常昂贵的方法来进行相等比较.使用跳转表.

例如

isSpaceChar8 :: Char -> Bool
isSpaceChar8 c =
    c == ' '     ||
    c == '\t'    ||
    c == '\n'    ||
    c == '\r'    ||
    c == '\f'    ||
    c == '\v'    ||
    c == '\xa0'
Run Code Online (Sandbox Code Playgroud)

这将内联和优化很好.

不知道是什么countIf,但它糟透了.我怀疑它是一个if你返回0?怎么样:

Stat
   (a + 1)
   (if isVowel c then a + 1 else a)
   ...
Run Code Online (Sandbox Code Playgroud)

然后看看核心.