【问题标题】:GHC compiled binary stack overflow?GHC编译的二进制堆栈溢出?
【发布时间】:2012-08-14 15:22:54
【问题描述】:

我正在阅读 > Chapter 8,想看看 SumFile.hs 程序如何处理 100 万个数字:

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

当我向程序提供 100 万个整数时:

runhaskell SumFile.hs

但是,当我使用 GHC 编译它时:

ghc SumFile.hs

二进制给出“堆栈空间溢出”错误:

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

我有两个问题:

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

谢谢!

编辑:

好吧,原因是 map,但这是一个使用惰性字节串的修改版本:

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,二进制版本也会占用堆栈空间。

【问题讨论】:

    标签: stack haskell


    【解决方案1】:

    我在#haskell 上与人们进行了讨论,ByteString 版本给出堆栈溢出错误的原因是由于内部未严格评估的嵌套 Just (Sum Num)。

    本质上,当我们映射两个 Maybe (Just Num) 时,比如 Just (Sum 2) 和 Just (Sum 3),foldl' 使用 seq 来生成 Just ((Sum 2) mappend (Sum 3)),即。 seq 严格评估最外层的构造函数(映射两个 Just (Monoid) 以生成一个 Just (Monoid))。在这种情况下,内部 Monoid 没有被严格评估,所以它们被保留为 mappend connected (Sum Num)。这导致 100 万个 mappend connected (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)
    

    【讨论】:

      【解决方案2】:

      首先,简单说明一下:ghc运行时的栈与栈段无关,它是运行时的内部结构,这不是缓冲区溢出类型攻击的来源。

      第二。 Haskell 很懒惰。惰性 io (getContents) 产生惰性列表。 sum 懒惰地产生结果。但是,一旦请求 sum 的结果,它必须递归地挖掘列表,很快就会耗尽堆栈空间(如果愿意,您可以查看源代码)

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

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

      啊。顺便说一下。 GHC 正在优化编译器。这并不常见,但编译程序中的内存泄漏仍然可能存在一些问题,而 ghci 则不存在这些问题,反之亦然。

      【讨论】:

        【解决方案3】:

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

        【讨论】:

          猜你喜欢
          • 2011-05-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-05-18
          • 2014-12-07
          • 2012-01-19
          • 1970-01-01
          • 2010-09-08
          相关资源
          最近更新 更多