为什么函数 运行 越调用越快?

Why does a function run faster the more I call it?

我试图在尝试优化某个函数之前对其执行进行计时。 (代码是 Elixir,但我使用的是 Erlang 的 :timer.tc。)

我的一般方法是 "run it lots of times, then calculate the average duration." 但是 我 运行 它 的次数越多,平均值就会急剧下降(达到一定程度)。

一个例子:

some_func = fn ->
  # not my actual function; it's a pure function,
  # but exhibits the same speedup
  :rand.uniform() 
end

run_n_times = fn (count, func) ->
  Enum.each(1..count, fn (_i) ->
    func.()
  end)
end

n = 20

{microseconds, :ok} = :timer.tc(run_n_times, [n, some_func])
IO.puts "#{microseconds / n} microseconds per call (#{microseconds} total for #{n} calls)"

增加 n 值的输出是这样的(轻微格式化):

174.8       microseconds per call (3496    total for 20      calls )
21.505      microseconds per call (4301    total for 200     calls )
4.5755      microseconds per call (9151    total for 2000    calls )
0.543415    microseconds per call (108683  total for 200000  calls )
0.578474    microseconds per call (578474  total for 1000000 calls )
0.5502955   microseconds per call (1100591 total for 2000000 calls )
0.556457    microseconds per call (2225828 total for 4000000 calls )
0.544754125 microseconds per call (4358033 total for 8000000 calls )

为什么函数 运行 调用得越多越快,这对基准测试意味着什么? 例如,是否有像 "run something >= 200k times in order to benchmark"?

由于您的函数非常快(基本上什么都不做),我认为您在这里看到的是设置的开销,而不是函数运行时的任何加速。在这种情况下,在开始 运行 函数之前,您必须构造一个范围、构造一个匿名函数并调用 Enum.each 函数。对于少量重复,这些因素可能比实际重复对基准测试的整体运行时间贡献更大。

我支持 Paweł Obrok 在他的回答中所写的内容。您可以通过在循环内多次调用该函数来优化您的代码:

run_n_times = fn (count, func) ->
  Enum.each(1..count, fn (_i) ->
    func.()
    func.()
    func.()
    func.()
    func.()
    func.()
    func.()
    func.()
    func.()
    func.()
  end)
end

这是 10 个调用,但您可以调用 100 个或 1000 个。你在同一个循环中做的越多,开销就越少。

我不知道 Erlang 到底做了什么,但如果你在 Javascript 中用现代 Javascript 解释器做同样的事情,那么前几个调用将被解释(慢)。然后解释器发现你经常调用这个函数并用一个快速而肮脏的编译器编译它。又调用了一百次,解释器弄清楚发生了什么并再次编译它,这次使用的是合适的编译器。再调用一千次,它会使用高度优化的编译器再次编译。那会给出你找到的那种数字。