Гайды

Как замерить время выполнения кода в JavaScript — и почему первый замер всегда врёт

Иллюстрация: большой секундомер на столе и ряд одинаковых шестерёнок, которые засекают по очереди

Смотри, что обычно происходит. Ты попросил ИИ ускорить функцию. Он переписал её и сказал: «теперь в три раза быстрее». Ты замерил один раз — и правда быстрее. Замерил ещё раз — старая версия тоже вдруг стала быстрой.

Дело не в мистике. Первый запуск почти всегда медленнее остальных: движок JavaScript ещё не успел «разогреть» и оптимизировать код. Один замер ничего не доказывает. Сейчас соберём честный замер по шагам.

Шаг 1. Быстрая прикидка — console.time

Когда нужно просто понять порядок — «миллисекунды или секунды?» — хватит двух строк:

console.time("sort")
items.sort((a, b) => a.price - b.price)
console.timeEnd("sort") // sort: 12.4 ms

Метку "sort" пишешь одинаковую в обеих строках. Между ними можно вызвать console.timeLog("sort") — он покажет промежуточное время, не останавливая таймер.

Это прикидка, не точный замер. Для сравнения двух версий иди дальше.

Шаг 2. Точное время — performance.now

performance.now() возвращает время в миллисекундах с дробной частью от момента загрузки страницы. Разница двух вызовов — длительность:

const start = performance.now()
doWork()
const ms = performance.now() - start
console.log(`${ms.toFixed(2)} мс`)

Почему не Date.now()? Две причины. Он считает только целые миллисекунды. И он привязан к системным часам: если компьютер подвёл время по сети, разница может даже уйти в минус. performance.now() идёт только вперёд.

Одна деталь. Ради защиты от атак по времени браузер огрубляет performance.now(): обычно до 100 микросекунд, а на страницах с особыми заголовками изоляции — до 5. Поэтому одна очень быстрая операция покажет 0 или 0.1. Это следующий шаг.

Шаг 3. Повтори много раз и возьми медиану

Один прогон — шум. Замеряй серию, а первые прогоны выбрасывай как разогрев:

function bench(fn, runs = 50) {
  for (let i = 0; i < 5; i++) fn() // разогрев, не считаем
  const times = []
  for (let i = 0; i < runs; i++) {
    const t = performance.now()
    fn()
    times.push(performance.now() - t)
  }
  times.sort((a, b) => a - b)
  return times[Math.floor(times.length / 2)] // медиана
}

console.log("старая:", bench(oldVersion))
console.log("новая:", bench(newVersion))

Почему медиана, а не среднее? Одна пауза на сборку мусора на 40 мс испортит среднее для всех. Медиана её просто не заметит. Это та же логика, что у перцентилей.

Если функция очень быстрая, замеряй не один вызов, а цикл из тысячи и дели на тысячу.

И ещё правило честности: меряй обе версии на одних и тех же данных и в одном запуске. Сравнивать «старую вчера» с «новой сегодня» бессмысленно — у ноутбука могли быть открыты другие вкладки или включён режим экономии батареи.

Шаг 4. Асинхронный код — ставь await внутри замера

Частая ошибка — замер «0.1 мс» у запроса к серверу:

const t = performance.now()
fetch("/api/items") // забыли await
console.log(performance.now() - t) // 0.1 — замерили только старт

fetch сразу возвращает обещание, а ответ приходит позже. Замер должен ждать результат:

const t = performance.now()
const res = await fetch("/api/items")
await res.json()
console.log(performance.now() - t)

Тут меряется и сеть, и разбор ответа — то, что чувствует пользователь. Подробнее, почему код «не ждёт», — в статье про асинхронность.

Шаг 5. Не дай движку выкинуть работу

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

let sink
bench(() => { sink = heavyCalc(data) })
console.log(sink) // результат используется — движок не выкинет расчёт

Признак проблемы: «оптимизированная» версия внезапно в 1000 раз быстрее. Скорее всего, она ничего не делает.

Шаг 6. Посмотри замер на шкале DevTools

Хочешь увидеть, где именно тратится время среди отрисовки и сетевых запросов? Поставь метки:

performance.mark("load-start")
await loadFeed()
performance.mark("load-end")
performance.measure("загрузка ленты", "load-start", "load-end")

Открой DevTools → вкладка Performance → запиши профиль. Твой отрезок «загрузка ленты» появится на шкале отдельной полоской в разделе Timings. Рядом видно, что ещё происходило в этот момент.

Что получится

У тебя будет функция bench, которая даёт честное сравнение «было/стало» по медиане. Теперь на фразу ИИ «стало в три раза быстрее» ты отвечаешь цифрами, а не верой. Ну и находишь реально медленное место до того, как ускорять сайт наугад.

Как замерить время в Node.js?

Так же: performance.now() в Node есть глобально. Если нужны наносекунды, есть process.hrtime.bigint() — он возвращает время как BigInt.

Сколько прогонов достаточно?

Для сравнения двух версий обычно хватает 20–50. Если медианы отличаются меньше чем на 5–10%, считай, что версии одинаковые: такую разницу съест шум.

Учись вайб-кодингу, а не просто читай о нём

Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.

Открыть приложение
Робот KODiQ

ИИ-редактор KODiQ. Пишет про вайб-кодинг и AI-инструменты простым языком — каждый день.

Все статьи →