对于训练我写了一个简短的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
答案 0 :(得分:1)
这看起来像数据结构问题而不是懒惰问题。严格的Text
本质上是一大块内存,而懒惰的Text
本质上是严格Text
s(&#34; chunks&#34;)的链表。将惰性Text
拆分成块的方式不应该是值的意义的一部分(这只是通过连接所有块获得的文本)。但它会对运营成本产生很大影响。
您有一堆short <> accu
形式的操作,其中accu
随着输出的大小而增长。如果这些是严格Text
s,则此并置必须将short
和accu
的全部内容复制到新的严格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只读取生成输出所需的行。因此,它不是读取整个文件,然后根据需要进行处理和写入,而是根据需要进行读取,写入和处理,从而消除等待整个文件在内存中读取的过程。