Простой парсер, у которого заканчивается память

Я хотел бы понять, почему у этого простого парсера заканчивается память для больших файлов. Я действительно не понимаю, что я делаю неправильно.

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

и профиль его, вы найдете, что использование памяти еще растет неограниченно. (И когда я преобразовал ваш код для использования инкрементного чтения со строгим ByteStrings, это не имело никакого значения.)

Я считаю, что проблема заключается в следующем: потому что Attoparsec делает автоматический возврат, он должен быть готов к parseAll (ваша версия или моя-это не имеет значения) для использования следующим образом:

(parseAll <* somethingThatDoesntMatch) <|> parseDifferently

если parseAll проанализировал полмиллиона строк и дошел до конца, somethingThatDoesntMatch заставит его вернуться к началу, а затем переделать все с parseDifferently. Таким образом, метаинформация для отслеживания и сами ByteStrings не могут быть освобождены до тех пор, пока анализ не будет полностью завершен.

теперь, ваш парсер (и мой пример выше), "очевидно" не нужно будет возвращаться таким образом, но Attoparsec не выводит это.

Я могу придумать несколько способов сделать это:

  1. если вы анализируете мегабайты вместо гигабайт, рассмотрите возможность использования Parsec, который возвращается только тогда, когда он сделан явным (например, используя try).
  2. разбейте файл журнала на строки (или блоки строк) с помощью ручного анализатора без обратного отслеживания и запустите Attoparsec парсер до завершения на каждой строке / блоке.

если вы посмотрите на документация attoparsec вы заметите, что есть аналогичный пример, и он сопровождается следующим комментарием:

обратите внимание на перекрытие Парсеры anyChar и string "-->". Хотя это будет работать, это не очень эффективно, так как это вызовет много отступлений.

используя альтернативу anyChar который отклоняет символы, принятые endOfLine должен устранить проблему. Е. Г.

satisfy (\c -> c `notElem` ['\n', '\r'])