我想理解为什么这个简单的解析器对大文件的内存不足。我真的很无知,我做错了什么。
import Data.Attoparsec.ByteString.Char8
import qualified Data.Attoparsec.ByteString.Lazy as Lazy
import System.Environment
import qualified Data.ByteString.Lazy as B
import Control.Applicative
parseLine :: Parser String
parseLine = manyTill' anyChar (endOfLine <|> endOfInput)
parseAll :: Parser [Int]
parseAll = manyTill'
(parseLine >> (return 0)) -- discarding what's been read
endOfInput
main :: IO()
main = do
[fn] <- getArgs
text <- B.readFile fn
case Lazy.parse parseAll text of
Lazy.Fail _ _ _ -> putStrLn "bad"
Lazy.Done _ _ -> putStrLn "ok"
我正在使用:
运行该程序 runhaskell.exe test.hs x.log
输出:
test.hs: Out of memory
X.log的大小约为500MB。我的机器有16GB的RAM。
如果您查看attoparsec的文档,您会注意到有一个类似的示例,并且附有以下评论:
请注意重叠的解析器
anyChar
和string "-->"
。虽然这会起作用,但这并不是很有效,因为它会引起很多回溯。
使用anyChar
的替代方案,该替代拒绝endOfLine
接受的字符应解决问题。例如
satisfy (c -> c `notElem` ['n', 'r'])
我对attoparsec并不那么熟悉,但是我认为您可能很难单独使用它来在恒定内存中解析一个巨大的文件。如果您用以下方式替换顶级解析器parseAll
parseAll :: Parser ()
parseAll = skipMany anyChar
和个人资料,您会发现内存用法 stly stly 在没有界限的情况下生长。(当我将您的代码转换为严格的ByteString
s的增量读数时,它没有任何区别。)
我相信问题是:因为AttoparSec确实会自动回溯,因此必须为parseAll
(您的版本或我的版本或我的)做好准备:
(parseAll <* somethingThatDoesntMatch) <|> parseDifferently
如果parseAll
已解析了半百万行并达到末端,则somethingThatDoesntMatch
将使它一直回溯到开始,然后用parseDifferently
恢复所有内容。因此,在解析完全完成之前,无法释放用于回溯和字节的元信息。
现在,您的解析器(和上面的我的示例)"显然"不需要以这种方式回溯,但是Attoparsec不会推断出来。
我可以想到几种继续前进的方法:
- 如果您是在解析Megabytes而不是Gigabytes,请考虑使用PARSEC,仅在明确时回溯(例如,使用
try
)。 - 使用手工制作的,非折线的解析器将日志文件分解为行(或行块),然后运行您的Attoparsec解析器以在每行/块上完成。