for vs forEach vs reduce

Бенчмарк трёх способов обойти массив. Главный вывод: цифры прыгают между запусками из-за деоптимизации на SMI. forEach стабильно чуть медленнее, for и reduce плюс-минус равны в горячем коде.

Что измерял AsForJS

  • Один и тот же sum массива через for, forEach, reduce.
  • Прогон по миллионам элементов, многократный запуск.
  • Между запусками — --trace-deopt для понимания, где функция уходит в Ignition.

Почему числа прыгают

  • Массив на ходу мог стать holey или сдвинуться SMI → Double.
  • При первом запуске IC ещё monomorphic, на втором — polymorphic.
  • TurboFan мог деоптимизировать функцию reduce-колбэка после первой кучи итераций.

var помогает оптимизатору

«Внутрь V8 добавили специальную оптимизацию, которая старается убрать из окружения... любой идентификатор, который не используется внутри самого тела этой функции.»

Используешь var в горячем коде, не возвращая замыкание наружу — V8 чистит окружение агрессивнее.

Замыкание удерживает память

Возвращаешь из функции другую функцию (закрытие) — весь объём захваченного scope залочен от GC, даже если используется один identifier.

// Плохой случай — bigData никогда не освободится
function make(bigData) {
  return  => 42;  // bigData в скоупе, GC не очистит
}

Подводные камни

  • forEach всегда вызывает функцию через apply — лишний накладной расход.
  • Hot-цикл на стрелке часто инлайнится, но не всегда.
  • Реальное правило: используй for/of или for, если важна скорость; map/reduce/forEach — для читаемости при не-горячем коде.

Источники

См. также