懒惰解析具有数百万个数据点的大型文件

时间:2017-03-11 03:40:17

标签: parsing haskell conduit attoparsec

我有一个解析器,我正在尝试编写,而且我已经浏览了它的多个版本,而我似乎无法降低内存使用率。我试图解析维基百科sql转储,并在此示例中获取页面条目文件并将它们全部放在一个巨大的向量(1300万页)中。之前我有这个工作,但我将文件分成小块并从文件系统聚合它们。无论如何,下面是我尝试一次解析这些文件。我提出了"INSERT INTO ... (data),(data)..(data);"行。我让其他行在解析器中失败而不是屈服值。插入行的平均大小为1MB,条目为8k。

我已经尝试过使用deepseqs感知千万个版本,并且每行输出页面条目列表而不是我现在使用的向量,并使用lineC和部分解析器而不是linesUnboundedC。我假设必须有一些高级别的概念我错过了保持内存使用指数。该文件为5GB,我很容易通过16GB的内存,但我似乎无法在任何地方找到一个懒惰的内存泄漏。我假设这段代码能够独立存储每一行​​,然后在没有额外内存的情况下继续前进到下一行,但我无法弄清楚出了什么问题。我没有计算条目数量或使用更复杂的monoid来使用相同的解析器从文件中获取数据的问题。

非常感谢任何帮助!

{-# 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 个答案:

没有答案