生成不同整数的树会导致空间泄漏
Posted
技术标签:
【中文标题】生成不同整数的树会导致空间泄漏【英文标题】:Generating a tree of distinct integers results in a space leak 【发布时间】:2015-12-24 12:38:34 【问题描述】:我想生成一个包含不同整数的树并找到它们的总和。代码如下:
-# 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)
使用-O2
编译结果
348,785,728 bytes allocated in the heap
147,227,228 bytes copied during GC
34,656,860 bytes maximum residency (13 sample(s))
35,468 bytes maximum slop
72 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 565 colls, 0 par 0.764s 1.024s 0.0018s 0.0071s
Gen 1 13 colls, 0 par 0.936s 1.014s 0.0780s 0.3214s
INIT time 0.000s ( 0.001s elapsed)
MUT time 0.936s ( 0.764s elapsed)
GC time 1.700s ( 2.038s elapsed)
EXIT time 0.000s ( 0.002s elapsed)
Total time 2.636s ( 2.805s elapsed)
%GC time 64.5% (72.7% elapsed)
Alloc rate 372,631,936 bytes per MUT second
Productivity 35.5% of total user, 33.4% of total elapsed
为什么会出现这种空间泄漏?怎么去掉?
【问题讨论】:
import Control.Monad.Trans.State.Strict
代替,因为你不能在这里使用奇怪的额外懒惰。这似乎是最有可能的问题。你可能会或可能不会真正受益于那种花哨的sumTree
,这取决于它的倾斜方式(我认为)。
呃...我想你可以从额外的懒惰中受益,但必须非常小心地求和。我一般不会推荐这种方法——太脆弱了。
@dfeuer,这是我尝试的第一件事。它稍微好一点,但仍然有泄漏。我写的best version 使用严格和惰性状态(但没有明确的State
)。速度是原来的两倍,使用的总内存为 27 MB,但仍然是 GC 时间的一半。
你的sumTree
强制左子树(至少)因为(a + n)
在严格的累加器中(n
是由计算左树的State
计算输出的)。如果你写一个不带累加器的sumTree
并切换到Int
-s,它会在 2 MB 内存中运行。我不确定为什么Integer
即使在这种情况下也会泄漏空间;我可能会看看它,稍后再写一个答案,因为我现在很忙。
原来Integer
泄漏空间是因为它构建了整个(+)
thunk 树(“经典”泄漏),而Int
被拆箱并变得严格。左侧总和上的seq
或爆炸图案可修复它。我同意它相当脆弱。
【参考方案1】:
任何时候你构建一棵树,你都应该尝试找到一种完全自上而下工作的方法。这通常对惰性、并发性、缓存利用率、GC 有效性等有好处。您构建的树只是按顺序编号的完整二叉树。我建议您考虑使用以下签名并进行一些位移:
tree :: Bits b => Int -> Tree b
您可以分解一个作为起点的辅助函数。
【讨论】:
以上是关于生成不同整数的树会导致空间泄漏的主要内容,如果未能解决你的问题,请参考以下文章