Haskell 导致内存消耗爆炸的标准,看不到CAF
基本上我有一个简单的函数调用 与一起使用时,会导致 内存消耗呈爆炸式增长 假设我有以下程序:Haskell 导致内存消耗爆炸的标准,看不到CAF,haskell,ghc,Haskell,Ghc,基本上我有一个简单的函数调用 与一起使用时,会导致 内存消耗呈爆炸式增长 假设我有以下程序: {-# 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 ->
{-# 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编译)运行良好,没有
记忆爆炸
如果我们使用cabalbuild-v
生成编译转储
调用命令,然后标记-ddump siml-fforce recomp-O0-dsuppress all
(在中建议)到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
而且似乎没有生产咖啡馆。所以为什么呢
使用标准的后一个程序是否会导致内存消耗爆炸,而前一个程序
不是吗?我在您没有
标准的版本中使用GHC版本7.8.3,由lst()
返回的列表被惰性地生成,然后在mysum
使用它时被增量垃圾收集,因为没有其他对列表的引用
但是,对于标准
版本,请查看:
和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 #-}
pureFunc::(b->c)->(a->b)->a->Benchmarkable
pureFunc reduce f0 x0=基准$go f0 x0
去哪里
|n>GOFx(n-1)
{-#内联pureFunc}
上面的go
中的x
似乎最终会绑定到您的lst()
返回的列表,而n
是基准测试的迭代次数。当第一次基准测试迭代完成时,x
将全部被评估,但这次它不能被垃圾收集:它仍然保存在内存中,因为它通过递归的go f x(n-1)
与后面的迭代共享,您不需要检查标准源就知道lst()
将被共享:在计算紧邻lambda的主体时,任何子表达式都将被共享(因此最多计算一次)。额外的lambda可能会通过重载、各种语法糖结构和编译器优化引入,但这些都不会像您从核心看到的那样在这里发生
如果您不想共享lst()
,那么您应该将whnf
的参数重构为类似whnf(mysum.lst)(
)谢谢,我在使用O3
优化之前确实尝试过whnf(mysum.lst)(
,但是我发现基准时间独立于num
,所以我放弃了这个想法。但是,使用O0
再次运行此命令,表明它明显依赖于num
。我在
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 #-}