让我们比较实际数字。如果在没有优化的情况下运行,您的 kolakoski 版本将使用大约 70k:
$ ghc --make Kolakoski-Unit && ./Kolakoski-Unit +RTS -s
2
在堆中分配了 288,002,359,096 字节
GC 期间复制了 1,343,933,816 字节
67,576 字节最大驻留(422 个样本)
52,128 字节最大斜率
2 MB 总内存在使用(0 MB 由于碎片丢失)
总时间(经过) 平均暂停 最大暂停
Gen 0 551615 colls, 0 par 1.89s 2.30s 0.0000s 0.0001s
Gen 1 422 colls,0 par 0.02s 0.02s 0.0001s 0.0001s
初始化时间 0.00s(经过 0.00s)
MUT时间37.34s(经过37.25s)
GC时间1.91s(经过2.33s)
退出时间 0.00s(经过 0.00s)
总时间 39.25s(经过 39.58s)
%GC 时间 4.9%(经过 5.9%)
分配速率 7,712,197,063 字节/MUT 秒
生产力占总用户的 95.1%,占总使用时间的 94.3%
经过优化,它使用约 4GB(尽管任务管理器中的实际内存使用量高达约 6GB)。
$ ghc --make Kolakoski-Unit -O && ./Kolakoski-Unit +RTS -s
2
在堆中分配了 64,000,066,608 字节
GC 期间复制了 27,971,527,816 个字节
3,899,031,480 字节最大驻留(34 个样本)
91,679,728 字节最大斜率
9549 MB 总内存在使用(0 MB 由于碎片丢失)
总时间(经过) 平均暂停 最大暂停
Gen 0 122806 colls, 0 par 8.67s 9.48s 0.0001s 0.0148s
Gen 1 34 colls, 0 par 11.55s 69.78s 2.0524s 56.2970s
初始化时间 0.00s(经过 0.00s)
MUT时间8.80s(经过8.43s)
GC 时间 20.22s(经过 79.26s)
退出时间 0.03s(经过 0.89s)
总时间 29.05s(经过 88.58s)
%GC 时间 69.6%(经过 89.5%)
分配速率 7,275,318,406 字节/MUT 秒
生产力占总用户的 30.4%,占总使用时间的 10.0%
如果我们使用基于列表的版本并且没有优化,内存消耗与启用优化的版本非常相似:
kolakoskiList :: [Int]
kolakoskiList = 1 : 2 : helper
where
helper = 2 : concat (zipWith replicate helper (cycle [1, 2]))
$ ghc --make Kolakoski-List && ./Kolakoski-List +RTS -s
2
在堆中分配了 96,000,143,328 字节
GC 期间复制了 26,615,974,536 个字节
3,565,429,808 字节最大驻留(34 个样本)
83,610,688 字节最大斜率
8732 MB 总内存在使用(0 MB 由于碎片丢失)
总时间(经过) 平均暂停 最大暂停
Gen 0 184252 colls, 0 par 9.98s 10.16s 0.0001s 0.0006s
Gen 1 34 colls, 0 par 10.45s 21.61s 0.6357s 12.0792s
初始化时间 0.00s(经过 0.00s)
MUT时间12.02s(经过11.88s)
GC 时间 20.44s(经过 31.77s)
退出时间 0.05s(经过 0.69s)
总时间 32.50 秒(经过 44.34 秒)
%GC 时间 62.9%(经过 71.7%)
分配速率 7,989,608,807 字节/MUT 秒
生产力占总用户的 37.1%,占总使用时间的 27.2%
现在,我们可以检查GHC flag reference 中自动设置在-O 上的标志。由于列表版本似乎与优化版本做同样的事情,有人可能会认为 GHC 将 kolakoski 转换为 kolakoskiList:
kolakoskiOptimized _ = kolakoskiList
让我们用-ddump-simpl -dsupress-all 在核心中检查一下:
==================== Tidy Core ====================
Result size of Tidy Core = {terms: 45, types: 30, coercions: 0}
kolakoski
kolakoski =
\ ds_d10r ->
case ds_d10r of _ { () ->
: (I# 1)
(: (I# 2)
(letrec {
helper_aNo
helper_aNo =
\ ds1_d10s ->
case ds1_d10s of _ { () ->
: (I# 2)
(concat
(zipWith
(replicate) (helper_aNo ()) (cycle (: (I# 1) (: (I# 2) ([]))))))
}; } in
helper_aNo ()))
}
main
main = print $fShowInt (!! (kolakoski ()) (I# 500000000))
main
main = runMainIO main
即使您不熟悉 GHC 的核心,您也可以看到 kolakoski 与您的原始版本基本相同。现在将其与-O -ddump-simpl -dsupress-all 进行比较:
==================== Tidy Core ====================
Result size of Tidy Core = {terms: 125, types: 102, coercions: 9}
kolakoski6
kolakoski6 = I# 1
kolakoski5
kolakoski5 = I# 2
Rec {
go_r1NG
go_r1NG =
\ ds_a14B _ys_a14C ->
case ds_a14B of _ {
[] -> [];
: ipv_a14H ipv1_a14I ->
case _ys_a14C of _ {
[] -> [];
: ipv2_a14O ipv3_a14P ->
case ipv_a14H of _ { I# n#_a13J ->
case tagToEnum# (<=# n#_a13J 0) of _ {
False ->
let {
lvl2_s1N3
lvl2_s1N3 = : ipv2_a14O ([]) } in
letrec {
xs_a1LH
xs_a1LH =
\ m_a1LO ->
case tagToEnum# (<=# m_a1LO 1) of _ {
False -> : ipv2_a14O (xs_a1LH (-# m_a1LO 1));
True -> lvl2_s1N3
}; } in
++ (xs_a1LH n#_a13J) (go_r1NG ipv1_a14I ipv3_a14P);
True -> ++ ([]) (go_r1NG ipv1_a14I ipv3_a14P)
}
}
}
}
end Rec }
lvl_r1NH
lvl_r1NH = : kolakoski5 ([])
lvl1_r1NI
lvl1_r1NI = : kolakoski6 lvl_r1NH
Rec {
xs'_r1NJ
xs'_r1NJ = ++ lvl1_r1NI xs'_r1NJ
end Rec }
Rec {
kolakoski3
kolakoski3 = : kolakoski5 kolakoski4
kolakoski4
kolakoski4 = go_r1NG kolakoski3 xs'_r1NJ
end Rec }
kolakoski2
kolakoski2 = : kolakoski5 kolakoski3
kolakoski1
kolakoski1 = : kolakoski6 kolakoski2
kolakoski
kolakoski = \ ds_d13p -> case ds_d13p of _ { () -> kolakoski1 }
此版本包含几个顶级CAFs,它们将在程序的生命周期内保留。因此,您确实会生成第 500,000,000 个值的列表并保存。
现在,那里发生了什么?函数内部的东西向外浮动。让我们再次检查标志引用。 -O 暗示了一个有希望的标志:
-ffull-laziness 开启完全惰性(向外浮动绑定)。 由-O暗示。
这就是导致您的问题的标志。事实上,你可以使用ghc --make -O -fno-full-laziness Kolakoski-Unit.hs 来获取你原来的内存消耗:
$ ghc --make Kolakoski-Unit.hs -O -fno-full-laziness && ./Kolakoski-Unit +RTS -s
2
在堆中分配了 192,001,417,688 字节
GC 期间复制了 637,962,464 个字节
66,104 字节最大驻留(151 个样本)
43,448 字节最大斜率
2 MB 总内存在使用(0 MB 由于碎片丢失)
总时间(经过) 平均暂停 最大暂停
Gen 0 368364 colls, 0 par 1.34s 1.32s 0.0000s 0.0002s
Gen 1 151 colls,0 par 0.00s 0.01s 0.0001s 0.0003s
初始化时间 0.00s(经过 0.00s)
MUT时间27.89s(经过28.13s)
GC时间1.34s(经过1.33s)
退出时间 0.00s(经过 0.00s)
总时间 29.25s(经过 29.46s)
%GC 时间 4.6%(经过 4.5%)
分配速率 6,884,084,443 字节/MUT 秒
生产力占总用户的 95.4%,占总使用时间的 94.7%
相关问题