2016-03-16 3 views
2

Я пытаюсь выполнить выполнение функции, прежде чем пытаться ее оптимизировать. (Код Elixir, но я использую Erlang's :timer.tc.)Почему функция работает быстрее, чем больше я ее называю?

Мой общий подход «запускайте его много раз, а затем вычислите среднюю продолжительность». Но среднее снижение резко больше, чем больше я запускаю его (до точки).

Пример:

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) 

Почему функция работать быстрее, чем больше я это называю, и что это означает для бенчмаркинга? Например, существует ли правило большого пальца, как «запустить что-то> = 200 тыс. Раз для сравнения»?

ответ

5

Поскольку ваша функция очень быстрая (практически ничего не делает), я думаю, что вы видите здесь накладные расходы на установку, а не ускорение во время выполнения функции. В этом случае перед запуском вашей функции вы должны построить диапазон, построить анонимную функцию и вызвать функцию Enum.each. Для небольшого числа повторений эти факторы, вероятно, в большей степени влияют на общую продолжительность выполнения теста, чем на фактические повторения.

+0

Вы также должны добавить очень короткие эталонные пробеги (3,5 мс для 20 прогонов, 4,3 мс для 200, 9 мс на 2000 год), вы также можете наблюдать, что у процессора не хватает времени, чтобы проснуться из режимов малой мощности в современных высокочастотных системах на мобильных платформах. Тест должен, по крайней мере, принимать 100 мс, но предпочтительно несколько секунд. –

0

Я второй, что писал Павел Оброк в своем ответе. Вы могли бы оптимизировать свой код, вызвав функцию несколько раз внутри цикла:

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 из них. Чем больше вы будете делать в одном цикле, тем меньше будет накладных расходов.

0

Я не знаю, что именно делает Erlang, но если вы сделаете то же самое в Javascript с помощью современного Javascript-интерпретатора, то первые несколько вызовов будут интерпретироваться (медленно). Затем интерпретатор выясняет, что вы часто вызываете эту функцию и компилируете ее быстрым и грязным компилятором. Еще сто звонков, и интерпретатор выясняет, что происходит и компилирует его снова, с надлежащим компилятором на этот раз. И еще тысяча звонков, он снова компилируется с помощью высоко оптимизирующего компилятора. Это даст точно такие числа, которые вы нашли.

Смежные вопросы