use*_*068 7 haskell memory-leaks attoparsec
我想了解为什么这个简单的解析器用于大文件的内存不足.我真的很无知我做错了什么.
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"
Run Code Online (Sandbox Code Playgroud)
我正在运行该程序:
runhaskell.exe test.hs x.log
Run Code Online (Sandbox Code Playgroud)
输出:
test.hs: Out of memory
Run Code Online (Sandbox Code Playgroud)
x.log的大小约为500MB.我的机器有16GB的RAM.
我对 Attoparsec 不太熟悉,但我认为单独使用它来解析恒定内存中的大文件可能会很困难。如果将顶级解析器替换parseAll
为:
parseAll :: Parser ()
parseAll = skipMany anyChar
Run Code Online (Sandbox Code Playgroud)
并对其进行分析,您会发现内存使用量仍然无限制地增长。(当我将您的代码转换为使用带有 strict 的增量读取时ByteString
,它没有任何区别。)
我相信问题是这样的:因为 Attoparsec 会自动回溯,所以它必须准备好parseAll
(你的版本或我的版本 - 没关系)这样使用:
(parseAll <* somethingThatDoesntMatch) <|> parseDifferently
Run Code Online (Sandbox Code Playgroud)
如果parseAll
已经解析了 50 万行并到达末尾,somethingThatDoesntMatch
将导致它一路回溯到开头,然后使用 重新解析所有内容parseDifferently
。因此,在解析完全完成之前,无法释放用于回溯的元信息和字节字符串本身。
现在,您的解析器(以及我上面的示例)“显然”不需要以这种方式回溯,但 Attoparsec 不会推断出这一点。
我可以想出几种方法来继续:
try
)。