我正在尝试优化设计用于获取大型数据集的库 然后对它应用不同的操作。既然图书馆正在运作,我想要 优化它。
我的印象是非严格的评估允许 GHC结合操作使得数据只在迭代时迭代一次 编写函数以便命令参数以便于减少。 (并且可能减少对每个数据执行的操作数量)
为了测试这个,我编写了以下代码:
import Criterion.Main
main = defaultMain
[ bench "warmup (whnf)" $ whnf putStrLn "HelloWorld",
bench "single (whnf)" $ whnf single [1..10000000],
bench "single (nf)" $ nf single [1..10000000],
bench "double (whnf)" $ whnf double [1..10000000],
bench "double (nf)" $ nf double [1..10000000]]
single :: [Int] -> [Int]
single lst = fmap (* 2) lst
double :: [Int] -> [Int]
double lst = fmap (* 3) $ fmap (* 2) lst
使用Criterion库进行基准测试我得到以下结果:
benchmarking warmup (whnf)
mean: 13.72408 ns, lb 13.63687 ns, ub 13.81438 ns, ci 0.950
std dev: 455.7039 ps, lb 409.6489 ps, ub 510.8538 ps, ci 0.950
benchmarking single (whnf)
mean: 15.88809 ns, lb 15.79157 ns, ub 15.99774 ns, ci 0.950
std dev: 527.8374 ps, lb 458.6027 ps, ub 644.3497 ps, ci 0.950
benchmarking single (nf)
collecting 100 samples, 1 iterations each, in estimated 107.0255 s
mean: 195.4457 ms, lb 195.0313 ms, ub 195.9297 ms, ci 0.950
std dev: 2.299726 ms, lb 2.006414 ms, ub 2.681129 ms, ci 0.950
benchmarking double (whnf)
mean: 15.24267 ns, lb 15.17950 ns, ub 15.33299 ns, ci 0.950
std dev: 384.3045 ps, lb 288.1722 ps, ub 507.9676 ps, ci 0.950
benchmarking double (nf)
collecting 100 samples, 1 iterations each, in estimated 20.56069 s
mean: 205.3217 ms, lb 204.9625 ms, ub 205.8897 ms, ci 0.950
std dev: 2.256761 ms, lb 1.590083 ms, ub 3.324734 ms, ci 0.950
GHC是否优化了“双重”功能,以便仅列出该列表 通过(* 6)操作一次? nf结果表明情况就是如此 否则“double”的平均计算时间将是“single”
的两倍使whnf版本运行得如此之快的区别是什么?我可以 只假设实际上没有任何东西被执行(或者只是第一个 减少中的迭代)
我甚至使用了正确的术语吗?
答案 0 :(得分:4)
查看GHC使用-ddump-simpl
选项生成的核心(中间代码),我们可以确认GHC确实将map
的两个应用程序合并为一个(使用-O2
) 。转储的相关部分是:
Main.main10 :: GHC.Types.Int -> GHC.Types.Int
GblId
[Arity 1
NoCafRefs]
Main.main10 =
\ (x_a1Ru :: GHC.Types.Int) ->
case x_a1Ru of _ { GHC.Types.I# x1_a1vc ->
GHC.Types.I# (GHC.Prim.*# (GHC.Prim.+# x1_a1vc 2) 3)
}
Main.double :: [GHC.Types.Int] -> [GHC.Types.Int]
GblId
[Arity 1
NoCafRefs
Str: DmdType S]
Main.double =
\ (lst_a1gF :: [GHC.Types.Int]) ->
GHC.Base.map @ GHC.Types.Int @ GHC.Types.Int Main.main10 lst_a1gF
请注意GHC.Base.map
中Main.double
只有Main.main10
的使用方式,指的是组合函数Functor
,它们都加2并乘以3.这可能是GHC优先的结果内联列表的fmap
实例,以便map
变为map
,然后applying a rewrite rule允许(:)
两个应用程序融合,再添加一些内联和其他优化。
WHNF意味着表达式仅被评估为“最外层”数据构造函数或lambda。在这种情况下,这意味着第一个{{1}}构造函数。这就是为什么它快得多,因为几乎没有任何工作要做。有关详细信息,请参阅我对What is Weak Head Normal Form?的回答。