列表程序中的空间泄漏

时间:2010-07-06 20:47:39

标签: performance haskell lazy-evaluation

我正在解决Haskell中Project Euler的一些问题。我在里面为一个谜语编写了一个程序,它没有像我预期的那样工作。

当我在运行程序时查看任务管理器时,我看到它正在使用> ghc上有1千兆字节的RAM。我的一个朋友在Java中编写了一个具有相同含义的程序,并在7秒内成功完成。

import Data.List

opl = find vw $ map (\x-> fromDigits (x++[0,0,9]) ) 
        $ sequence [[1],re,[2],re,[3],re,[4],re,[5],re,[6],re,[7],re,[8],re]

vw x = hh^2 == x
    where hh = (round.sqrt.fromIntegral) x

re = [0..9]

fromDigits x = foldl1 (\n m->10*n+m) x

我知道这个程序会输出我想要的数量,只要有足够的RAM和时间,但必须有更好的表现方式。

4 个答案:

答案 0 :(得分:28)

这里的主要问题是序列有空间泄漏。它的定义如下:

sequence [] = [[]]
sequence (xs:xss) = [ y:ys | y <- xs, ys <- sequence xss ]

所以问题是递归调用sequence xss生成的列表被重用于xs的每个元素,因此它不能在结束之前被丢弃。没有空间泄漏的版本是

myseq :: [[a]] -> [[a]]
myseq xs = go (reverse xs) []
 where
  go [] acc = [acc]
  go (xs:xss) acc = concat [ go xss (x:acc) | x <- xs ]

PS。答案似乎是Just 1229314359627783009

编辑版本,避免使用concat:

seqlists :: [[a]] -> [[a]]
seqlists xss = go (reverse xss) [] []
 where
   go []       acc rest = acc : rest
   go (xs:xss) acc rest = foldr (\y r -> go xss (y:acc) r) rest xs

请注意,这两个版本都会以与标准sequence不同的顺序生成结果,因此虽然它们可以解决此问题但我们不能将其用作sequence的专用版本。< / p>

答案 1 :(得分:3)

继Simon Marlow给出的答案之后,这里有一个序列版本可以避免空间泄漏,而原本就像原版一样工作,包括保留顺序。

它仍然使用原始序列的漂亮,简单的列表理解 - 唯一的区别是引入了伪数据依赖,阻止递归调用被共享。

sequenceDummy d [] = d `seq` [[]]
sequenceDummy _ (xs:xss) = [ y:ys | y <- xs, ys <- sequenceDummy (Just y) xss ]

sequenceUnshared = sequenceDummy Nothing

我认为这是一种避免共享导致空间泄漏的更好方法。

我责怪过度分享“完全懒惰”的转变。通常,这可以很好地创建共享以避免重新计算,但有时重新计算比存储共享结果更有效。

如果有更直接的方法告诉编译器不要共享特定的表达式,那就太好了 - 上面的虚拟Maybe参数有效并且有效,但它基本上是一个复杂到足够复杂的黑客ghc不能说没有真正的依赖。 (在严格的语言中,您没有这些问题,因为您只有将变量显式绑定到值的共享。)

答案 2 :(得分:0)

编辑:我认为我在这里错了 - 将类型签名更改为:: Maybe Word64(我觉得这个问题就足够了)也需要永远/有空间泄漏,所以它不可能是旧的整数bug。

您的问题似乎是一个旧的GHC错误(我认为已经修复)与Integer导致空间泄漏。使用-O2编译时,下面的代码在大约150毫秒内完成。

import Data.List
import Data.Word

main = print opl

opl :: Maybe Word32
opl = find vw $ map (\x-> fromDigits (x++[0,0,9]) ) $ sequence [[1],re,[2],re,[3],re,[4],re,[5],re,[6],re,[7],re,[8],re]

vw x = hh^2 == x
    where hh = (round.sqrt.fromIntegral) x

re = [0..9]

fromDigits x = foldl1 (\n m->10*n+m) x

答案 3 :(得分:-2)

由于你正在寻找一个带有vw中找到的那些特征的十九位数字,我会尝试简化映射函数中的构造,只为启动者说fromDigits x*1000+9。附加到列表是O(左侧列表的长度),因此最后抛出最后三位数会损害计算时间。

除此之外(对你们两个人来说),使用严格版本的折叠(foldl1')也会有所帮助。