标准导致内存消耗爆炸,没有CAF在眼前 [英] Criterion causing memory consumption to explode, no CAFs in sight

查看:85
本文介绍了标准导致内存消耗爆炸,没有CAF在眼前的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

基本上我有一个简单的函数调用,当与 Criterion <

假设我有以下程序:



<$
模块主要其中
import Criterion.Main $ b $ {$#$} $ b import Data.List

num :: Int
num = 10000000

lst :: a - > [Int]
lst _ = [1,2..num]

myadd :: Int - > Int - > Int
myadd!x!y = let!result = x + y in
result

mysum = foldl'myadd 0

main :: IO ()
main = do
print $ mysum(lst())

然后这个程序(用O0编译)运行良好,没有
的内存爆炸。



如果我们使用 cabal build -v 产生一个调用的编译
命令的转储,然后标记 -ddump-simpl -fforce-recomp -O0 -dsuppress-all (建议在
IO / Monadic赋值运算符导致ghci爆炸无限列表)到 ghc - -make -no-link ... 命令,我们得到以下核心:

  num 
num = I#10000000

lst
lst = \ @ a_a3Yn _ - > enumFromThenTo $ fEnumInt(I#1)(I#2)num

myadd
myadd =
\ x_a3Cx y_a3Cy - >
case x_a3Cx of x1_X3CC {I#ipv_s4gX - >
case y_a3Cy of y1_X3CE {I#ipv1_s4h0 - >
+ $ fNumInt x1_X3CC y1_X3CE
}
}

mysum
mysum = foldl'myadd(I#0)

main
main =
print
$ fShowInt(mysum(enumFromThenTo $ fEnumInt(I#1)(I#2)num))

main
main = runMainIO main

似乎没有CAF正在生产,与
一致该程序不会爆炸的事实。现在,如果我
运行使用条件1.1.0.0的以下程序:

  { - #OPTIONS_GHC -fno-cse # - } 
{ - #LANGUAGE BangPatterns# - }
模块Main其中
import Criterion.Main
import Data.List

num :: Int
num = 10000000

lst :: a - > [Int]
lst _ = [1,2..num]

myadd :: Int - > Int - > Int
myadd!x!y = let!result = x + y in
result

mysum = foldl'myadd 0

main :: IO ()
main = defaultMain [
bgroupsummation
[benchmysum$ whnf mysum(lst())]
]

然后内存消耗爆炸。然而,打印
核心产量:

pre $ $ $ $ $ $ $ $ $ $ $#$ 10000000

lst
lst = \ @ a_a3UV _ - > enumFromThenTo $ fEnumInt(I#1)(I#2)num

myadd
myadd =
\ x_a3Cx y_a3Cy - >
case x_a3Cx of x1_X3CC {I#ipv_s461 - >
case y_a3Cy of y1_X3CE {I#ipv1_s464 - >
+ $ fNumInt x1_X3CC y1_X3CE
}
}

mysum
mysum = foldl'myadd(I#0)

main
main =
defaultMain
(:(bgroup
(unpackCString#summation#)
(:( bench bench
(unpackCString#mysum #)
(whnf mysum(enumFromThenTo $ fEnumInt(I#1)(I#2)num)))
([])))
([]))

main
main = runMainIO main

似乎没有CAF存在产生的。因此,为什么
做后者的程序,它使用标准,导致内存消耗爆炸,而前一个程序
没有?我使用GHC版本7.8.3

解决方案

在没有标准的版本中,由 lst()返回的列表被懒惰地产生,然后增量地收集垃圾,而 mysum 消耗它,因为那里没有其他的引用。



对于标准版本,请看 whnf

  whnf ::(a  - > b) - > a  - >可执行基准
whnf = pureFunc id
{ - #INLINE $#
$ $ $ $和 pureFunc

  pureFunc ::(b  - > c) - > (a  - > b) - > a  - >基准
pureFunc降低f0 x0 =基准$ f0 x0
其中fx n
| n< = 0 = return()
|否则=评估(减少(f x​​))>>去fx(n-1)
{ - #INLINE pureFunc# - }

看来( x )在 go 中最终会被绑定到您的 lst() ,而 n 是基准测试的迭代次数。当第一次基准迭代完成时, x 将全部被评估,但是这次它不能被垃圾收集:它仍然保存在内存中,因为它是共享的通过递归去掉fx(n-1)


Basically I have a simple function call, which when used in conjunction with Criterion, results in the memory consumption exploding.

Suppose I have the following program :

{-# OPTIONS_GHC -fno-cse #-}
{-# LANGUAGE BangPatterns #-}
module Main where
import Criterion.Main
import Data.List

num :: Int
num = 10000000

lst :: a -> [Int]
lst _ = [1,2..num]

myadd :: Int -> Int -> Int
myadd !x !y = let !result = x + y in
  result

mysum = foldl' myadd 0

main :: IO ()
main = do
  print $ mysum (lst ())

Then this program (compiled with O0) runs fine, without the memory exploding.

If we use cabal build -v to yield a dump of the compilation commands invoked, and then tag -ddump-simpl -fforce-recomp -O0 -dsuppress-all (suggested in IO/Monadic assign operator causing ghci to explode for infinite list) to the end of the ghc --make -no-link ... command, we get the following core :

num
num = I# 10000000

lst
lst = \ @ a_a3Yn _ -> enumFromThenTo $fEnumInt (I# 1) (I# 2) num

myadd
myadd =
  \ x_a3Cx y_a3Cy ->
    case x_a3Cx of x1_X3CC { I# ipv_s4gX ->
    case y_a3Cy of y1_X3CE { I# ipv1_s4h0 ->
    + $fNumInt x1_X3CC y1_X3CE
    }
    }

mysum
mysum = foldl' myadd (I# 0)

main
main =
  print
    $fShowInt (mysum (enumFromThenTo $fEnumInt (I# 1) (I# 2) num))

main
main = runMainIO main

It seems that no CAFs are being produced, which is consistent with the fact that the program does not explode. Now if I run the following program which uses criterion 1.1.0.0 :

{-# OPTIONS_GHC -fno-cse #-}
{-# LANGUAGE BangPatterns #-}
module Main where
import Criterion.Main
import Data.List

num :: Int
num = 10000000

lst :: a -> [Int]
lst _ = [1,2..num]

myadd :: Int -> Int -> Int
myadd !x !y = let !result = x + y in
  result

mysum = foldl' myadd 0

main :: IO ()
main = defaultMain [
  bgroup "summation" 
    [bench "mysum" $ whnf mysum (lst ())]
  ]

then the memory consumption explodes. However printing the core yields :

num
num = I# 10000000

lst
lst = \ @ a_a3UV _ -> enumFromThenTo $fEnumInt (I# 1) (I# 2) num

myadd
myadd =
  \ x_a3Cx y_a3Cy ->
    case x_a3Cx of x1_X3CC { I# ipv_s461 ->
    case y_a3Cy of y1_X3CE { I# ipv1_s464 ->
    + $fNumInt x1_X3CC y1_X3CE
    }
    }

mysum
mysum = foldl' myadd (I# 0)

main
main =
  defaultMain
    (: (bgroup
      (unpackCString# "summation"#)
      (: (bench
            (unpackCString# "mysum"#)
            (whnf mysum (enumFromThenTo $fEnumInt (I# 1) (I# 2) num)))
         ([])))
       ([]))

main
main = runMainIO main

and it seems that no CAFs are being produced. Therefore why does the latter program, which uses criterion, result in the memory consumption exploding, whereas the former program does not? I am using GHC version 7.8.3

解决方案

In your version without criterion, the list returned by lst () gets produced lazily and then incrementally garbage collected while mysum consumes it, since there are no other references to the list.

For the criterion version, however, look at the definition of whnf:

whnf :: (a -> b) -> a -> Benchmarkable
whnf = pureFunc id
{-# INLINE whnf #-}

and pureFunc:

pureFunc :: (b -> c) -> (a -> b) -> a -> Benchmarkable
pureFunc reduce f0 x0 = Benchmarkable $ go f0 x0
  where go f x n
          | n <= 0    = return ()
          | otherwise = evaluate (reduce (f x)) >> go f x (n-1)
{-# INLINE pureFunc #-}

It seems like x in go above would eventually become bound to the list returned by your lst (), while n is the number of iterations for the benchmarking. When the first benchmark iteration is finished, x will all have been evaluated, but this time it cannot be garbage collected: It is still kept in memory because it is shared with the following iterations through the recursive go f x (n-1).

这篇关于标准导致内存消耗爆炸,没有CAF在眼前的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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