Ленивый анализ больших файлов с миллионами точек данных
У меня есть парсер, который я пытаюсь написать, и я просмотрел несколько его версий, и я не могу уменьшить использование памяти. Я пытаюсь проанализировать дампы Википедии 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