【问题标题】:How to avoid stack space overflows?如何避免堆栈空间溢出?
【发布时间】:2011-09-21 07:18:08
【问题描述】:

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

为了最简化案例,请查看以下返回斐波那契数的函数的简单实现(取自 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编译。

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

【问题讨论】:

  • @David:阅读此内容以澄清 Haskell 中的 TCO:haskell.org/pipermail/haskell-cafe/2009-March/058607.html
  • fibs !! (10^6) ...这是一个非常大的数字。
  • 三个查找中的每一个都应该在几乎恒定的堆空间中工作 - 但是,您提到 fibs 两次会导致它被保留。而您的fib_at 版本具有指数级复杂性。再多的尾调用优化也无济于事......
  • @camccann> 确实如此。 120719474515914100282640417445683502254279809033418884477131589123371500059867801 ... 6074509262836314172778883659535102587911419872025962380687123573280832406684390626(208988位)。使用类似函数式的 Ruby 代码:Enumerator.new { |y| a,b=0,1;循环{y.yield a; a,b=b,a+b} }.with_index {|elt,ix|如果 ix == 10**6-1 然后打印 elt;休息;结束}
  • @David:我得到了不同的结果,1953282128707757731632.... 这似乎与 wolfram alpha 一致。试试我的代码并告诉我。

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


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

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

    我建议研究这个wiki page on Folds 并看看seq function.

    【讨论】:

    • 您获得了“$ 无用使用奖”(参见Useless Use of cat Award)。
    • @Tsuyoshi:太好了,我在哪里可以收集它? :-)
    • 你不会保存太久的 :) f x = ((id . id) <$>) (id $! id) $ x
    • @yatima2975:这很好混淆。如果有人感到困惑,它相当于更具可读性的f x = (fmap >>= fmap fmap) id (($) $! id) $ x。
    【解决方案3】:

    Theselinkshere会给你一个很好的介绍你的thunk too many 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                   
    

    【讨论】:

    • 谢谢。我的错我相信 Haskell 的程序员不需要关心堆栈限制之类的事情,也不需要像强制评估那样调整代码。顺便提一句。知道如何对第一个函数“fibs = 0 : 1 : zipWith (+) fibs (tail fibs)”使用 bang-pattern 才能有效吗?
    • 是的,zipWith 的严格版本。谢谢米哈伊尔!
    猜你喜欢
    • 2010-11-30
    • 2020-03-08
    • 2014-04-13
    • 1970-01-01
    • 2011-11-23
    • 2010-12-04
    • 2016-07-12
    • 2020-02-12
    • 2015-11-14
    相关资源
    最近更新 更多