在编写我的穷人的FParsec版本时遇到了这种现象。考虑:
let add x = x+1
let fromLeft = add>>add>>add>>add>>add>>add>>add>>add>>add>>add
let fromRight = add<<add<<add<<add<<add<<add<<add<<add<<add<<add
let imperative x =
let mutable result = x
for i = 0 to 9 do
result <- add result
result
测试所有三个功能的性能:
let runs = 10000000
printf "From left\n"
time(fun()->fromLeft 0) runs
printf "\nFrom right\n"
time(fun()->fromRight 0) runs
printf "\nImperative\n"
time(fun()->imperative 0) runs
结果如下:
fromLeft
为59毫秒,fromRight
为658毫秒
Imperative
为26毫秒。
测试在发布模式和VS外部完成。结果是稳定的,不依赖于我测试函数的顺序。如果Imperative
运行时间被视为add
函数本身的开销,并且从两个结果中减去,则两个组合物的性能不同的因素是11x或19x。
有谁知道造成这种差异的原因?
我的time
组合器
let inline time func n =
GC.Collect()
GC.WaitForPendingFinalizers()
printfn "Starting"
let stopwatch = Stopwatch.StartNew()
for i = 0 to n-1 do func() |> ignore
stopwatch.Stop()
printfn "Took %A ms" stopwatch.Elapsed.TotalMilliseconds
答案 0 :(得分:4)
一个非常粗略的答案是编译器将fromLeft
中的函数包含在内,但出于某种原因,我们不会对fromRight
进行相同的优化。可以通过完全括号化组合来强制关联性:
let fromLeft = add>>(add>>(add>>(add>>(add>>(add>>(add>>(add>>(add>>add))))))))
let fromRight = ((((((((add<<add)<<add)<<add)<<add)<<add)<<add)<<add)<<add)<<add
导致:
From left
Starting
Took 645.648 ms
From right
Starting
Took 625.058 ms
Imperative
Starting
Took 23.0332 ms
像这样颠倒括号:
let fromLeft = ((((((((add>>add)>>add)>>add)>>add)>>add)>>add)>>add)>>add)>>add
let fromRight = add<<(add<<(add<<(add<<(add<<(add<<(add<<(add<<(add<<add))))))))
结果:
From left
Starting
Took 86.3503 ms
From right
Starting
Took 75.6358 ms
Imperative
Starting
Took 33.7193 ms
所以这看起来就像编译器中缺少的优化。