Haskell - 垃圾收集无法回收足够的空间
Posted
技术标签:
【中文标题】Haskell - 垃圾收集无法回收足够的空间【英文标题】:Haskell - Garbage collection fails to reclaim sufficient space 【发布时间】:2013-01-22 21:17:03 【问题描述】:我正在做一个程序来将所有奇数相加,直到 n:
oddSum' n result | n==0 = result
| otherwise = oddSum' (n-1) ((mod n 2)*(n)+result)
oddSum n = oddSum' n 0
我的输入有两个错误(我把它们放在下面),我正在使用尾递归,为什么会发生堆栈溢出? (注意:我在 Ubuntu 上使用 Hugs)
奇和 20000 错误 - 控制堆栈溢出
奇和 100000 错误 - 垃圾回收未能回收足够的空间
【问题讨论】:
尝试用ghc -O
编译它,它的严格分析器可能会在第二个参数中检测到oddSum'
是严格的,并插入所需的seq
本身。
【参考方案1】:
oddSum 3
oddSum 2 ((2 mod 2)*2 + 3)
oddSum 1 ((1 mod 2)*1 + ((2 mod 2)*2 + 3))
您正在 result
变量中构建一个巨大的 thunk。
一旦你计算了这个,所有的计算都必须一次完成,然后堆栈溢出,因为,例如,要执行加法,你首先必须计算操作数,以及操作数中加法的操作数。
如果 thunk 变得太大,就会发生堆溢出。
尝试使用
result `seq` ((mod n 2) * n + result)
在递归中。
【讨论】:
谢谢!我认为将括号放在 mod n 2 中,使其成为 (mod n 2) 我会强制对其进行评估。 不,直到编译器可以看到函数中实际需要该值。例如,如果您将otherwise
替换为result >= 0
,那么这将强制生成的代码在每次递归中评估result
。 (但我不建议这样做,这是一种损害可读性的技巧,也不能在任何地方应用。)【参考方案2】:
首先,不要使用 Hugs,它不受支持。通过优化 GHC 机会,这样的事情将被编译成一个紧密高效的循环(仍然你的代码不会很好)。
非严格的累加器总是存在构建大量 thunk 的风险。一种解决方案是使其严格:
-# LANGUAGE BangPatterns #-
oddSum' n !acc | n==0 = acc
| otherwise = oddSum' (n-1) $ (n`mod`2)*n + acc
当然,这不是惯用的;显式编写尾递归函数很麻烦,并且在 Haskell 中有些不受欢迎。大多数这类事情都可以用库函数很好地完成,比如
oddSum n = sum [1, 3 .. n]
...不幸的是,它也不能在恒定空间中可靠地工作。它确实适用于严格版本的折叠(sum
只是一个专门化的版本),
import Data.List
oddSum n = foldl' (+) 0 [1, 3 .. n]
【讨论】:
我想过用列表来做,但选择了我自己的实现作为一种学习方式,并且不要太依赖已经实现的功能。我在我大学的计算机实验室,它只有 Hugs 和 stundets 无法安装任何东西(我在家里使用 ghc)。 不支持拥抱,但拥抱有效。它没有问题,而且编译速度很快(当我编辑我的文件时它甚至会注意到,所以我不需要:r
)。它仅限于 Haskell 98 并且不支持 GHC 扩展,但它非常适合继续学习,部分原因是错误消息更加清晰,特别是它给出了类型错误,其中 ghc 告诉您编写实例声明。
使用有限堆栈/堆的 Hugs 暴露了 OP 对 Haskell 中评估的一个重要误解,ghc -O2
可能隐藏了它。我们喜欢尽快发现我们做错了什么,不是吗? ghc -O2
在编码过程中比 :r
慢(在 ghci 或拥抱中),交互性较差,优化过早。
顺便说一句,我认为使用$!
比砰砰声更好。 oddSum' n acc | n==0 = acc
然后| otherwise = oddSum' (n-1) $! (mod n 2)*n + acc
以上是关于Haskell - 垃圾收集无法回收足够的空间的主要内容,如果未能解决你的问题,请参考以下文章