リストのシリアル化を定義する方法は次のとおりです。
instance Binary a => Binary [a] where
put l = put (length l) >> mapM_ put l
つまり、最初にリストの長さをシリアライズしてから、リスト自体をシリアライズします。
リストの長さを調べるには、リスト全体を評価する必要があります。しかし、その要素は 2 番目の部分で必要になるため、ガベージ コレクションはできませんmapM_ put l
。したがって、長さが評価された後、要素のシリアル化が開始される前に、リスト全体をメモリに格納する必要があります。
ヒープ プロファイルは次のようになります。
リストが作成されてその長さを計算している間にどのように増加し、要素がシリアル化されて GC によって収集できるようになると減少するかに注目してください。
それで、これを修正する方法は?あなたの例では、すでに長さを知っています。したがって、計算するのではなく、既知の長さを取る関数を書くことができます。
import Data.Binary
import Data.ByteString.Lazy as L
import qualified Data.ByteString as B
import Data.Binary.Put
main = do
let len = 10000001 :: Int
bs = encodeWithLength len [0..len-1]
L.writeFile "test" bs
putWithLength :: Binary a => Int -> [a] -> Put
putWithLength len list =
put len >> mapM_ put list
encodeWithLength :: Binary a => Int -> [a] -> ByteString
encodeWithLength len list = runPut $ putWithLength len list
このプログラムは、53k のヒープ領域内で実行されます。
安全機能を に含めることもできますputWithLength
: リストをシリアライズしながら長さを計算し、最後に最初の引数でチェックします。不一致がある場合は、エラーをスローします。
演習putWithLength
:上記のように計算された値を使用する代わりに、長さを渡す必要があるのはなぜですか?