序列化和计算值列表

时间:2011-08-11 04:30:18

标签: serialization haskell

我需要使用自定义编码功能(我有)序列化大量值。我已经完成了它并且它可以工作,但我还想让它计算有多少值被序列化并写入磁盘,同时仍使用相对恒定的内存量(即它不需要保留整个输入列出,因为它非常大。)

不需要保持计数,二元,谷物和火焰建造者都可以工作(使用相当于B.writeFile "foo" . runPut . mapM_ encodeValue);但无论我尝试使用这些库中的任何一个,似乎生成的ByteString都会在内存中保留,直到它完成,而不是一旦一个块可用就开始写入磁盘(即使使用{{3来自blaze-builder)。

这是一个最小的例子,展示了我一直在尝试做的事情:

import Data.Binary
import Data.Binary.Put
import Control.Monad(foldM)
import qualified Data.ByteString.Lazy as B

main :: IO ()
main = do let ns = [1..10000000] :: [Int]
              (count,b) = runPutM $ foldM (\ c n -> c `seq` (put n >> return (c+1))) (0 :: Int) ns
          B.writeFile "testOut" b
          print count

编译并使用+RTS -hy运行时,结果是一个由ByteString值支配的近似三角形图。

我到目前为止找到的唯一解决方案(我不是很喜欢)是使用foldM在IO中进行循环(直接或使用B.appendFile)而不是放或直接构建一个Builder值,这对我来说似乎并不优雅。还有更好的方法吗?

1 个答案:

答案 0 :(得分:2)

我有点惊讶toByteStringIO不起作用,希望有更熟悉该库的人会提供答案。

话虽这么说,每当我想将流处理与IO动作混合时,我通常会发现迭代是最优雅的解决方案。这是因为它们允许精确控制处理和保留的数据量,以及将流方面与其他任意IO操作相结合。在hackage上有several iteratee implementations;这个例子是“iteratee”,因为它是我最熟悉的那个。

import Data.Binary.Put
import Control.Monad
import Control.Monad.IO.Class
import qualified Data.ByteString.Lazy as B
import Data.ByteString.Lazy.Internal (defaultChunkSize)
import Data.Iteratee hiding (foldM)
import qualified Data.Iteratee as I

main :: IO ()
main = do 
  let ns = [1..80000000] :: [Int]
  iter <- enumPureNChunk ns (defaultChunkSize `div` 8)
                            (joinI $ serializer $ writer "testOut")
  count <- run iter
  print count

serializer = mapChunks ((:[]) . runPutM . foldM
   (\ !cnt n -> put n >> return (cnt+1)) 0)

writer fp = I.foldM
   (\ !cnt (len,ck) -> liftIO (B.appendFile fp ck) >> return (cnt+len))
   0

这有三个部分。 writer是“iteratee”,即数据使用者。它将每个数据块写为接收数据并保持长度的运行计数。 serializer是流变换器a.k.a.“enumeratee”。它采用类型为[Int]的输入块,并将其序列化为类型为[(Int, B.ByteString)]的流(元素数,bytestring)。最后enumPureNChunk是“枚举器”,它在这种情况下从输入列表产生流。从输入中获取足够的元素来填充单个惰性字节串块(我在64位,32位系统除以4),然后将它们写入磁盘,以便它们可以进行GC。