基本上我有一个简单的函数调用,其中 当与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
答案 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)
。