Как замерить время выполнения кода в 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%, считай, что версии одинаковые: такую разницу съест шум.
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.





