2011-08-11 74 views
4

我需要使用自定義編碼函數(我有)序列化一大串值。我已經做到了這一點,它的工作原理,但我也想計算有多少值被序列化和寫入磁盤,同時仍然使用相對恆定的內存量(即它不應該保持整個輸入列表,因爲它獲得非常大大)。如果沒有保持計數,二元,穀物和大火建造者的所有工作的要求(使用等效的B.writeFile "foo" . runPut . mapM_ encodeValue);但無論我試圖對這些庫進行什麼操作,看起來ByteString似乎都會一直保留在內存中,直到完成,而不是一有塊可用就開始寫入磁盤(即使使用火焰的toByteStringIO -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,結果是字節字符串值占主導地位的幾乎爲三角形的圖形。

到目前爲止,我發現(我不是一個大風扇)唯一的辦法是做循環(直接或foldM)在IO使用B.appendFile,而不是把內或直接構建一個Builder值,這對我來說看起來不是很優雅。有沒有更好的辦法?

回答

2

我有點驚訝,toByteStringIO不起作用,希望有人更熟悉該圖書館將提供一個答案。這就是說,無論何時我想將流處理與IO操作混合在一起,我通常會發現iteratees是最優雅的解決方案。這是因爲它們允許精確控制處理和保留多少數據,以及將流方面與其他任意IO操作相結合。有關於hackage的severaliterateeimplementations;這個例子是「迭代」,因爲它是我最熟悉的。

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是「迭代器」,即數據消費者。它將每個數據塊寫入其接收的數據,並保持長度的運行計數。 serializer是流變換器a.k.a.「enumeratee」。它需要一個類型爲[Int]的輸入塊,並將其序列化爲類型爲[(Int, B.ByteString)](元素數,字節串)的流。最後enumPureNChunk是「枚舉器」,它在輸入列表中產生一個流。它從輸入中獲取足夠的元素來填充單個惰性字節塊(我在64位上,對於32位系統除以4),然後將它們寫入磁盤,以便它們可以被GC化。

+0

爲什麼8192與其他值相反?我在哪裏可以找到'run'函數的定義和類型,因爲我無法在iteratee中找到它? – ivanm

+0

儘管我認爲你的解決方案演示了我用自己的解決方案所遇到的問題:似乎不可能構建「ByteString」並計算同時有多少個值;你在寫入磁盤時似乎必須這樣做。 – ivanm

+0

哦,而且你所做的與我的原始版本不同的一個方面是:如果我理解你的代碼,你將返回並打印保存到磁盤的**字節數**,而不是數字**值**。因此,它是錯誤的:( – ivanm