2011-03-09 19 views
5

Próbuję zmodyfikować monadę Data.Binary.PutM w transformator monad. Zacząłem więc przez changin To definicja zDlaczego zmiana monady Data.Binary.Put w transformator powoduje wyciek pamięci?

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

do

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

Wtedy oczywiście zmieniłem implementacje zamian i >> = funkcje:

Od:

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

Do:

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

Tak jakby monada PutM była tylko monadą pisarza. Niestety to (again) spowodowało wyciek przestrzeni. Dla mnie jest jasne (czy jest?), Że ghc odkłada gdzieś ocenę, ale próbowałem umieścić $! zamiast $ wszędzie, jak sugerowały niektóre tutoriale, ale to nie pomogło. Ponadto nie jestem pewien, w jaki sposób profiler pamięci jest przydatny, jeśli to, co mi pokazuje, jest następujące:

Memory profile.

I pod względem kompletności, jest to profil pamięć uzyskać przy użyciu oryginalnego Data.Binary.Put monady:

Original memory profile

Zainteresowanych here jest kod używam go przetestować i linia używam skompilować, uruchomić i stworzyć profil pamięci to:

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

mam nadzieję, że nie jestem przykry ktoś przez mojego sagi pytań wycieków pamięci. Uważam, że w Internecie nie ma zbyt wielu dobrych zasobów na ten temat, które pozostawiają po sobie ślad.

Dzięki za spojrzenie.

+2

Cześć Peter - Nie jestem przekonany, masz przeciek „przestrzeń” w Data.Binary tjniepoprawny uchwyt danych zatrzymujący gromadzenie śmieci. Myślę, że budowanie ogromnego profilu pamięci jest spowodowane tym, że struktura danych (drzewo) nie jest przesyłana strumieniowo - wszystko musi znajdować się w pamięci (oraz podobnie duży wyjściowy ByteString), dopóki nie zakończy się serializacja. Moją intuicją jest problem - drzewo - nie Data.Binary. –

+0

Witam @ Stephen, zapomniałem wspomnieć, że jeśli używam oryginalnej Monady Data.Binary.Put (tej bez Tożsamości), to ładnie się strumieniuje (brak zauważalnego zwiększenia pamięci). Rozumiem, że jeśli pamięć została skonsumowana wyłącznie przez strukturę drzewa, wzrost pamięci przejawia się w obu przypadkach. –

+0

Czy możesz przesłać nam więcej kodu? – fuz

Odpowiedz

7

Jak zauważył w swoim komentarzu jako stephen tetley, problem polega na nadmiernej surowości. Jeśli wystarczy dodać trochę więcej lenistwo do próbki Identity (~(PairS b w') w definicji (>>)) dostaniesz taką samą stałą obraz run Pamięć:

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 

rzeczywiście można używać zwykłych krotki tutaj i $ zamiast $!

PS Jeszcze raz: prawidłowa odpowiedź znajduje się w komentarzu stephen tetley. Rzecz w tym, że twój pierwszy przykład używa leniwychlet powiązań dla implementacji >>, więc Tree nie musi być całkowicie zbudowany, a zatem "jest przesyłany strumieniowo". Twój drugi przykład tożsamości jest ścisły, więc rozumiem, że cały Tree zostaje wbudowany w pamięć przed przetworzeniem. Rzeczywiście można łatwo dodać surowość do 1 przykładu i obserwować jak to się zaczyna „wyginanie” Pamięć:

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

+1 za rozwiązanie mojego problemu, tak małą zmianę i rozwiązuje to, na co patrzyłem przez ostatnie dwa dni :-). Niestety nie rozumiem, dlaczego tak się dzieje. Czy mógłbyś wyjaśnić swoje rozumowanie, aby następnym razem sam mogłem rozwiązać ten problem? –

+0

Zobacz PS do mojej odpowiedzi –

+0

& @ Stephen Tetley, dziękuję –