2011-03-09 15 views
5

私はData.Binary.PutMモナドをモナドトランスに変更しようとしています。なぜData.Binary.Putモナドを変圧器に変更するとメモリリークが発生するのですか?

だから私は

その後
newtype PutM a = Put { unPut :: Identity (PairS a) }

newtype PutM a = Put { unPut :: PairS a }

からの定義をCHANGINによって開始されたが、もちろん私はリターン>> =関数の実装を変更しました

return a = Put $ PairS a mempty 
{-# INLINE return #-} 

m >>= k = Put $ 
    let PairS a w = unPut m 
     PairS b w1 = unPut (k a) 
    in PairS b (w `mappend` w1) 
{-# INLINE (>>=) #-} 

m >> k = Put $ 
    let PairS _ w = unPut m 
     PairS b w1 = unPut k 
    in PairS b (w `mappend` w1) 
{-# INLINE (>>) #-} 

To:

return a = Put $! return $! PairS a mempty 
{-# INLINE return #-} 

m >>= k = Put $! 
    do PairS a w <- unPut m 
     PairS b w1 <- unPut (k a) 
     return $! PairS b $! (w `mappend` w1) 
{-# INLINE (>>=) #-} 

m >> k = Put $! 
    do PairS _ w <- unPut m 
     PairS b w1 <- unPut k 
     return $! PairS b $! (w `mappend` w1) 
{-# INLINE (>>) #-} 

あたかもPutMモナドが単なるWriterモナドであるかのように。残念ながら、これは(again)スペースリークを作成しました。 ghcがどこかで評価を延期していることは私にはっきりしていますが、$の代わりに$!を貼り付けようとしましたが、いくつかのチュートリアルで示唆されているように助けにはなりませんでした。また、メモリプロファイラが私に何を示しているのかが分かっていれば、これはどのように役立ちますか分かりません。

Memory profile

万全を期すために、これはオリジナルのData.Binary.Putモナドを使用しているとき私が得るメモリプロファイルです:

Original memory profile

興味を持っている場合、hereは、私はそれをテストするために使用しているコードで、私は、コンパイルを実行し、メモリのプロファイルを作成するために使用している行がある:

ghc -auto-all -fforce-recomp -O2 --make test5.hs && ./test5 +RTS -hT && hp2ps -c test5.hp && okular test5.ps 

私はメモリリークの質問の私の武勇伝による迷惑誰もいないよ願っています。私はこの話題に関してインターネット上に多くの良い資源がないことを知っています。

ありがとうございます。

+2

こんにちはピーター - 私はあなたがData.Binaryすなわち内「宇宙リーク」を持って確信していませんガベージコレクションを停止しているデータのハンドルが間違っています。なぜ私はあなたのデータ構造(ツリー)がストリームしないために巨大なメモリプロファイルを構築していると思う - それがシリアル化を完了するまで、すべてのメモリ(プラス同様に大きな出力ByteString)にする必要があります。私の直感は、問題は木です - Data.Binaryではありません。 –

+0

こんにちは@stephen、私は元のData.Binary.Putモナド(それにアイデンティティのないもの)を使用する場合、それはうまくストリーミングしています(顕著なメモリの増加はありません)。メモリが純粋にツリー構造によって消費された場合、メモリの増加はどちらの場合でも明らかになります。 –

+0

さらにコードを送ってもらえますか? – fuz

答えて

7

stephen tetleyがコメントで指摘したように、ここでの問題は過度の厳密さです。あなたが実際に代わり$!

の通常ここにタプルと $を使用することができます

data PairS a = PairS a {-# UNPACK #-}!Builder 

sndS :: PairS a -> Builder 
sndS (PairS _ !b) = b 

newtype PutM a = Put { unPut :: Identity (PairS a) } 

type Put = PutM() 

instance Monad PutM where 
    return a = Put $! return $! PairS a mempty 
    {-# INLINE return #-} 

    m >>= k = Put $! 
     do PairS a w <- unPut m 
      PairS b w' <- unPut (k a) 
      return $! PairS b $! (w `mappend` w') 
    {-# INLINE (>>=) #-} 

    m >> k = Put $! 
     do PairS _ w <- unPut m 
      ~(PairS b w') <- unPut k 
      return $! PairS b $! (w `mappend` w') 
    {-# INLINE (>>) #-} 

tell' :: Builder -> Put 
tell' b = Put $! return $! PairS() b 

runPut :: Put -> L.ByteString 
runPut = toLazyByteString . sndS . runIdentity . unPut 

:あなたは自分のアイデンティティのサンプル(あなた(>>)定義における~(PairS b w'))にいくつかのより多くの怠惰を追加する場合は、同じ定数メモリが絵を実行しますよ

PSもう一度:正解は実際にはstephen tetleyコメントです。第1の例では、遅延使用letバインディングを>>の実装に使用しているため、Treeは完全にビルドされず、したがって「ストリームされていません」。あなたの第2のアイデンティティの例は厳密なものなので、処理する前に全体のTreeがメモリに組み込まれていると私は理解しています。あなたが実際に簡単に第一例に厳しさを追加し、それが記憶」独占]を起動する方法を観察することができます:

m >> k = Put $ 
      case unPut m of 
      PairS _ w -> 
       case unPut k of 
        PairS b w' -> 
         PairS b (w `mappend` w') 
+1

+1私の問題を解決するために+1、このような小さな変化と私は最後の2日間の凝視していたものを解決する:-)。残念ながら私はそれがなぜそれをするのか分かりません。あなたは自分の推論を説明して、次回自分でそのような問題を解決することができるでしょうか? –

+0

私の答えにPSを見てください –

+0

&@stephen tetley、ありがとう –