我有一个解析器,我正在尝试编写,而且我已经浏览了它的多个版本,而我似乎无法降低内存使用率。我试图解析维基百科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