我有一些生成搜索树的算法:
data SearchTree a = Solution a | Contradiction | Search [ SearchTree a ]
deriving (Show, Functor)
算法懒惰地生成这棵树。我还定义了一个简单的求值器,它实际上只是深度优先搜索。
simpleEval :: MonadPlus m => SearchTree a -> m a
simpleEval (Solution a) = return a
simpleEval Contradiction = mzero
simpleEval (Search ps) = foldr mplus mzero $ map simpleEval ps
我注意到我的算法生成的许多解决方案类似于以下搜索树:
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'
即,它们有很多非常深的分支,没有解决方案,一些深层分支有一个解决方案,很少有浅层分支和一个解决方案。在此基础上,我决定我想要另一个评估员,一个将放弃的评估员。在太深的树枝上。称之为cutoffEval
。 cutoffEval 5 tree0
应该只找到b
因为它有无限多个深度小于5的分支要考虑,它们只包含b
s。我这样实现了它:
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
但与simpleEval
:
putStrLn $ take 4000 $ simpleEval tree0 -- 2MB residency
putStrLn $ take 4000 $ cutoffEval 10 tree0 -- 600MB residency
分析显示几乎所有分配都发生在cutoff.go
;并且大多数分配是由于一些神秘的,称为main:Tree.sat_s5jg
和(,)
构造函数。在我看来,由于无可辩驳的模式,元组构造函数被构建为thunk而不是被plus
强制;并且通常空间泄漏的解决方案是使您的函数更严格,但是在这里删除无可辩驳的模式导致cutoff
挂起,所以我不能这样做。
我用GHC 7.6,7.8和7.10测试了这个。每个人都发现了这个问题。
所以我的问题是:cutoffEval
可以编写为在simpleEval
这样的常量空间中运行吗?更一般地说,如果我不能使我的实现更严格,因为算法取决于它,我如何修复空间泄漏?
答案 0 :(得分:1)
在我看来,内存泄漏的原因实际上是一个错误。你的cutoff
函数混合在一起,切断过深的分支,评估上半部分。然后在cutoffEval
中,你会深入到底部,切割树枝,并继续以递归方式探索它们。这实际上是广度优先搜索,每次传递的cu
级别。这意味着整个树最终将构建并保留在内存中直到结束! (与深度优先搜索的情况不同,GC可以回收访问的子树。)
如果你想切断太深的树枝,那么得到cutoff
结果的第一部分就是你想要的。
无论如何,我建议将评估者和截止部分分开(见下文)。在这种情况下,您可以在树的截止版本上使用原始评估程序。
另外一条评论来自MonadPlus
约束,您只使用幺半部分 - mzero
和mplus
。仅使用Monoid
会更清晰,更通用。 monad比monad更多(例如Sum
只计算solutoins,或Last
找到最后的解决方案。)
simpleEval :: (Monoid m) => (a -> m) -> SearchTree a -> m
simpleEval f = go
where
go (Solution a) = f a
go Contradiction = mempty
go (Search ps) = mconcat $ map go ps
cutoff :: Int -> SearchTree a -> SearchTree a
cutoff cu = go cu
where
go 0 _ = Contradiction -- too deep branches are just failures
go d (Search ps) = Search $ map (go (d - 1)) ps
go _ x = x
cutoffEval :: (Monoid m) => Int -> (a -> m) -> SearchTree a -> m
cutoffEval cu f = simpleEval f . cutoff cu