haskell - IO/Monadic 分配运算符导致 ghci 为无限列表爆炸

标签 haskell memory-leaks ghci

考虑以下程序。它永远运行并且没有任何用处,但是 ghci 中的内存消耗是恒定的:

--NoExplode.hs
module Main (main) where

test :: [Int] -> IO()
test lst = do
  print "test"
  rList lst

rList :: [Int] -> IO ()
rList [] = return ()
rList (x:xs) = do
  rList xs

main = do
  test [1..]

现在考虑以下对上述内容的简单修改版本。当这个程序在 ghci 中运行时,内存会爆炸。唯一的区别是 print "test"现在分配给 xdo test block .
--Explode.hs
module Main (main) where

test :: [Int] -> IO()
test lst = do
  x <- print "test"
  rList lst

rList :: [Int] -> IO ()
rList [] = return ()
rList (x:xs) = do
  rList xs

main = do
  test [1..]

为什么要更改 print "test"x <- print "test"导致ghci炸毁?

p.s.我在试图理解 Memory exploding upon writing a lazy bytestring to file in ghci 时遇到了这个问题,并且那里的问题(我认为)本质上是上述问题。谢谢

最佳答案

免责声明 : 我不是 GHCi 专家,对 GHC 核心也不是很好。现在我已经失去了可信度,让我们试着了解会发生什么:

GHCi 和 CAF

GHCi retains all evaluated CAFs :

Normally, any evaluation of top-level expressions (otherwise known as CAFs or Constant Applicative Forms) in loaded modules is retained between evaluations.



现在您可能想知道为什么两个版本之间存在如此大的差异。让我们看看 -ddump-simpl 的核心.请注意,您可能想要删除 -dsuppress-all当您自己转储程序时。

你的程序转储

非爆款:

❯ ghc SO.hs -ddump-simpl -fforce-recomp -O0 -dsuppress-all
[1 of 1] Compiling Main             ( SO.hs, SO.o )

==================== Tidy Core ====================
Result size of Tidy Core = {terms: 29, types: 28, coercions: 0}

$dShow_rq2
$dShow_rq2 = $fShow[] $fShowChar

Rec {
rList_reI
rList_reI =
  \ ds_dpU ->
    case ds_dpU of _ {
      [] -> return $fMonadIO ();
      : x_aho xs_ahp -> rList_reI xs_ahp
    }
end Rec }

main
main =
  >>
    $fMonadIO
    (print $dShow_rq2 (unpackCString# "test"))
    (rList_reI (enumFrom $fEnumInt (I# 1)))

main
main = runMainIO main

重要的部分是 [1..] 的位置,几乎在最后:
enumFrom $fEnumInt (I# 1))

如您所见,该列表不是 CAF。但是如果我们改为使用爆炸版本会发生什么?

爆款

❯ ghc SO.hs -ddump-simpl -fforce-recomp -O0 -dsuppress-all
[1 of 1] Compiling Main             ( SO.hs, SO.o )

==================== Tidy Core ====================
Result size of Tidy Core = {terms: 32, types: 31, coercions: 0}

$dShow_rq3
$dShow_rq3 = $fShow[] $fShowChar

Rec {
rList_reI
rList_reI =
  \ ds_dpV ->
    case ds_dpV of _ {
      [] -> return $fMonadIO ();
      : x_ahp xs_ahq -> rList_reI xs_ahq
    }
end Rec }

lst_rq4
lst_rq4 = enumFrom $fEnumInt (I# 1)

main
main =
  >>=
    $fMonadIO
    (print $dShow_rq3 (unpackCString# "test"))
    (\ _ -> rList_reI lst_rq4)

main
main = runMainIO main

突然出现了一个新的顶级表达式,即 lst_rq4 ,生成列表。如前所述,GHCi 保留了顶级表达式的求值,所以 lst_rq4也会被保留。

现在有一个选项可以放弃评估:

Turning on +r causes all evaluation of top-level expressions to be discarded after each evaluation (they are still retained during a single evaluation).



但是由于“它们在单次评估期间仍然保留”,甚至 :set +r在这种情况下不会帮助你。不幸的是,我无法回答为什么 GHC 引入了一个新的顶级表达式。

为什么这甚至发生在优化的代码中?

该列表仍然是一个顶级表达式:
main2
main2 = eftInt 1 2147483647

有趣的是,GHC 实际上并没有创建无限列表,因为 Int是有界的。

怎样才能摆脱泄漏?

在这种情况下,如果您将列表放入测试中,您可以摆脱它:
test = do
   x <- print "test"
   rList [1..]

这将阻止 GHC 创建顶级表达式。

但是,我真的不能就此给出一般性的建议。不幸的是,我的 Haskell-fu 还不够好。

关于haskell - IO/Monadic 分配运算符导致 ghci 为无限列表爆炸,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24986296/

相关文章:

opengl - 从 OpenGL/GLUT 应用程序读取像素

haskell - 将非 monadic 函数绑定(bind)到 monad

haskell - 是的,withAsync

Python/lxml 消耗了太多内存

c++ - 使用类 vector 时会遇到内存泄漏吗? (C++)

com - CComBSTR 的内存泄漏

haskell - 从边列表构造树: missing Leaf nodes

haskell - Haskell 中带有 `Ord` 参数的 Memoize 函数

haskell - ghci 中的命令 ls 或 dir

Haskell:数据类型