Простой парсер, у которого заканчивается память
Я хотел бы понять, почему у этого простого парсера заканчивается память для больших файлов. Я действительно не понимаю, что я делаю неправильно.
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.размер журнала составляет около 500 МБ. Моя машина имеет 16GB ОЗУ.
2 ответов
Я не так хорошо знаком с Attoparsec, но я думаю, что вам может быть трудно использовать его в одиночку, чтобы проанализировать огромный файл в постоянной памяти. Если вы замените парсер верхнего уровня parseAll
С:
parseAll :: Parser ()
parseAll = skipMany anyChar
и профиль его, вы найдете, что использование памяти еще растет неограниченно. (И когда я преобразовал ваш код для использования инкрементного чтения со строгим ByteString
s, это не имело никакого значения.)
Я считаю, что проблема заключается в следующем: потому что Attoparsec делает автоматический возврат, он должен быть готов к parseAll
(ваша версия или моя-это не имеет значения) для использования следующим образом:
(parseAll <* somethingThatDoesntMatch) <|> parseDifferently
если parseAll
проанализировал полмиллиона строк и дошел до конца, somethingThatDoesntMatch
заставит его вернуться к началу, а затем переделать все с parseDifferently
. Таким образом, метаинформация для отслеживания и сами ByteStrings не могут быть освобождены до тех пор, пока анализ не будет полностью завершен.
теперь, ваш парсер (и мой пример выше), "очевидно" не нужно будет возвращаться таким образом, но Attoparsec не выводит это.
Я могу придумать несколько способов сделать это:
- если вы анализируете мегабайты вместо гигабайт, рассмотрите возможность использования Parsec, который возвращается только тогда, когда он сделан явным (например, используя
try
). - разбейте файл журнала на строки (или блоки строк) с помощью ручного анализатора без обратного отслеживания и запустите Attoparsec парсер до завершения на каждой строке / блоке.
если вы посмотрите на документация attoparsec вы заметите, что есть аналогичный пример, и он сопровождается следующим комментарием:
обратите внимание на перекрытие Парсеры
anyChar
иstring "-->"
. Хотя это будет работать, это не очень эффективно, так как это вызовет много отступлений.
используя альтернативу anyChar
который отклоняет символы, принятые endOfLine
должен устранить проблему. Е. Г.
satisfy (\c -> c `notElem` ['\n', '\r'])