一个函数参数的性能问题

我编写了两个简单的函数,定义如下

function test1(var1::Float64)
    for i = 1:1e7
        sin(var1)*cos(var1)
    end
end

function test2(var1::Float64)
    cs = cos(var1)
    ss = sin(var1)
    for i = 1:1e7
        ss*cs
    end
end

第一个函数的benchmark结果如下

BenchmarkTools.Trial: 47 samples with 1 evaluation.
 Range (min … max):  102.527 ms … 130.312 ms  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     105.625 ms               ┊ GC (median):    0.00%
 Time  (mean ± σ):   106.386 ms ±   4.531 ms  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ██                                                             
  ██▄▆▁▆▇▆▆▄▇▇▆▆▄▆▄▁▄▁▄▁▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▄ ▁
  103 ms           Histogram: frequency by time          130 ms <

 Memory estimate: 0 bytes, allocs estimate: 0.

第二个函数的benchmark结果如下

BenchmarkTools.Trial: 10000 samples with 996 evaluations.
 Range (min … max):  24.322 ns … 38.767 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     24.735 ns              ┊ GC (median):    0.00%
 Time  (mean ± σ):   24.864 ns ±  0.898 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

     █ ▅ ▂                                                     
  ▃▃▁█▂█▃█▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂ ▂
  24.3 ns         Histogram: frequency by time        28.9 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

两个函数的性能差别非常大,但若将其改写成

function test1()
    var1 = 100.
    for i = 1:1e7
        sin(var1)*cos(var1)
    end
end
function test2()
    var1 = 100.
    cs = cos(var1)
    ss = sin(var1)
    for i = 1:1e7
        ss*cs
    end
end

则二者性能没有区别,请问这是为什么?

总的来说,这属于编译优化的 DCE(dead code elimination):

  1. 这里所有的代码都没有返回值
  2. csss 是 Float64 类型的标量,所以 llvm 有足够的理由认为移除 for 循环不会对程序执行带来任何影响。(作为对比,你可以写一个 var1 = [100., ] 的向量版本,这种情况下 llvm 应该就没办法优化了)

@code_llvm test1()@code_llvm test2() 可以看到 llvm 的优化代码。

请问为什么var1作为一个参数传入时不会有编译优化,而直接写在函数体中却会被优化?

不是很清楚,编译优化对我来说就是一个黑盒。写在函数体里应该会标记为常量进行处理,从而触发 DCE,作为对比

function test1()
    var1 = rand()
    for i = 1:1e7
        sin(var1)*cos(var1)
    end
end

也是运行时才知道具体的值,也不会触发 DCE。

1 个赞