Haskell:Data.Text与Data.Text.Lazy Performance

时间:2017-01-06 08:28:56

标签: performance haskell

对于训练我写了一个简短的Haskell程序作为Perl脚本的替代品。 该程序读取包含多行消息的日志文件,并简单地将它们连接起来,以便为每条消息生成一行。 我的测试输入文件有14000行,大小为1 MB。 使用Data.Text的版本的运行时间为3.5秒,使用Data.Text.Lazy的版本仅为0.1秒(原始perl脚本需要0.2秒)。 我在其他帖子中发现使用Data.Text.Lazy只对大量数据有意义,并没有预料到这样的差异。 任何人都可以解释为什么或我的程序有什么问题?

源代码的相关部分(两个版本之间的唯一区别是导入Data.Text *):

{-# LANGUAGE OverloadedStrings #-}
module Main where
import Data.Char (isDigit)
import Data.Monoid ((<>))
import qualified Data.Text.Lazy    as T
import qualified Data.Text.Lazy.IO as T
import System.Environment (getArgs, getProgName)
import System.IO (openFile, stdin, stdout, Handle,
                  IOMode(ReadMode,WriteMode))

main :: IO ()
main = do
  (inp,out) <- getInput
  actLog <- T.hGetContents inp
  let newLog = processLog actLog
  T.hPutStr out newLog

processLog :: T.Text -> T.Text
processLog = foldr joinLines "" . T.lines

joinLines :: T.Text -> T.Text -> T.Text
joinLines elem accu
  | T.length elem == 0    = accu                     -- Blank Line
  | T.head elem   == ' '  = textElem <> accu         -- Continuation
  | isDigit (T.head elem) = "\n" <> textElem <> accu -- Start
  | otherwise             = accu                     -- Garbage
    where textElem = T.strip elem

2 个答案:

答案 0 :(得分:1)

这看起来像数据结构问题而不是懒惰问题。严格的Text本质上是一大块内存,而懒惰的Text本质上是严格Text s(&#34; chunks&#34;)的链表。将惰性Text拆分成块的方式不应该是值的意义的一部分(这只是通过连接所有块获得的文本)。但它会对运营成本产生很大影响。

您有一堆short <> accu形式的操作,其中accu随着输出的大小而增长。如果这些是严格Text s,则此并置必须将shortaccu的全部内容复制到新的严格Text值中。总运行时间必然是二次的。如果它们是惰性Text,则<>有另一个选项:它可以将short的块列表添加到accu的块列表中。这根本不需要触摸accu,但即使这样做,组成accu的链接列表的主干也可能比整个文本要少得多。 accu的内容,取决于块大小。这就是为什么你的懒惰Text版本速度要快得多。

看起来您可以用

形式编写程序
processLog = T.unlines . processLines . T.lines
processLines :: [T.Text] -> [T.Text]
processLines = ...

留下了如何连接到库函数T.unlines的问题,在这种情况下,无论使用严格Text还是惰性Text,都可以期望它有效。< / p>

答案 1 :(得分:0)

惰性和普通Data.Text之间的区别在于是否将整个文件读入

的内存中
actLog <- T.hGetContents inp 

处理时,惰性Haskell只读取生成输出所需的行。因此,它不是读取整个文件,然后根据需要进行处理和写入,而是根据需要进行读取,写入和处理,从而消除等待整个文件在内存中读取的过程。