Бенчмарки JS — как правильно

Большинство домашних замеров неверны. Виноваты: непрогретый JIT, dead code elimination, аллокация в одной итерации, шум GC, неверная мера времени.

5 правил адекватного бенчмарка

  1. Прогрей JIT — несколько тысяч итераций до начала замера, чтобы TurboFan успел.
  2. Не давай V8 выкинуть результат — сохраняй в переменную/массив, иначе dead code elimination оптимизирует всё в noop.
  3. Большие данные не аллоцируй в той же итерации, что и замер — это будет тест аллокатора, а не алгоритма.
  4. Меряй много раз и смотри распределение, не среднее. Используй console.time или performance.now(), лучше Benchmark.js.
  5. Изолируй deopt через %ClearFunctionFeedback/прогрев.

Минимальный шаблон

function bench(fn, iters = 1e6) {
  // warmup
  for (let i = 0; i < 1000; i++) fn;
  const t = performance.now();
  let sink;
  for (let i = 0; i < iters; i++) sink = fn;
  return { ms: performance.now() - t, sink };
}

Чужие бенчмарки врут

«Прежде чем мы перейдем к замерам, я хотел бы продемонстрировать пруф того, о чем я рассказывал.»

Спикер AsForJS неоднократно показывал, что код в тестах подписчиков измеряет не то, что они думают:

  • Замеряют создание массива вместо обхода
  • Сравнивают Map vs Object без прогрева
  • Меряют 1 итерацию в наносекундах

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

  • console.time точен до миллисекунд.
  • performance.now() — до микросекунд, но не наносекунд.
  • Между прогонами очищай --isolates (новый процесс Node).
  • Один и тот же код в Chrome / d8 / Node даёт разные числа.

Источники

См. также