5

Data.Binary.PutM モナドをモナド変換子に変更しようとしています。だから私はそれの定義を変更することから始めました

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

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

それからもちろんreturn>>=関数の実装を変更しました:

から:

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 (>>) #-}

に:

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 モナドであるかのように。残念ながら、これは(再び)スペースリークを引き起こしました。ghc がどこかで評価を延期していることは明らかですが、いくつかのチュートリアルで提案されているように、どこにでも配置しようとしまし$!$が、それは役に立ちませんでした。また、次のような場合、メモリ プロファイラーがどのように役立つかわかりません。

メモリ プロファイル.

完全を期すために、これは元の Data.Binary.Put モナドを使用したときに得られるメモリ プロファイルです。

元のメモリ プロファイル

興味がある場合は、テストに使用しているコードと、コンパイル、実行、およびメモリ プロファイルの作成に使用している行を次に示します

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

メモリ リークに関する私の一連の質問で誰かを悩ませていないことを願っています。インターネット上には、このトピックに関する優れたリソースがあまりないことがわかりました。

ご覧いただきありがとうございます。

4

1 に答える 1

7

stephen tetley彼のコメントで指摘されているように、ここでの問題は過度の厳密さにあります。~(PairS b w')Identity サンプル (定義内) にさらに遅延を追加するだけで(>>)、同じ一定のメモリ実行画像が得られます。

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

実際には、ここで通常のタプルを使用できます$$!

PS 繰り返しますが、正しい答えは実際にはstephen tetleyコメントにあります。問題は、最初の例では実装に遅延 letバインディングを使用しているため、完全にビルドする必要がなく、「ストリーミング」されることです。あなたの 2 番目のアイデンティティの例は厳密なので、処理される前に全体がメモリに組み込まれることを理解しています。実際には、最初の例に厳密性を簡単に追加して、メモリを「占有」し始める方法を観察できます。>>TreeTree

m >> k  = Put $
          case unPut m of
            PairS _ w ->
                case unPut k of
                  PairS b w' ->
                      PairS b (w `mappend` w')
于 2011-03-09T14:55:38.297 に答える