我想了解为什么这个简单的解析器会耗尽大文件的内存。我真的很无知我做错了什么。
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。
答案 0 :(得分:4)
如果你看the documentation of attoparsec,你会注意到有类似的例子,并附有以下评论:
请注意重叠的解析器
anyChar
和string "-->"
。虽然这会起作用,但效率不高,因为它会导致大量的回溯。
使用拒绝anyChar
接受的字符的endOfLine
替代方法应解决问题。 E.g。
satisfy (\c -> c `notElem` ['\n', '\r'])
答案 1 :(得分:3)
我对Attoparsec并不熟悉,但我认为你可能很难单独使用它来解析常量内存中的大文件。如果您将顶级解析器parseAll
替换为:
parseAll :: Parser ()
parseAll = skipMany anyChar
并对其进行分析,您会发现内存使用仍然无限制地增长。 (当我将您的代码转换为使用严格ByteString
s的增量读取时,它没有任何区别。)
我认为问题是这样的:因为Attoparsec会进行自动回溯,所以必须为parseAll
(您的版本或我的版本 - 无关紧要)做好准备,以便像这样使用:
(parseAll <* somethingThatDoesntMatch) <|> parseDifferently
如果parseAll
解析了50万行并到达结尾,somethingThatDoesntMatch
将导致它一直回溯到开头,然后用parseDifferently
重新解析所有内容。因此,在解析完成之前,无法释放回溯的元信息和ByteStrings本身。
现在,你的解析器(以及我上面的例子),“显然”不需要以这种方式回溯,但是Attoparsec并没有推断出这一点。
我可以想到几种方法:
try
)。