[英]Haskell benchmarking/Optimization of nf/whnf of non-strict reduction
我正在嘗試優化一個旨在獲取大型數據集然后對其應用不同操作的庫。 既然庫正在運行,我想優化它。
我的印象是非嚴格評估允許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版本運行得如此之快的區別是什么? 我只能假設實際上沒有執行任何操作(或者只是減少中的第一次迭代)
我甚至使用了正確的術語嗎?
查看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
這都是由3這增加了2和乘法可能是GHC的結果第一內聯Functor
的列表,以便例如fmap
成為map
,然后應用重寫規則 ,允許融合兩個map
應用程序,以及一些更多的內聯和其他優化。
WHNF意味着表達式僅被評估為“最外層”數據構造函數或lambda。 在這種情況下,這意味着第一個(:)
構造函數。 這就是為什么它快得多,因為幾乎沒有任何工作要做。 請參閱我的回答什么是弱頭正常形式? 更多細節。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.