Pytanie Dlaczego zawijanie monady Data.Binary.Put tworzy przeciek pamięci?


Próbuję zawinąć monadę Data.Binary.Put do drugiej, aby później móc zadawać pytania typu "ile bajtów zamierza napisać" lub "jaka jest aktualna pozycja w pliku". Ale nawet bardzo banalne napisy jak:

data Writer1M a = Writer1M { write :: P.PutM a }
or
data Writer2M a = Writer2M { write :: (a, P.Put) }

stworzyć ogromny wyciek przestrzeni, a program zazwyczaj ulega awarii (po zajętym 4 GB pamięci RAM). Oto, co próbowałem do tej pory:

-- This works well and consumes almost no memory.

type Writer = P.Put

writer :: P.Put -> Writer
writer put = put

writeToFile :: String -> Writer -> IO ()
writeToFile path writer = BL.writeFile path (P.runPut writer)

-- This one will cause memory leak.

data Writer1M a = Writer1M { write :: P.PutM a }

instance Monad Writer1M where
  return a = Writer1M $ return a
  ma >>= f = Writer1M $ (write ma) >>= \a -> write $ f a

type WriterM = Writer1M
type Writer = WriterM ()

writer :: P.Put -> Writer
writer put = Writer1M $ put

writeToFile :: String -> Writer -> IO ()
writeToFile path writer = BL.writeFile path (P.runPut $ write writer)
-- This one will crash as well with exactly the
-- same memory foot print as Writer1M

data Writer2M a = Writer2M { write :: (a, P.Put) }

instance Monad Writer2M where
  return a = Writer2M $ (a, return ())
  ma >>= f = Writer2M $ (b, p >> p')
                        where (a,p) = write ma
                              (b,p') = write $ f a

type WriterM = Writer2M
type Writer = WriterM ()

writer :: P.Put -> Writer
writer put = Writer2M $ ((), put)

writeToFile :: String -> Writer -> IO ()
writeToFile path writer = BL.writeFile path (P.runPut $ snd $ write writer)

Jestem nowy dla Haskella i nie ma to dla mnie znaczenia, ale monady na opakowaniach wydają się bardzo trywialne, więc zgaduję, że jest coś oczywistego, czego mi brakuje.

Dzięki za spojrzenie.

AKTUALIZACJA: Oto przykładowy kod demonstrujący problem: http://hpaste.org/43400/why_wrapping_the_databinaryp

UPDATE2: Jest jeszcze druga część tego pytania tutaj.


12
2018-01-28 13:48


pochodzenie


Z jakich flag kompilatora korzystasz? - C. A. McCann
Po zapytaniu spróbowałem z -O2 (wcześniej go nie użyłem), ale wydruk stopy pamięci się nie zmienił. - Peter Jankuliak
Czy mógłbyś opublikować prosty program testowy, aby inni nie musieli tworzyć własnych? - Thomas M. DuBuisson
Dobry pomysł, wkrótce coś skompiluję. Czy mogę jakoś opublikować go tutaj na stackoverflow, czy zamiast tego powinienem użyć hpaste? - Peter Jankuliak
Próbowałem twojej próbki, a dzięki moim flagom GHC 6.12.3 i -O2 obie wersje pokazują prawie identyczne zachowanie w czasie / przestrzeni. Wymiana data w "problematycznym" opakowaniu z newtype jeszcze bardziej zmniejsza różnicę. Bez -O2 jest zdecydowanie chciwy w pamięci. Na pewno skompilowałeś ponownie z -O2? Próbować -O2 -fforce-recomp - Ed'ka


Odpowiedzi:


Po trochę pogawędkę, okazało się, że problemem wydaje się być użycie binarnego (>> =) do implementacji (>>). Następujący dodatek do implementacji Monita Writer1M rozwiązuje problem:

  m >> k = Writer1M $ write m >> write k

Podczas gdy ta wersja nadal wycieka z pamięci:

  m >> k = Writer1M $ write m >>= const (write k)

Patrzeć na źródło binarne, (>>) wydaje się wyraźnie odrzucić wynik pierwszej monady. Nie wiem jednak, jak dokładnie zapobiega to wyciekowi. Moją najlepszą teorią jest to, że GHC w inny sposób zatrzymuje się na obiekcie PairS, a referencja "a" przecieka, ponieważ nigdy nie jest postrzegana.


4
2018-01-29 21:27





Czy próbowałeś jeszcze bardziej monadę? ścisły? Na przykład. spróbuj ustawić konstruktory swojej klasy danych jako ścisłe / zastąp je nowymi typami.

Nie wiem, jaki jest dokładny problem, ale jest to zwykle źródło wycieków.

PS: I spróbuj usunąć niepotrzebne lambdy, na przykład:

  ma >>= f = Writer1M $ (write ma) >=> write . f

2
2018-01-28 14:05



Zmiana z danych na typ nowy jest tym, co zasugerowali dobrzy ludzie w # pasjansie, niestety zmieniając to, a usunięcie lambda, zgodnie z sugestią, nie zmieniło wydrukowania stopy pamięci. Ale dzięki za sugestię. - Peter Jankuliak
Czy próbowałeś również profilowania? - fuz
Tak, oto wynik: i.imgur.com/4Q2E3.png , żółty obszar pojawia się, gdy używam jednej z owijek. - Peter Jankuliak
Bardzo dziwny. Myślę, że nie mogę ci pomóc. - fuz
Przypuszczam, że Writer1M nieumyślnie działa jako element podtrzymujący, przytrzymując wskaźnik na wyjściu, a tym samym uniemożliwiając GC zbieranie próbek po drodze. Spróbuj biegać z profilowaniem ustalającym i zobacz, czy to ci coś powie. - Paul Johnson