为什么功能组成从左到右比右到左快11倍到19倍?

时间:2014-05-26 18:04:16

标签: performance f#

在编写我的穷人的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

1 个答案:

答案 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

所以这看起来就像编译器中缺少的优化。