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 - 垃圾收集无法回收足够的空间的主要内容,如果未能解决你的问题,请参考以下文章

JVM垃圾回收算法解析

G1 GC垃圾收集流程

jvmJava垃圾回收

java垃圾回收

java垃圾回收

垃圾收集器与内存分配策略---垃圾收集算法