IO / Monadic赋值运算符导致ghci爆炸无限列表 [英] IO/Monadic assign operator causing ghci to explode for infinite list

查看:116
本文介绍了IO / Monadic赋值运算符导致ghci爆炸无限列表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑下面的程序。它永远运行,没有任何用处,但ghci中的内存消耗是不变的:

   -  NoExplode.hs 
模块Main(main)where

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

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

main = do
test [1 ..]

现在考虑以上的简单修改版本。当这个程序在ghci中运行时,内存爆炸了。唯一的区别是, printtest现在分配给中的 x / code> test的块

   -  Explode.hs 
模块Main(main)其中

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

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

main = do
test [1 ..]

为什么要更改打印test to x < - printtest导致ghci炸毁?



ps当我试图理解内存爆炸在写一个懒惰的字符串文件在ghci ,那里的问题(我认为)基本上融入上述。谢谢 核心。现在,我已经失去了我的信誉,让我们试着了解发生了什么:



GHCi和CAFs



GHCi 保留所有评估过的CAF


通常情况下,在评估过程中保留加载模块中顶级表达式(也称为CAF或常量应用表单)的任何评估。


现在你可能会想知道为什么两个版本之间有这么大的区别。让我们看看 -ddump-simpl 的核心。请注意,当您自行转储程序时,您可能希望删除 -dsuppress-all 您程序的转储

非爆炸版本:



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

== ================== Tidy Core ====================
Tidy Core的结果大小= {terms:29,类型:28,强制:0}

$ dShow_rq2
$ dShow_rq2 = $ fShow [] $ fShowChar

Rec {
rList_reI
rList_reI =
\ ds_dpU - >
case ds_dpU of _ {
[] - >返回$ 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 
[编译Main(SO.hs,SO.o)

====================整洁的核心====================
Tidy Core的结果大小= {terms:32,types:31,coercions:0}

$ dShow_rq3
$ dShow_rq3 = $ fShow [] $ fShowChar

Rec {
rList_reI
rList_reI =
\ ds_dpV - >
案例ds_dpV of _ {
[] - >返回$ 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 也将被保留。



现在可以选择放弃评估:


开启 + r 原因所有对顶级表达式的评估在每次评估后都会被丢弃(它们在单次评估期间仍然保留)。

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



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



  main2 
main2 = eftInt 1 2147483647

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

怎样才能摆脱泄漏?

在这种情况下,你如果您将列表放入中,可以将其删除:

  test = do 
x < - printtest
rList [1 ..]

这会防止GHC创建顶级表达式。



然而,我无法给出一般性建议。不幸的是,我的Haskell-fu还不够好。


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..]

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..]

Why does changing print "test" to x <- print "test" cause ghci to blow up?

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

解决方案

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 and CAFs

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.

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.

Dumps of your programs

Non-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: 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

The important part is the location of [1..], almost at the end:

enumFrom $fEnumInt (I# 1))

As you can see, the list isn't a CAF. But what happens if we instead use the exploding version?

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

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:

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

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.

Why does this even happen in optimized code?

The list is still a top-level expression:

main2
main2 = eftInt 1 2147483647

Funny enough, GHC actually doesn't create an infinite list, since Int is bounded.

How can one get rid of the leak?

In this case you can get rid of it if you place the list in test:

test = do
   x <- print "test"
   rList [1..]

This will prevent GHC from creating a top-level expression.

However, I can't really give a general advise on this. Unfortunately, my Haskell-fu isn't yet good enough.

这篇关于IO / Monadic赋值运算符导致ghci爆炸无限列表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