Ленивый анализ больших файлов с миллионами точек данных

У меня есть парсер, который я пытаюсь написать, и я просмотрел несколько его версий, и я не могу уменьшить использование памяти. Я пытаюсь проанализировать дампы Википедии sql и в этом примере взять файл записей страницы и выбросить их все в один гигантский вектор (13 миллионов страниц). Я получил это раньше, но я разбил файл на более мелкие части и собрал их из файловой системы. В любом случае, ниже моя попытка разобрать эти файлы за один проход. Я вытаскиваю линии "INSERT INTO ... (data),(data)..(data);", Я позволяю другим строкам просто не работать в парсере и не выдает значения. Строки вставки в среднем имеют размер 1 МБ и 8 000 записей.

Я попробовал то, что похоже на тысячу версий этого с deepseqs и выводом списков записей страниц в каждой строке вместо вектора, который я имею сейчас, и использую lineC с частичным парсером вместо linesUnboundedC. Я предполагаю, что должна быть какая-то концепция высокого уровня, которую я упускаю, которая сохраняет экспоненциальное использование памяти. Файл объемом 5 ГБ, и я с легкостью продуваю 16 ГБ памяти, но я не могу нигде остановить утечку памяти. Я хотел бы предположить, что этот код сможет хранить каждую строку независимо, а затем перейти без дополнительной памяти на следующую строку, но я не могу понять, что не так. У меня не было проблем с подсчетом количества записей или использованием более сложного моноида для получения данных из файла с использованием того же парсера.

Любая помощь высоко ценится!

{-# LANGUAGE OverloadedStrings #-}

module Parser.Helper where

import Conduit

import Control.DeepSeq
import Control.Monad (void)
import Control.Monad.Primitive

import Data.Attoparsec.Text
import Data.Maybe (catMaybes)
import Data.Text (Text)
import qualified Data.Vector as V
import Data.Vector.Generic (Vector)

import System.IO (openFile,IOMode(..))

data Page = Page !Int !Text !Bool
  deriving Show

instance NFData Page where
  rnf (Page pid title isRedirect) = pid `seq` title `seq` isRedirect `seq` ()

parseFile
  :: (Foldable t, Vector v e)
  => FilePath -> Parser (t e) -> IO [v e]
parseFile fp parser =
  do
    handle <- openFile fp ReadMode
    runConduit $ sourceHandle handle
      .| decodeUtf8LenientC
      .| peekForeverE linesUnboundedC
      .| vectors parser
      .| sinkList

vectors
  :: (Foldable t, Vector v e, MonadBase base m, Control.Monad.Primitive.PrimMonad base)
  => Parser (t e) -> ConduitM Text (v e) m ()
vectors parser =
  do
    vectorBuilderC (1024*1024)
      (\f ->
         peekForeverE $ do
           ml <- await
           case ml of
             Nothing -> return ()
             Just l ->
               case parseOnly parser l of
                 Left _ -> return ()
                 Right v -> mapM_ f v
      )

parsePageLine :: Parser (V.Vector Page)
parsePageLine =
  do
    string "INSERT INTO" *> skipWhile (/= '(')
    V.fromList . catMaybes <$> sepBy' parsePageField (char ',') <* char ';'

parsePageField :: Parser (Maybe Page)
parsePageField =
  do
    void $ char '('
    pid <- parseInt <* char ','
    namespace <- parseInt <* char ','
    title <- parseTextField <* char ','
    _ <- skipField <* char ','
    _ <- skipField <* char ','
    redirect <- parseInt <* char ','
    void $ sepBy' skipField (char ',')
    void $ char ')'
    let ret = case namespace == 0 of
          True -> Just $ Page pid title (redirect == 1)
          False -> Nothing
    return $ force ret

parseTextField :: Parser Text
parseTextField = char '\'' *> scan False f <* char '\''
  where
    f :: Bool -> Char -> Maybe Bool
    f False '\'' = Nothing
    f False '\\' = Just True
    f _ _ = Just False

skipField :: Parser ()
skipField = void $ scan False f
  where
    f :: Bool-> Char -> Maybe Bool
    f False '\\' = Just True
    f False ',' = Nothing
    f False ')' = Nothing
    f _ _ = Just False

parseInt :: Parser Int
parseInt = signed $ decimal

0 ответов

Другие вопросы по тегам