Императивный код и процессор

Императивный код «легко ложится на процессор» — потому что транслируется в линейные машинные инструкции с минимумом косвенности. Декларативный (через массивы методов и закрытий) — дороже.

Почему императив быстрее на CPU

  • Cache-friendly — данные обходятся последовательно.
  • Branch prediction — простые ветвления хорошо предсказываются.
  • Inlining — V8 чаще инлайнит простые императивные функции.
  • Меньше слоёв косвенности → меньше cache misses.

Пример

// Декларатив: 3 промежуточных массива, 3 прохода, 3 функции колбэков
const sum = arr.filter(x => x > 0).map(x => x * 2).reduce((a, b) => a + b, 0);

// Императив: 1 проход, 0 промежуточных массивов
let s = 0;
for (let i = 0; i < arr.length; i++) {
  if (arr[i] > 0) s += arr[i] * 2;
}

В горячем коде разница может быть 5-10x на больших массивах.

Не везде это win

  • Декларативный код легче читать и тестировать.
  • Современный V8 ИНОГДА сливает chain методов через оптимизатор — но не всегда.
  • При маленьких N (до 100) разница в шуме.

Принцип Mechanical Sympathy

Понимай железо. Cache lines обычно 64 байта. Если структура больше — touching её провоцирует cache miss.

В JS это:

  • Hidden class — лучше fixed shape (in-object properties помещаются в одну cache line).
  • Массив SMI plain — последовательность 4-байтных целых, чудо для CPU.
  • for...of с iterator protocol — лишний косвенный вызов.

Источники

См. также