6

GHCI で、次の簡単なテストを実行します。

encodeFile "test" [0..10000000]

この行は非常に高速 (<10 秒) に実行されますが、終了するまでにメモリ使用量が最大 500MB に達します。ByteString.Lazy を使用するため、encodeFile は遅延するべきではありませんか?


編集:以下のローマンの答えは素晴らしいです!また、別の質問に対するこの回答を指摘したいと思います。これは、Data.Binary がリストに対して厳密なエンコードを行い、もう少し洗練された回避策を提供する理由を説明しています。

4

1 に答える 1

9

リストのシリアル化を定義する方法は次のとおりです。

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:上記のように計算された値を使用する代わりに、長さを渡す必要があるのはなぜですか?

于 2012-07-26T08:08:09.717 に答える