GHC编译二进制堆栈溢出?

时间:2012-08-18 11:50:06

标签: stack haskell

我正在读<<现实世界haskell>>第8章想看看SumFile.hs程序如何处理100万个数字:

main :: IO ()
main = do
  contents <- getContents
  print (sumFile contents)
    where sumFile = sum . map read . words

当我通过以下方式向程序提供100万个整数时:

runhaskell SumFile.hs&lt; data.txt,程序给出了正确的结果。

然而,当我使用GHC编译它时:

ghc SumFile.hs

二进制文件出现“堆栈空间溢出”错误:

./SumFile < data.txt 
Stack space overflow: current size 8388608 bytes.
Use `+RTS -Ksize -RTS' to increase it.

我有两个问题:

  1. 导致堆栈空间使用的原因是什么?
  2. 为什么编译版本与解释版本不同,我该怎么办?
  3. 谢谢!

    编辑:

    好的原因是map,但这是一个使用lazy bytestring的修改版本:

    import qualified Data.ByteString.Lazy as L
    import qualified Data.ByteString.Lazy.Char8 as LCHAR
    import Data.Monoid
    import Data.List
    
    main :: IO ()
    main = do
      contents <- L.getContents
      case sumFile contents of
        Nothing -> print "Invalid input"
        Just s -> print $ getSum s
       where sumFile = foldl' mappend (Just (Sum 0)) . map ((fmap Sum) . (fmap fst) . LCHAR.readInt) . (LCHAR.words)
    

    结果是一样的:即使我没有使用sum,二进制版本也会占用堆栈空间。

3 个答案:

答案 0 :(得分:1)

我与#haskell上的人讨论过,ByteString版本给出堆栈溢出错误的原因是嵌套的Just(Sum Num),其中内部部分未经过严格评估。

基本上,当我们使用两个Maybe(Just Num),比如Just(Sum 2)和Just(Sum 3)时,foldl'使用seq生成Just((Sum 2)mappend(Sum 3)),即。 seq严格评估最外面的构造函数(mappend two Just(Monoid)生成一个Just(Monoid))。在这种情况下,内部Monoid不会被严格评估,因此它们保持为mappend连接(Sum Num)。这导致100万mappend连接(Sum Num)包裹在Just。

所以#haskell上的Saizan给出了这个严格评估Maybe(Sum Num)内部部分的版本

import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Lazy.Char8 as LCHAR
import Data.Monoid
import Data.List

forceMaybe Nothing = Nothing
forceMaybe (Just x) = x `seq` (Just x)

main :: IO ()
main = do
  contents <- L.getContents
  case sumFile contents of
    Nothing -> print "Invalid input"
    Just s -> print $ getSum s
   where sumFile = foldl' (\ x y -> forceMaybe (x `mappend` y)) (Just (Sum 0)) . map ((fmap Sum) . (fmap fst) . LCHAR.readInt) . (LCHAR.words)

答案 1 :(得分:0)

我查看了本书的在线版本,在该程序下有一些讨论,它使用堆栈空间的原因是由于地图,用foldl替换地图'解决了问题。

答案 2 :(得分:0)

首先,简单说明:ghc运行时堆栈没有处理堆栈段,它是运行时的内部结构,这不是缓冲区溢出类型攻击的来源。

二。哈斯克尔很懒。 Lazy io(getContents)生成懒惰列表。总和懒得产生结果。但是,一旦请求求和的结果,它必须递归地挖掘列表,快速耗尽堆栈空间(如果希望你可以查看源代码)

为了避免它,你必须使用严格版本的和,它应该消除问题。标准库对这种情况有特殊功能,foldl' - foldl的严格版本。使用foldl' (+) 0代替总和应该可以消除问题

第三。当使用惰性IO时,堆栈空间泄漏是非常常见的问题。如果切换到基于迭代的IO,则可以解决该问题。否则,应该学会在需要的地方添加严格注释。

阿。顺便说一下。 GHC正在优化编译器。这种情况并不常见,但仍有可能在编译程序中出现内存泄漏问题,并且不能使用ghci,反之亦然。