生成元组列表时内存泄漏

时间:2014-09-17 15:48:19

标签: haskell optimization memory-leaks

以下代码为Quant类型的量词计算数字树 1 ,类似于函数类型allany

treeOfNumbers :: [(Integer, Integer)]
treeOfNumbers =
  [0..] >>= \ row ->
  let 
    inc = [0 .. row]
    dec = reverse inc
  in
  zip dec inc

type Quant = (Integer -> Bool) -> [Integer] -> Bool

check :: Quant -> (Integer, Integer) -> Bool
check q (m,n) =
  q (\ d -> d - m > 0) [1 .. domN]
  where
    domN = m + n

genTree :: Quant -> [(Integer, Integer)]
genTree q =
  filter (check q) treeOfNumbers

例如,take 10 $ genTree all的值为

[(0,0),(0,1),(0,2),(0,3),(0,4),(0,5),(0,6),(0,7),(0,8),(0,9)]

但是,此代码似乎会导致内存泄漏。由于ghci的堆限制在100M,genTree all(0,1600)周围被中断。限制在500M时,在变得非常慢之后会在(0,3950)附近停止。

如何改进?我对Haskell的经验有限,只能猜测我treeOfNumbers的实现可能是罪魁祸首; check适用于大值,没有任何问题。


1 参见计算语义与功能编程(Jan van Eijck和Christina Unger),剑桥大学出版社,2010年,第157-159页。

2 个答案:

答案 0 :(得分:6)

这里没有内存泄漏。您通过将其定义为顶级常量,明确告诉它保留整个元组列表。懒惰意味着它在需要之前不会产生价值,但这对保留价值没有帮助。 treeOfNumbers不会被垃圾收集,直到垃圾收集器可以证明它永远不会被再次使用。一些粗略的数学表明,当(0,1600)出现在列表中时,它将保持大约1,280,000个元组。这会占用大量的记忆。

答案 1 :(得分:3)

除非您绝对必须,否则不要为临时实体命名。

treeOfNumbers :: [(Integer, Integer)]
treeOfNumbers = [0..] >>= \ row -> zip [row, row-1 .. 0] [0 .. row]
  = [p | row <- [0..], p <- zip [row, row-1 .. 0] [0 .. row]]

type Quant = (Integer -> Bool) -> [Integer] -> Bool

check :: Quant -> (Integer, Integer) -> Bool
check q (m,n) = q (> m) [1 .. m + n] 

genTree :: Quant -> [(Integer, Integer)]
genTree q = filter (check q) treeOfNumbers
  = [ p | p <- [p | row <- [0..], p <- zip [row, row-1 .. 0] [0 .. row]]
        , check q p]
  = [ p | row <- [0..], p <- zip [row, row-1 .. 0] [0 .. row]
        , check q p]
  = [ (m,n) | row <- [0..], (m,n) <- zip [row, row-1 .. 0] [0 .. row]
            , q (> m) [1 .. m + n]]

现在genTree all在GHCi内部几乎恒定的内存中运行,尽管它在某种程度上正在放缓。但它在几秒钟内轻松达到了3000。

使用顶级treeOfNumbers,运行filter (check all) treeOfNumbers会在35M内存中达到3000,并且比上面慢两倍。

所以它不仅仅是关于顶级名称;定义中的所有临时名称也会导致数据保留。

此外,请使用-O2标志对其进行编译,并将其作为独立可执行文件运行,以用于任何大型工作。