导致内存消耗激增的标准,看不到 CAF
Criterion causing memory consumption to explode, no CAFs in sight
基本上我有一个简单的函数调用,
当与 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
在没有 criterion
的版本中,lst ()
返回的列表会延迟生成,然后在 mysum
消耗它的同时逐步进行垃圾收集,因为没有其他对该列表的引用.
对于 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 #-}
上面 go
中的 x
似乎最终会绑定到您的 lst ()
返回的列表,而 n
是基准测试的迭代次数.当第一个基准测试迭代完成时,x
将全部被评估,但这次它不能被垃圾收集:它仍然保留在内存中,因为它与 shared通过递归 go f x (n-1)
.
进行迭代
您无需检查标准的来源就知道 lst ()
将被共享:在立即计算主体的过程中,任何子表达式都将被共享(因此最多计算一次)周围的拉姆达。可能会通过重载、各种语法糖构造和编译器优化引入额外的 lambda,但是 none 正如您从核心中看到的那样,这里发生了这种情况。
如果您不希望 lst ()
被共享,那么您应该将 whnf
的参数重构为类似 whnf (mysum . lst) ()
.
的参数
基本上我有一个简单的函数调用, 当与 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
在没有 criterion
的版本中,lst ()
返回的列表会延迟生成,然后在 mysum
消耗它的同时逐步进行垃圾收集,因为没有其他对该列表的引用.
对于 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 #-}
上面 go
中的 x
似乎最终会绑定到您的 lst ()
返回的列表,而 n
是基准测试的迭代次数.当第一个基准测试迭代完成时,x
将全部被评估,但这次它不能被垃圾收集:它仍然保留在内存中,因为它与 shared通过递归 go f x (n-1)
.
您无需检查标准的来源就知道 lst ()
将被共享:在立即计算主体的过程中,任何子表达式都将被共享(因此最多计算一次)周围的拉姆达。可能会通过重载、各种语法糖构造和编译器优化引入额外的 lambda,但是 none 正如您从核心中看到的那样,这里发生了这种情况。
如果您不希望 lst ()
被共享,那么您应该将 whnf
的参数重构为类似 whnf (mysum . lst) ()
.