2011-03-09 34 views
5

我試圖修改Data.Binary.PutM monad成monad變換器。於是我開始了變化它的定義從爲什麼將Data.Binary.Put monad更改爲變換器會產生內存泄漏?

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

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

那麼當然我改變了回報>> =函數的實現:

來源:

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 monad只是一個Writer monad。不幸的是(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。 – 2011-03-09 12:58:00

+0

嗨@stephen,我忘了提及,如果我使用原始的Data.Binary.Put monad(沒有Identity的那個),那麼它流很好(沒有通知的內存增加)。我的理解是,如果記憶純粹是由樹結構消耗的,記憶力的增加會在兩種情況下表現出來。 – 2011-03-09 13:23:34

+0

你能給我們發一些更多的代碼嗎? – fuz 2011-03-09 13:27:30

回答

7

由於stephen tetley在他的評論中指出,這裏的問題過於嚴格。如果你只是(你(>>)定義~(PairS b w'))添加一些更懶惰你的身份,你的樣品會得到同樣的常量內存運行畫面:

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綁定爲>>實現,所以Tree不是被迫完全構建,因此「是流」。您的第二個身份示例是嚴格的,所以我的理解是整個Tree在處理之前會先建在內存中。實際上,你可以輕鬆地添加到嚴厲一號樣板房,並觀察它是如何開始「霸佔」記憶:

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

+1解決我的問題,這樣一個小小的變化,並解決了我最近兩天一直在盯着:-)。不幸的是,我不明白它爲什麼這樣做。你能解釋一下你的推理嗎?下次我能自己解決這個問題嗎? – 2011-03-09 15:18:26

+0

請參閱我的回答 – 2011-03-09 15:38:12

+0

&@stephen tetley,謝謝 – 2011-03-09 15:51:39