Skip to main content

Заміри продуктивності JS-коду

Продуктивність JS міряють трьома різними інструментами під три різні питання: таймер у коді каже, скільки триває операція, профайлер каже, де саме витрачається час, а мікробенчмарк каже, яка з реалізацій швидша. Окремий шар, це метрики реального користувача: довгі задачі, FPS і Core Web Vitals, бо кінцева мета не «швидкий алгоритм», а чутливий інтерфейс.

Теорія

TL;DR

  • Швидкий замір: performance.now(), performance.mark() / performance.measure(), console.time(). У Node.js те саме дає модуль node:perf_hooks.
  • Профілювання: вкладка Performance у DevTools дає flame chart, FPS і long tasks; Memory дає heap snapshot; Coverage показує, скільки JS реально виконується.
  • Node.js профілюють через node --inspect плюс Chrome DevTools, або node --prof плюс prof-process, або clinic.js та 0x.
  • Мікробенчмарки пишуть на Benchmark.js чи tinybench: вони роблять прогрів JIT, багато ітерацій і статистику.
  • Відгук UI: Long Tasks API ловить задачі довші за 50 мс, requestAnimationFrame дає FPS.
  • Сторінкові метрики: Navigation і Resource Timing (TTFB, завантаження ресурсів), Core Web Vitals (LCP, CLS, INP, TBT), Lighthouse для разової перевірки.
  • Методика важливіша за інструмент: прогрів, фіксовані дані, багато прогонів, медіана і квантилі, а не середнє.

Швидкий приклад

javascript
// 1. Найпростіший точний таймер, у мілісекундах з дробовою частиною const t0 = performance.now(); buildIndex(items); const t1 = performance.now(); console.log(`Час: ${(t1 - t0).toFixed(2)} ms`); // 2. Те саме через User Timing API: позначки лишаються у таймлайні DevTools performance.mark('A'); buildIndex(items); performance.mark('B'); performance.measure('build-index', 'A', 'B'); console.table(performance.getEntriesByName('build-index')); // 3. Найкоротший варіант для налагодження console.time('op'); buildIndex(items); console.timeEnd('op');

Швидкі заміри прямо в коді

Це інструментування: ви самі ставите таймер навколо ділянки, яка вас цікавить.

Браузер

javascript
// Точний таймер (у мс, з долями) const t0 = performance.now(); // ... код ... const t1 = performance.now(); console.log(`Час: ${(t1 - t0).toFixed(2)} ms`);
javascript
// User Timing API: позначки і вимірювання performance.mark('A'); // ... код ... performance.mark('B'); performance.measure('my-op', 'A', 'B'); console.table(performance.getEntriesByName('my-op'));
javascript
// Швидко і просто console.time('op'); // ... код ... console.timeEnd('op');

performance.now() повертає монотонний час високої точності від старту сторінки, тому він не стрибає при зміні системного годинника, на відміну від Date.now(). Перевага performance.mark() над голим таймером у тому, що позначки потрапляють у таймлайн профайлера, і ви бачите свій замір поруч із реальними подіями браузера.

Node.js

javascript
// perf_hooks, монотонні таймери const { performance, PerformanceObserver } = require('node:perf_hooks'); performance.mark('A'); // ... код ... performance.mark('B'); performance.measure('op', 'A', 'B'); new PerformanceObserver(list => console.table(list.getEntries())) .observe({ entryTypes: ['measure'] });

Профілювання: шукаємо, де саме гальмує

Таймер каже «повільно», але не каже «через що». Для цього є профайлер.

Браузер DevTools

  • Performance (або Performance Insights): запис, і ви отримуєте flame chart (гарячі функції), FPS, layout і recalculate style, long tasks (≥ 50 мс).
  • Memory: Heap snapshot та Allocation instrumentation, шукаємо витоки і зайве сміття.
  • Coverage: скільки JS і CSS реально виконується, це прямо впливає на TBT.

Node.js

  • node --inspect і Chrome DevTools, звідти CPU profile і heap.
  • node --prof плюс prof-process, або clinic.js чи 0x для зручних графів полум'я.

