我正在尝试优化一个库,该库旨在获取大型数据集,然后对其应用不同的操作。现在该库正在运行,我想对其进行优化。
我的印象是,非严格评估允许 GHC 组合操作,以便在编写所有函数时仅迭代一次数据,以便对参数进行排序以促进 whnf 减少。(并可能减少对每个数据执行的操作数量)
为了测试这一点,我编写了以下代码:
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 版本运行得如此之快?我只能假设实际上没有执行任何操作(或者只是减少的第一次迭代)
我什至使用了正确的术语吗?