我需要使用自定义编码功能(我有)序列化大量值。我已经完成了它并且它可以工作,但我还想让它计算有多少值被序列化并写入磁盘,同时仍使用相对恒定的内存量(即它不需要保留整个输入列出,因为它非常大。)
不需要保持计数,二元,谷物和火焰建造者都可以工作(使用相当于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值,这对我来说似乎并不优雅。还有更好的方法吗?
答案 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。