Haskell 生成不同整数的树会导致空间泄漏

Haskell 生成不同整数的树会导致空间泄漏,haskell,memory-leaks,profiling,Haskell,Memory Leaks,Profiling,我想生成一个包含不同整数的树,并找到它们的和。代码如下: {-# LANGUAGE BangPatterns #-} import Control.Applicative import Control.Monad.Trans.State data Tree a = Leaf a | Branch (Tree a) a (Tree a) new = get <* modify' (+ 1) tree :: Integer -> Tree Integer tree n = eva

我想生成一个包含不同整数的树,并找到它们的和。代码如下:

{-# LANGUAGE BangPatterns #-}

import Control.Applicative
import Control.Monad.Trans.State

data Tree a = Leaf a | Branch (Tree a) a (Tree a)

new = get <* modify' (+ 1)

tree :: Integer -> Tree Integer
tree n = evalState (go n) 0 where
    go 0 = Leaf <$> new
    go n = Branch <$> go (n - 1) <*> new <*> go (n - 1)

sumTree = go 0 where
    go !a (Leaf n)       = a + n
    go !a (Branch l n r) = go (go (a + n) l) r

main = print $ sumTree (tree 20)

为什么我会有空间泄漏?如何删除它?

无论何时构建一棵树,都应该尝试找到一种从上到下专门工作的方法。这通常有利于惰性、并发性、缓存利用率、GC有效性等。您构建的树只是一个按顺序编号的完整二叉树。我建议您考虑使用以下签名并做一些移位:

tree :: Bits b => Int -> Tree b

您可以分解一个以起点为起点的helper函数。

导入Control.Monad.Trans.State.Strict
,因为您不能在这里使用奇怪的额外惰性。这似乎是最可能的问题。你可能会也可能不会真的从这种花哨的sumTree中受益,这取决于它的倾斜程度(我认为)。。。我想你可以从额外的懒惰中获益,但必须非常小心地总结。“我一般不推荐这种方法——太脆弱了。”德弗尔,这是我第一次尝试的。稍微好一点,但还是有漏洞。我编写的代码使用严格状态和惰性状态(但没有显式的
状态
)。两倍的速度,27 MB的总内存在使用中,但仍然是GC花费时间的一半。您的
sumTree
强制左子树(至少),因为严格累加器中的
(a+n)
n
由计算左树的
状态
计算输出)。如果您在不使用累加器的情况下编写一个
sumTree
,并切换到
Int
-s,它将在2MB内存中运行。我不确定为什么
Integer
即使在这种情况下也会泄漏空间;我可能会看看它,稍后再写一个答案,因为我现在很忙。
Integer
会泄漏空间,因为它构建了整个
(+)
thunk树(“经典”泄漏),而
Int
会被解除绑定并变得严格。左和上的
seq
或bang模式将其修复。我同意它相当脆弱。
tree :: Bits b => Int -> Tree b