问题描述
考虑以下程序.它永远运行并且没有任何用处,但是 ghci 中的内存消耗是恒定的:
Consider the following program. It runs forever and does nothing useful, but the memory consumption in ghci is constant :
--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"
现在分配给 x
在 test
的 do
块中.
Now consider the following trivially modified version of the above. When this program is run in ghci the memory explodes. The only difference is that print "test"
is now assigned to x
in the do
block of test
.
--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炸掉?
Why does changing print "test"
to x <- print "test"
cause ghci to blow up?
附言我在尝试理解 内存爆炸时遇到了这个问题在将一个惰性字节串写入 ghci 中的文件时,那里的问题(我认为)基本上就是上述问题.谢谢
p.s. I came across this when trying to understand Memory exploding upon writing a lazy bytestring to file in ghci, and the problem there (I think) essentially distils to the above. Thanks
推荐答案
免责声明:我不是 GHCi 专家,也不擅长 GHC 核心.现在我已经失去了我的可信度,让我们试着了解会发生什么:
Disclaimer: I'm not a GHCi expert, and also not that good with GHC core. Now that I've lost my credibility, lets try to understand what happens:
GHCi 保留所有评估的 CAF:
通常,对加载模块中的顶级表达式(也称为 CAF 或常量应用形式)的任何计算都会在计算之间保留.
现在您可能想知道为什么两个版本之间有如此大的差异.让我们用 -ddump-simpl
来看看核心.请注意,当您自己转储程序时,您可能希望删除 -dsuppress-all
.
Now you might wonder why there's such a big difference between both versions. Lets have a look at the core with -ddump-simpl
. Note that you might want to drop -dsuppress-all
when you dump the programs yourself.
❯ 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..]
的位置,几乎在最后:
The important part is the location of [1..]
, almost at the end:
enumFrom $fEnumInt (I# 1))
如您所见,该列表不是 CAF.但是如果我们改为使用爆炸版本会发生什么?
As you can see, the list isn't a CAF. But what happens if we instead use the exploding version?
❯ 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
也将保留.
There's suddenly a new top-level expression, namely lst_rq4
, which generates the list. And as seen before, GHCi retains the evaluations of top-level expressions, so lst_rq4
will also be retained.
现在可以选择放弃评估:
Now there is an option to discard the evaluations:
打开 +r
会导致顶级表达式的所有计算在每次计算后都被丢弃(它们在单次计算期间仍会保留).
但是由于在一次评估期间它们仍然保留",即使 :set +r
在这种情况下也不会帮助你.不幸的是,我无法回答为什么 GHC 引入了一个新的顶级表达式.
But since "they are still retained during a single evaluation" even :set +r
won't help you in this case. Unfortunately I cannot answer why GHC introduces a new top-level expression.
列表仍然是顶级表达式:
The list is still a top-level expression:
main2
main2 = eftInt 1 2147483647
有趣的是,GHC 实际上不会创建无限列表,因为 Int
是有界的.
Funny enough, GHC actually doesn't create an infinite list, since Int
is bounded.
在这种情况下,如果您将列表放在测试中,您可以摆脱它:
In this case you can get rid of it if you place the list in test:
test = do
x <- print "test"
rList [1..]
这将阻止 GHC 创建顶级表达式.
This will prevent GHC from creating a top-level expression.
但是,我真的无法就此给出一般性建议.不幸的是,我的 Haskell-fu 还不够好.
However, I can't really give a general advise on this. Unfortunately, my Haskell-fu isn't yet good enough.
这篇关于IO/Monadic 分配运算符导致 ghci 因无限列表而爆炸的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!