1

我正在使用 xml-conduit 的流接口解析一些相当大的 XML 文件https://hackage.haskell.org/package/xml-conduit-1.8.0/docs/Text-XML-Stream-Parse.html#v:parseBytes但我看到了这种内存积累(这里是一个小测试文件):

随着时间的推移堆使用

顶级用户在哪里:

堆责备

实际数据不应该占用那么多堆——如果我序列化并重新读取,驻留内存使用量是千字节而不是兆字节。

我设法通过以下方式重现此最小示例:

{-# LANGUAGE BangPatterns      #-}
{-# LANGUAGE OverloadedStrings #-}

module Main where

import           Control.Monad
import           Control.Monad.IO.Class
import           Data.Conduit
import           Data.Conduit.Binary    (sourceFile)
import qualified Data.Conduit.List      as CL
import           Data.Text              (Text)
import           Text.XML.Stream.Parse

type Y = [(Text, Text)]

main :: IO ()
main = do
  res1 <- runConduitRes $
          sourceFile "test.xml"
          .| Text.XML.Stream.Parse.parseBytes def
          .| parseMain
          .| CL.foldM get []
  print res1

get :: (MonadIO m, Show a) => [a] -> [a] -> m [a]
get acc !vals = do
 liftIO $! print vals           -- this oughta force it?
 return $! take 1 vals ++ acc

parseMain = void $ tagIgnoreAttrs "Period" parseDetails

parseDetails = many parseParam >>= yield

parseParam = tag' "param" parseParamAttrs $ \idAttr -> do
  value <- content
  return (idAttr, value)

parseParamAttrs = do
  idAttr <- requireAttr "id"
  attr "name"
  return idAttr
4

1 回答 1

0

如果我改变get为只是返回["hi"]或其他东西,我不会得到积累。因此,返回的文本似乎保留了对它们所在的较大文本的一些参考(例如零拷贝切片,参见https://hackage.haskell.org/package/text-0.11.2.0/docs/Data-Text上的评论.html#g:18),所以即使我们只使用了一小部分,文本的其余部分也不能被垃圾收集。

我们的解决方法是使用Data.Text.copy我们想要产生的任何属性:

someattr <- requireAttr "n"
yield (T.copy someattr)

这让我们可以使用几乎恒定的内存使用进行解析。

(如果我们想节省更多内存,我们可能会考虑使用https://markkarpov.com/post/short-bs-and-text.html#shorttext 。)

于 2019-03-29T12:38:52.950 回答