【问题标题】:*** Exception: stack overflow : Stack overflow*** 例外:堆栈溢出:堆栈溢出
【发布时间】:2019-05-18 03:06:50
【问题描述】:

我正在学习 Haskell,但我遇到了一个我没想到的异常“堆栈溢出”。

代码相当简单:

type Reals = Double

prod :: Reals -> Reals -> Reals
prod a b = a*b

coprod :: Reals -> Reals -> Reals
coprod a b = a + b - (prod a b)

newsum :: Reals -> Reals -> Int -> Reals
newsum a b n =   
    approxA!!n 
    where
      approxA = a : zipWith coprod approxA approxB 
      approxB = b : zipWith prod approxA approxB 

函数 prod、coprod 旨在在 [0,1] 中的实数上进行计算。函数

newsum a b n

对于 n 趋于无穷大,计算函数 (a,b) -> min(1, a+b)。

你可以试试

newsum 0.5 0.5 10

newsum 0.5 0.5 10000

看到这个。但是,通过调用

newsum 0.5 0.5 10000000

我得到一个堆栈溢出。 newsum 函数很简单,可以进行线性(在 n 中)个 Double 类型的操作。换句话说,构造 [Reals] 类型的(无限)列表的第 n 个元素

大约一个

大约B

需要线性时间。

问题 1:为什么会出现堆栈溢出? | Haskell(或 GHCi)的限制是什么?如何在上述简单程序中(提前)估计它们?

问题 2:Haskell 中是否有一个标志/命令来指示解释器在需要时自动分页内存?我想编写类似于数学的代码,不想花时间考虑内存限制、堆栈溢出等问题。

谢谢!

【问题讨论】:

  • 请一次只回答一个问题。 (不过,这看起来几乎与这里讨论的场景一模一样:wiki.haskell.org/Stack_overflow
  • “我想编写类似于数学的代码,不想花时间考虑内存限制、堆栈溢出等问题。”编程不是这样工作的。除非你很高兴在未来的千年中得到答案,否则就是这样。
  • Doesn't reproduce。尝试编译您的代码,而不是将源代码加载到 GHCi。
  • 该问题专门询问 GHCi。而且我认为 op 希望他们的代码在此处运行是合理的,毕竟这就是您使用优化编译的建议。

标签: haskell


【解决方案1】:

问题在于(!!) 是惰性的:它不会强制对列表中较早的值进行求值,因此ghci 最终会为第 n 项生成一个巨大的表达式,这会溢出其堆栈。

您可以使用运行时选项增加堆栈大小,例如 16GB 堆栈:

$ ghci +RTS -K16G
> newsum 0.5 0.5 10000000
0.9999999000001789

但是如果你超过了系统可用的内存(可能包括虚拟内存/交换页),你就会遇到问题(这里是 Linux OOM 杀手击倒我):

> newsum 0.5 0.5 100000000
Killed
$

这可以通过编写一个函数来解决,该函数强制在访问列表的每个头部之前对其进行评估:

strict :: [a] -> [a]
strict [] = []
strict (x:xs) = seq x (x : strict xs)

seq x y 在需要 y 的值时强制评估 x(到 WHNF,对于 Double 表示完全评估)。 像这样使用strict

newsum' :: Reals -> Reals -> Int -> Reals
newsum' a b n =
    strict approxA!!n
    where
      approxA = a : zipWith coprod approxA approxB
      approxB = b : zipWith prod approxA approxB

现在它在小的常量内存中运行。

另一种方法是使用更严格的数据结构(!a 是严格的a):

data List' a = !a :! List' a

但是你需要为这种类型重新实现(!!)zipWith

【讨论】:

  • 谢谢,我现在明白为什么在这种情况下懒惰会“创造”一个巨大的表达,以及 Seq 如何解决这个问题。谢谢人!非常有用的答案!
  • 再次感谢您之前的回答。不过我现在有一个疑问。使用标志“:set +s”运行 GHCi 以查看执行时间和内存使用情况,我看到程序 newsum' 仍在使用线性(以 n 为单位)内存量:尝试 10000、100000 和 1000000。这怎么可能?我怎样才能真正得到一个常数空间函数?谢谢!
  • @makkiato :set +s 输出整个评估的总分配量,而不是峰值内存使用量(我在另一个终端中使用 tophtop 来可视化)
  • @makkiato 版本之间的区别在于,严格的版本可以在分配过程中垃圾收集分配,而不是仅在最后
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-15
  • 2012-12-20
  • 2011-03-02
  • 2011-08-22
  • 2023-03-04
相关资源
最近更新 更多