Читати треба саме flame chart, а не лише підсумкові числа: широка смуга внизу означає функцію, у якій програма провела найбільше часу, і саме її варто оптимізувати першою.

Мікробенчмарки: порівняти дві реалізації

Коли питання звучить «яка з двох функцій швидша», голий performance.now() бреше: перший прогін іде по неоптимізованому коду, доки JIT не прогріється.

  • Benchmark.js та tinybench роблять прогрів, багато ітерацій і статистику за вас.
javascript
import Benchmark from 'benchmark'; const suite = new Benchmark.Suite(); function a(arr){ /* варіант A */ } function b(arr){ /* варіант B */ } suite .add('A', () => a(data)) .add('B', () => b(data)) .on('cycle', e => console.log(String(e.target))) .on('complete', function () { console.log('Faster:', this.filter('fastest').map('name')); }) .run({ async: true });

Важливо: прогрівайте тест (відкидайте перший прогін), фіксуйте вхідні дані, робіть кілька прогонів, закривайте зайві вкладки і розширення.

Метрики відгуку інтерфейсу і сторінки

Користувач не відчуває мілісекунд окремої функції, він відчуває підвисання і смикання.

  • Long Tasks API: ловимо блокування головного потоку.
javascript
new PerformanceObserver((list) => { for (const e of list.getEntries()) { console.log('Long task', e.duration.toFixed(1), 'ms'); } }).observe({ entryTypes: ['longtask'] });
  • rAF і FPS: простий індикатор плавності.
javascript
let last = performance.now(), frames = 0; function tick(ts){ frames++; if (ts - last >= 1000) { console.log('FPS:', frames); frames = 0; last = ts; } requestAnimationFrame(tick); } requestAnimationFrame(tick);

Сторінкові, мережеві та рендер-метрики:

  • Navigation і Resource Timing (performance.getEntriesByType('navigation' | 'resource')), звідси TTFB і час завантаження ресурсів.
  • Core Web Vitals (LCP, CLS, INP, TBT), ставте бібліотеку web-vitals на реальний трафік.
  • Lighthouse (разово), добре ловить регресії, але не замінює польові метрики з реальних користувачів.

Методика: як міряти коректно

  1. Сформулюйте мету: вас цікавить CPU, GC, layout чи мережа? Від цього залежить інструмент.
  2. Ізолюйте контекст: якщо міряєте чистий алгоритм, приберіть мережу і DOM.
  3. Прогрійте JIT: зробіть кілька «холостих» запусків перед заміром.
  4. Повторення і статистика: медіана і квантилі важливіші за середнє, бо одне випадкове сповільнення псує середнє.
  5. Контролюйте шум: фіксуйте дані та розмір входу, обмежте фонові процеси.
  6. Дивіться на полум'я (flame chart), а не лише на числа, оптимізуйте гарячі вузькі місця.
  7. Перевіряйте регресії в CI: невеликий бенч плюс порогові значення.

Що міряти в типових сценаріях:

СценарійЧим міряти
Алгоритм або колекціїМікробенч (Benchmark.js) плюс performance.now()
Інтерактивність UIDevTools Performance, Long Tasks, FPS
Рендер і стиліВкладки Rendering, Layout, Styles у профайлері; уникати layout thrashing
Пам'ять і витокиHeap snapshots, Allocation timeline
Node APIperf_hooks, CPU profile, перевірка синхронних методів (замінити на async або worker_threads)

Типові помилки

  • Один прогін «на око»: шум і непрогрітий JIT спотворять картину.
  • Мікробенчити DOM-операції: результат залежить від реального дерева і стилів, такі речі профілюють у DevTools, а не в бенчмарку.
  • Порівнювати дві реалізації на різних вхідних даних або різних розмірах входу.
  • Оптимізувати без flame chart: легко вилікувати не те місце і не отримати жодного виграшу.
  • Брати середнє замість медіани: один викид від збирача сміття зробить обидва варіанти «однаковими».
  • Використовувати Date.now() для коротких замірів: роздільність груба, а час немонотонний.
  • Довіряти лише лабораторному Lighthouse і не збирати польові Core Web Vitals із реальних пристроїв.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.