Выполнение объявления функции в пределах области действия функции вне ее - PullRequest
3 голосов
/ 23 февраля 2020

Я размышлял о влиянии на производительность того, следует ли объявлять функцию в пределах функции или вне ее.

Для этого я создал тест с использованием jsperf, и результаты были мне интересны и я надеюсь, что кто-то сможет объяснить, что здесь происходит.

Тест: https://jsperf.com/saarman-fn-scope/1

Google Chrome Результаты: chrome results

Результаты Microsoft Edge: edge results

Firefox Результаты: enter image description here

Ответы [ 3 ]

1 голос
/ 25 февраля 2020

V8 разработчик здесь. Короче говоря: вы стали жертвой ловушек микро-бенчмаркинга. Реально, «Тест 1» немного более эффективен, но в зависимости от вашей общей программы разница может быть слишком мала, чтобы иметь значение.

Причина, по которой «Тест 1» более эффективен, заключается в том, что он создает меньше замыканий. Думайте об этом как:

let mathAdd = new Function(...);
for (let i = 0; i < 1000; i++) {
  mathAdd();
}

против

for (let i = 0; i < 1000; i++) {
  let mathAdd = new Function(...);
  mathAdd();
}

Так же, как если бы вы звонили new Object() или new MyFunkyConstructor(), более эффективно делать это только один раз за пределами l oop, а не на каждой итерации.

Причина, по которой «Тест 1» выглядит медленнее , является артефактом настройки теста. Указанный c способ, которым jsperf.com оборачивает ваш код в функции под капотом, в этом случае побеждает механизм встраивания V8 [1]. Таким образом, в «Тесте 1» run указывается, а mathAdd нет, поэтому выполняется фактический вызов и фактическое добавление. В «Тесте 2», с другой стороны, и run, и mathAdd вставляются, компилятор впоследствии видит, что результаты не используются, и удаляет весь мертвый код, так что вы тестируете пустой l oop: он не создает функций, не вызывает функций и не выполняет добавления (кроме i++).

Не стесняйтесь проверять сгенерированный код сборки, чтобы убедиться в этом :-) На самом деле, если вы если вы хотите создать дополнительные микробенчмарки, вам следует привыкнуть к проверке кода сборки, чтобы убедиться, что тест измеряет то, что вы думаете, что он измеряет.

[1] Я не уверен, почему; если бы мне пришлось угадывать: вероятно, существует специальная обработка для выявления того факта, что, хотя run является новым замыканием при каждом запуске тестового примера, под ним всегда один и тот же код, но похоже, что специальный регистр применяется только к функциям в локальная область вызова, а не загрузка из цепочки контекста, как в вызове runmathAdd. Если это предположение верно, вы можете назвать это ошибкой (которой Firefox явно нет); с другой стороны, если единственное влияние заключается в том, что устранение мертвого кода в микробенчмарках больше не работает, то это, безусловно, не важная проблема, которую нужно исправить.

0 голосов
/ 23 февраля 2020

вынос:

код работает намного быстрее, если двигатель применяет хитроумные оптимизации.

Край чертовски медлен.

Chrome не попадает в быстрый путь в первый случай по какой-то причине. Может быть, оптимизация только добавит больше итераций.

Если функция находится внутри другой функции, это не имеет значения², как доказывает Firefox.

Кстати, лучшая оптимизация была бы:

да, ничего, поскольку ваш код не имеет видимого эффекта вообще. Бездействие может быть очень быстрым

²: с точки зрения производительности нет, но с точки зрения дизайна это имеет значение имеет значение

0 голосов
/ 23 февраля 2020

Я считаю, что в случае Chrome и Firefox происходит включение функции mathAdd. Поскольку это простая функция без побочных эффектов, которая создается и вызывается внутри функции, компилятор заменяет сайт вызова внутренним кодом функции.

Полученный код будет выглядеть следующим образом:

const run = count => {
  return 10 + count
}

for (let count = 0; count < 1000; count++) {
  run(count)
}

Это сохраняет во время выполнения объявление функции и новый кадр стека при вызове функции.

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


Я провел еще несколько тестов: https://jsperf.com/saarman-fn-scope/5

Перемещая код Test2 в l oop (все в l oop), я ожидал, что компилятор встроит вызов функции, потому что он имеет область видимости в l oop и содержит очень маленький код Это ожидание было неверным, но оно все же быстрее, чем Test1

Может быть, проблема заключалась в глубине функции? Перемещая код Test1 в значение для l oop (все в l oop 2), результат оказался самым медленным из всех ...

Итак, в заключение я совершенно не могу предсказать, когда эти Оптимизация вызовов применяется JS движками.

Стоит отметить, что движки браузера постоянно работают над оптимизацией для общих шаблонов JS. Поэтому часто не имеет смысла оптимизировать ваш код для их оптимизации.


В принципе, когда вам нужна лучшая производительность, избегайте вызовов функций, избегайте объявлений функций. Но всегда остерегайтесь преждевременной оптимизации. Представьте, как трудно было бы читать код без функций!

...