如果我将Kolakoski序列定义为
kolakoski :: () -> [Int]
kolakoski () = 1 : 2 : helper ()
where
helper () = 2 : concat (zipWith replicate (helper ()) (cycle [1, 2]))
并找到第500,000,000项
kolakoski () !! 500000000
我发现,使用ghc -O编译时,这会迅速消耗大量内存。但是关闭优化后,它几乎不使用任何东西。哪个优化导致了此问题,以及如何将其关闭?
让我们比较实际数字。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 cols,0 par 1.89s 2.30s 0.0000s 0.0001s Gen 1422 colls,0 par 0.02s 0.02s 0.0001s 0.0001s 初始化时间0.00s(经过0.00s) MUT时间37.34秒(经过37.25秒) GC时间1.91秒(经过2.33秒) 退出时间0.00s(经过0.00s) 总时间39.25秒(经过39.58秒) %GC时间4.9%(经过5.9%) 每MUT秒分配7,712,197,063字节的速率 生产力,占总用户的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列,0杆11.55s 69.78s 2.0524s 56.2970s 初始化时间0.00s(经过0.00s) MUT时间8.80秒(经过8.43秒) GC时间20.22s(经过79.26s) 退出时间0.03s(经过0.89s) 总时间29.05s(经过88.58s) %GC时间69.6%(已用89.5%) 每个MUT秒的分配速率7,275,318,406字节 生产力占总用户的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 cols,0 par 9.98s 10.16s 0.0001s 0.0006s Gen 1 34列,0杆10.45s 21.61s 0.6357s 12.0792s 初始化时间0.00s(经过0.00s) MUT时间12.02秒(经过11.88秒) GC时间20.44s(经过31.77s) 退出时间0.05秒(经过0.69秒) 总时间32.50秒(经过44.34秒) %GC时间62.9%(已消耗71.7%) 每个MUT秒的分配速率7,989,608,807字节 生产力,占总用户的37.1%,占总使用时间的27.2%
现在,我们可以检查GHC标志参考以获取自动设置为的标志-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 }
该版本包含几个顶级CAF,在程序的整个生命周期中都将保留这些CAF。因此,您实际上会生成最多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个样本) 最大43448个字节 正在使用2 MB的总内存(由于碎片丢失了0 MB) 总时间(经过)平均暂停最大暂停 Gen 0 368364 colls,0 par 1.34s 1.32s 0.0000s 0.0002s Gen 1151列,0 par 0.00s 0.01s 0.0001s 0.0003s 初始化时间0.00s(经过0.00s) MUT时间27.89s(经过28.13s) GC时间1.34秒(经过1.33秒) 退出时间0.00s(经过0.00s) 总时间29.25秒(经过29.46秒) %GC时间4.6%(经过4.5%) 每个MUT秒分配的速率6,884,084,443字节 生产力占总用户的95.4%,占总使用时间的94.7%
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句