如何避免堆栈空间溢出?

时间:2011-06-23 14:39:50

标签: haskell memory-leaks memory-management stack-overflow

如果我需要获取包含内存密集元素的大型列表的值,我对GHC抛出堆栈溢出感到有些惊讶。 我确实预计GHC会有TCO,所以我永远不会遇到这种情况。

为了简化大多数情况,请查看以下直接实现返回Fibonacci数的函数(取自HaskellWiki)。目标是显示百万分之一。

import Data.List

# elegant recursive definition
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

# a bit tricky using unfoldr from Data.List
fibs' = unfoldr (\(a,b) -> Just (a,(b,a+b))) (0,1)

# version using iterate
fibs'' = map fst $ iterate (\(a,b) -> (b,a+b)) (0,1)

# calculate number by definition
fib_at 0 = 0
fib_at 1 = 1
fib_at n = fib_at (n-1) + fib_at (n-2)

main = do
    {-- All following expressions abort with
        Stack space overflow: current size 8388608 bytes.
        Use `+RTS -Ksize -RTS' to increase it.
     --}
    print $ fibs !! (10^6)
    print . last $ take (10^6) fibs
    print $ fibs' !! (10^6)
    print $ fibs'' !! (10^6)

    -- following expression does not finish after several 
    -- minutes
    print $ fib_at (10^6)

源代码使用ghc -O2进行编译。

我做错了什么?我想避免使用增加的堆栈大小或其他特定的编译器选项重新编译。

3 个答案:

答案 0 :(得分:9)

These links here会很好地介绍太多thunk (空间泄漏)的问题。

如果你知道要注意什么(并且有一个不错的懒惰评估模型),那么解决它们很容易,例如:

{-# LANGUAGE BangPatterns #-}                        

import Data.List                                     

fibs' = unfoldr (\(!a,!b) -> Just (a,(b,a+b))) (0,1) 

main = do                                            
    print $ fibs' !! (10^6)  -- no more stack overflow                   

答案 1 :(得分:3)

所有的定义(无用的fib_at除外)都会延迟所有+操作,这意味着当你选择了第一百万个元素时,它就是一个带有一百万个延迟添加的thunk。你应该尝试更严格的事情。

答案 2 :(得分:1)

正如其他人所指出的那样,Haskell很懒,你必须强制评估thunk以避免堆栈溢出。 在我看来,这个版本的纤维应该可以达到10 ^ 6:

fibs' = unfoldr (\(a,b) -> Just (seq a (a, (b, a + b) )))  (0,1)

我建议您研究wiki page on Folds,并查看seq function.