导致内存消耗爆炸的标准,看不到CAF

时间:2015-03-29 18:22:58

标签: haskell ghc

基本上我有一个简单的函数调用,其中 当与Criterion结合使用时,会产生 内存消耗爆炸。

假设我有以下程序:

{-# 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 ())

然后这个程序(用O0编译)运行正常,没有 记忆爆炸。

如果我们使用cabal build -v来产生转换的转储 调用的命令,然后将-ddump-simpl -fforce-recomp -O0 -dsuppress-all(在IO/Monadic assign operator causing ghci to explode for infinite list中建议)标记到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 #-}
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 ())]
  ]

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

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

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

2 个答案:

答案 0 :(得分:3)

您不需要检查标准的来源以了解将共享lst ():在评估直接围绕的lambda的主体期间,将共享任何子表达式(因此最多计算一次) 。可以通过重载,各种语法糖结构和编译器优化来引入额外的lambdas,但是这些都不会发生在这里,正如您从Core中看到的那样。

如果您不希望共享lst (),那么您应该将whnf的参数重构为whnf (mysum . lst) ()之类的内容。

答案 1 :(得分:1)

在没有criterion的版本中,lst ()返回的列表会被懒惰地生成,然后在mysum消耗它时逐步收集垃圾,因为没有其他对列表的引用。 / p>

但是,对于criterion版本,请查看definition of whnf

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

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 #-}

上面x中的go似乎最终会绑定到lst ()返回的列表,而n是基准测试的迭代次数。当第一个基准测试迭代完成后,x将全部被评估,但这次它不能被垃圾收集:它仍然保存在内存中,因为它是共享并且通过以下迭代递归go f x (n-1)