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