Mam algorytmu, który generuje drzewo przeszukiwania:Jak naprawić przestrzeń wycieku spowodowanego przez lenistwo, gdy algorytm opiera się na lenistwo
data SearchTree a = Solution a | Contradiction | Search [ SearchTree a ]
deriving (Show, Functor)
Algorytm generujący tego drzewa leniwie. Zdefiniowałem również prosty ewaluator, który jest po prostu głębokim pierwszym wyszukiwaniem.
simpleEval :: MonadPlus m => SearchTree a -> m a
simpleEval (Solution a) = return a
simpleEval Contradiction = mzero
simpleEval (Search ps) = foldr mplus mzero $ map simpleEval ps
Zauważyłem, że wiele rozwiązań, które mój algorytm produkuje wyglądać jak na poniższym wyszukiwania drzewa:
nest :: Int -> SearchTree a -> SearchTree a
nest 0 = id
nest n = nest (n-1) . Search . (:[])
tree0 = Search ts where
ts = cycle $ t0 : replicate 100 t1 ++ [t2]
t0 = nest 100 $ Solution 'a'
t1 = nest 1000 $ Contradiction
t2 = nest 4 $ Solution 'b'
Mianowicie, mają wiele bardzo głębokich oddziałów bez rozwiązań, kilka głębokich gałęzie z rozwiązaniem i bardzo niewiele płytkich gałęzi z rozwiązaniem. Na tej podstawie postanowiłem, że chcę innego oceniającego, który "zrezygnuje" z oddziałów, które są zbyt głębokie. Nazywają to cutoffEval
. cutoffEval 5 tree0
powinien znaleźć tylko b
s, ponieważ istnieje nieskończenie wiele gałęzi o głębokości mniejszej niż 5 do rozważenia, i zawierają one tylko b
s. I wprowadziły go tak:
cutoff :: (MonadPlus m) => Int -> SearchTree a -> (m a, [SearchTree a])
cutoff cu = go cu where
plus ~(m0, l0) ~(m1, l1) = (mplus m0 m1, l0 ++ l1)
zero = (mzero, [])
go 0 x = (mzero, [x])
go _ Contradiction = zero
go _ (Solution a) = (return a, [])
go d (Search ps) = foldr plus zero $ map (go $ d-1) ps
cutoffEval :: MonadPlus m => Int -> SearchTree a -> m a
cutoffEval cu = go where
go t = case cutoff cu t of (r,ts) -> foldr mplus mzero $ r : map go ts
Ale ta funkcja daje ogromny wyciek przestrzeń, w porównaniu do simpleEval
:
putStrLn $ take 4000 $ simpleEval tree0 -- 2MB residency
putStrLn $ take 4000 $ cutoffEval 10 tree0 -- 600MB residency
Profilowanie wynika, że prawie cały przydział odbywa się w cutoff.go
; a większość alokacji wynika z czegoś tajemniczego o nazwie main:Tree.sat_s5jg
i konstruktora (,)
. Wydaje mi się, że ze względu na niezaprzeczalne wzorce, konstruktorzy krotki są budowani jako ciosy zamiast zmuszani przez plus
; i normalnie rozwiązaniem problemu wycieku przestrzeni jest sprawienie, aby twoja funkcja była bardziej rygorystyczna, ale tutaj usunięcie niepotwierdzonych wzorców powoduje zawieszenie się cutoff
, więc nie mogę tego zrobić.
Testowałem to z GHC 7.6, 7.8 i 7.10. Problem został znaleziony w każdym z nich.
Moje pytania brzmią: czy można napisać, że cutoffEval
działa w stałej przestrzeni, takiej jak simpleEval
? A bardziej ogólnie, jak mogę naprawić wyciek przestrzeni, jeśli nie mogę uczynić mojej implementacji bardziej rygorystyczną, ponieważ zależy od tego algorytm?
Ja [nie mogę] (http://ideone.com/hQu1ut) odtworzyć. Czy kompilujesz z '-O2'? Możesz zrobić "go" bezpiecznie zaostrzając, usuwając tylko pierwszy niezaprzeczalny wzorzec w 'plus', mówiąc w ten sposób" Potrzebuję 'seq', ale nie' deepseq'. BTW, 'foldr mplus mzero' to' msum'. – user3237465
@ user3237465 Kompiluję z optymalizacją. Mogę odtworzyć problem dokładnie tym modułem, który łączyłeś. Usunięcie tylko pierwszego niezmiennego wzorca nie daje żadnego efektu. Jakiej wersji GHC używasz? Być może powinienem podać wyniki profilowania na moim komputerze w pytaniu. – user2407038
@ user2407038 jakie wersje ghc * używasz *. Kiedy publikujesz pytanie * musisz * upewnić się, że inne osoby mogą odtworzyć wyniki, a tym samym znaleźć rozwiązanie. Więc jest to * Twoja * wersja ghc, która jest ważna, aby odpowiedzieć na twoje pytanie. – Bakuriu