DarkRiDDeR14 мин

Измерение производительности, которому можно верить: фиксируем условия и p95

ПроизводительностьИнженерные практики

Фраза «после оптимизации стало быстрее» не даёт проверить ни исходное условие, ни размер выигрыша. Цена ошибки — принять удачный прогрев кэша за эффект изменения и откатить полезную работу, когда другой браузер покажет обратный результат. Измерение начинается с описания входа, а не с красивого числа.

Соберём маленький локальный стенд: сервер отдаёт страницу с фиксированной задержкой, клиент делает серию запросов и считает медиану и p95. Такой тест не заменяет браузерный прогон, но показывает дисциплину измерения: одинаковый URL, количество повторов, единица времени, выбросы и границы вывода.

Число имеет смысл только вместе с условиями

Для сравнения нужны версия сборки, браузер, viewport, режим кэша, сеть, размер данных и порядок действий. Если поменялось сразу пять условий, разность не принадлежит одному изменению. В отчёте сначала фиксируйте входные данные, затем распределение наблюдений и только потом пишите вывод.

Минимальный протокол замера
ПараметрПример значенияЗачем
URL и commit/catalog, abc123что именно измеряли
Браузер и viewportChromium, 1280×800какой клиент выполнял код
Кэшcold или warmчто уже было в storage
Повторы20насколько устойчиво число
МетрикаTTFB, p95какой участок сравниваем
Цикл замера: зафиксировать условия, выполнить повторения, посчитать распределение, изменить один фактор и сравнить.
Цикл возвращает к исходным условиям после каждого изменения. Без этого p95 не показывает, что именно дало разницу.

Локальный p95 на серии запросов

Вход кода — число повторов и локальный endpoint. Сервер отвечает после одной и той же задержки, а клиент измеряет round trip. Ожидаемый результат — отсортированный список и p95, близкий к заданной задержке плюс накладные расходы Node. Это рабочий замер локального HTTP-пути, а не оценка интерфейса.

import { performance } from 'node:perf_hooks';
import { createServer as createMeasureServer } from 'node:http';

const server = createMeasureServer((request, response) => {
  setTimeout(() => response.end('ready'), 40);
});

function percentile(values, rank) {
  const sorted = [...values].sort((a, b) => a - b);
  return sorted[Math.min(sorted.length - 1, Math.ceil(sorted.length * rank) - 1)];
}

server.listen({ host: '127.0.0.1', port: 0 }, async function measure() {
  const address = server.address();
  const port = address.port;
  const samples = [];
  for (let index = 0; index < 20; index += 1) {
    const started = performance.now();
    await (await fetch('http://127.0.0.1:' + port)).text();
    samples.push(performance.now() - started);
  }
  console.log({ count: samples.length, median: percentile(samples, 0.5).toFixed(1), p95: percentile(samples, 0.95).toFixed(1) });
  server.close();
});

В результате count равен 20, медиана и p95 находятся около 40 миллисекунд. На загруженной машине числа выше, но их разброс остаётся видимым. Функция percentile здесь намеренно короткая: на малой выборке p95 — один элемент, поэтому отчёт должен сохранять размер серии и сами условия, а не только округлённый итог.

Cold и warm cache нельзя смешивать в одной выборке. Если нужен общий отчёт, маркируйте режим каждой строки и сравнивайте одинаковые поднаборы. Иначе p95 может вырасти только потому, что несколько первых запросов скачали ресурсы.

Среднее скрывает хвост

Среднее удобно для оценки общей стоимости, но пользователь попадает в хвост распределения. p95 означает, что 95 процентов измерений не превышают значение, а пять процентов превышают. Это не «медленность каждого запроса». При малом числе повторов один выброс сильно меняет p95, поэтому сравнивайте одинаковые объёмы выборки.

Для страницы полезно хранить несколько метрик отдельно: TTFB, время до DOMContentLoaded, LCP, long tasks и размер ответа. Нельзя складывать их в один балл без явного решения. LCP зависит от содержимого и браузера, а TTFB — от серверной и сетевой части. Изменение, которое улучшило один участок, может ухудшить другой.

Как не перепутать корреляцию с причиной

Сигнал и безопасный вывод
СигналМожно сказатьНужна дополнительная проверка
TTFB вышеответ начал приходить позжеbackend, сеть и proxy
p95 выросхвост стал тяжелее в этих условияхнагрузка и размер выборки
script появился раньшепорядок ресурсов изменилсявлияние на render и DOM
LCP изменилсявизуальный критерий изменилсякакой элемент стал LCP

Слово «корреляция» полезно только вместе с ограничением. Если TTFB и LCP выросли одновременно, это повод проверить сервер, но не доказательство, что сервер — единственная причина. Свяжите метрики по одному запуску, сохраните network log и посмотрите, какой элемент стал самым поздним крупным контентом.

Порядок замера

  1. Зафиксировать URL, код сборки, браузер, viewport, сеть и режим кэша.
  2. Прогреть или очистить кэш одинаковым способом перед каждым набором.
  3. Сделать одинаковое число повторов и сохранить сырые значения времени.
  4. Посчитать median, p95 и диапазон, указав единицу измерения.
  5. Изменить один фактор и повторить тот же набор.
  6. Записать, какая метрика улучшилась, какая ухудшилась и какие вопросы остались открытыми.

Ограничения и следующий шаг

Локальный HTTP-сервер не моделирует мобильную сеть, CPU браузера, кэш CDN и работу layout. Маленькая серия не годится для долгих выводов. W3C описывает интерфейсы измерения, но не устанавливает ваш порог приемки. Порог должен учитывать страницу, устройство и стоимость задержки.

Следующий шаг — добавить к локальной серии один контролируемый browser-run и сохранить raw JSON вместе с условиями. Проверяйте распределение, а не только среднее. Если результат меняется после очистки кэша или при другом viewport, сначала уточните входные данные, затем выбирайте оптимизацию.

Проверяемые источники

  • W3C Navigation Timing Level 2 — Working Draft, 25 February 2026. Определяет навигационные временные метки и связь между этапами загрузки документа. Граница применимости: Рабочий черновик стандарта не сообщает значения метрик вашего браузера и не заменяет полевой сбор.
  • W3C Performance Timeline — Candidate Recommendation Draft, 21 May 2025. Определяет PerformanceEntry, getEntries и PerformanceObserver как основу чтения метрик. Граница применимости: Спецификация не задаёт пороги качества и не гарантирует одинаковый набор entry во всех движках.
  • W3C Resource Timing — Candidate Recommendation Draft, 20 April 2026. Определяет записи ресурсов, размеры и render-blocking status для сетевого слоя страницы. Граница применимости: Не описывает серверную архитектуру, пользовательский опыт целиком или причинность конкретной задержки.