← Браузер/Performance API: измерение скорости#192 из 383← ПредыдущийСледующий →+25 XP
Полезно по теме:Гайд: как учить JavaScriptПрактика: async и сетьТермин: Event LoopТермин: Core Web Vitals

Performance API: измерение скорости

Оптимизировать то, что не измерено — значит гадать. Performance API даёт точные инструменты для измерения времени выполнения кода, загрузки ресурсов и взаимодействия пользователя с интерфейсом.

performance.now()

Высокоточный таймер с разрешением до микросекунд. Отсчитывает время от момента загрузки страницы (не от 1970 года как Date.now()):

const start = performance.now()

// ... что-то выполняется ...

const end = performance.now()
console.log(`Выполнено за ${end - start} мс`)
// Например: 1.2300000190734863 мс

Преимущество перед Date.now(): не зависит от системных часов и не изменяется при смене времени.

User Timing API: mark и measure

// Ставим метки
performance.mark('parse-start')
parseHugeJSON(data)
performance.mark('parse-end')

// Измеряем промежуток между метками
performance.measure('json-parsing', 'parse-start', 'parse-end')

// Получаем результат
const [measure] = performance.getEntriesByName('json-parsing')
console.log(`Парсинг JSON: ${measure.duration.toFixed(2)} мс`)

// Очистка
performance.clearMarks()
performance.clearMeasures()

PerformanceObserver

Подписка на появление новых записей производительности:

const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach(entry => {
    console.log(`${entry.name}: ${entry.duration.toFixed(2)} мс`)
  })
})

// Наблюдаем за пользовательскими метриками
observer.observe({ type: 'measure', buffered: true })

// Наблюдаем за LCP
observer.observe({ type: 'largest-contentful-paint', buffered: true })

Core Web Vitals через PerformanceObserver

LCP (Largest Contentful Paint):

new PerformanceObserver((list) => {
  const entries = list.getEntries()
  const lcp = entries[entries.length - 1]
  console.log('LCP:', lcp.startTime.toFixed(0), 'мс')
}).observe({ type: 'largest-contentful-paint', buffered: true })

FID/INP:

new PerformanceObserver((list) => {
  list.getEntries().forEach(entry => {
    console.log('Задержка взаимодействия:', entry.processingStart - entry.startTime, 'мс')
  })
}).observe({ type: 'event', durationThreshold: 16 })

CLS:

let cumulativeCLS = 0
new PerformanceObserver((list) => {
  list.getEntries().forEach(entry => {
    if (!entry.hadRecentInput) cumulativeCLS += entry.value
  })
  console.log('CLS:', cumulativeCLS.toFixed(4))
}).observe({ type: 'layout-shift', buffered: true })

Navigation Timing API

Детальная хронология загрузки страницы:

const nav = performance.getEntriesByType('navigation')[0]
console.log('DNS:', nav.domainLookupEnd - nav.domainLookupStart, 'мс')
console.log('TCP:', nav.connectEnd - nav.connectStart, 'мс')
console.log('TTFB:', nav.responseStart - nav.requestStart, 'мс')
console.log('DOM parsing:', nav.domInteractive - nav.responseEnd, 'мс')
console.log('Полная загрузка:', nav.loadEventEnd - nav.navigationStart, 'мс')

Resource Timing API

Тайминги для каждого загруженного ресурса:

const resources = performance.getEntriesByType('resource')
resources.forEach(res => {
  console.log(`${res.name}: ${res.duration.toFixed(0)} мс (${res.transferSize} байт)`)
})

Статистические функции

function percentile(arr, p) {
  const sorted = [...arr].sort((a, b) => a - b)
  const index = Math.ceil(sorted.length * p / 100) - 1
  return sorted[Math.max(0, index)]
}

const durations = [12, 45, 8, 234, 15, 90, 11, 67]
console.log('P50:', percentile(durations, 50))   // медиана
console.log('P95:', percentile(durations, 95))   // 95-й перцентиль
console.log('P99:', percentile(durations, 99))   // 99-й перцентиль

Оптимизация на основе данных

Собирай метрики → анализируй → оптимизируй → измеряй снова. Отправляй данные на сервер через navigator.sendBeacon() (не блокирует закрытие страницы):

window.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    const metrics = JSON.stringify(collectMetrics())
    navigator.sendBeacon('/analytics', metrics)
  }
})

Примеры

Система измерения производительности с метками, мерами и статистическим анализом

// Полная система Performance Tracking без браузерных API

class PerformanceTracker {
  constructor() {
    this._marks = new Map()         // name → timestamp
    this._measurements = new Map()  // name → number[]
  }

  // Аналог performance.now()
  _now() { return Date.now() }

  // Ставим метку времени
  mark(name) {
    this._marks.set(name, this._now())
    console.log(`[Mark] ${name} @ ${this._marks.get(name)} мс`)
  }

  // Измеряем промежуток между двумя метками
  measure(name, startMark, endMark) {
    const start = this._marks.get(startMark)
    const end = this._marks.get(endMark)

    if (start === undefined) throw new Error(`Метка не найдена: ${startMark}`)
    if (end === undefined) throw new Error(`Метка не найдена: ${endMark}`)

    const duration = end - start

    if (!this._measurements.has(name)) {
      this._measurements.set(name, [])
    }
    this._measurements.get(name).push(duration)

    console.log(`[Measure] ${name}: ${duration} мс`)
    return duration
  }

  // Вычисление p95
  _percentile(arr, p) {
    const sorted = [...arr].sort((a, b) => a - b)
    const index = Math.ceil(sorted.length * p / 100) - 1
    return sorted[Math.max(0, index)]
  }

  // Полная статистика по всем измерениям
  getMetrics() {
    const result = []

    for (const [name, durations] of this._measurements) {
      const count = durations.length
      const sum = durations.reduce((a, b) => a + b, 0)
      const avg = sum / count
      const min = Math.min(...durations)
      const max = Math.max(...durations)
      const p95 = this._percentile(durations, 95)

      result.push({ name, duration: durations[durations.length - 1], count, avg, min, max, p95 })
    }

    return result
  }

  // Удобная обёртка для измерения функции
  async time(name, fn) {
    const startMark = `${name}-start-${Date.now()}`
    const endMark = `${name}-end-${Date.now()}`
    this.mark(startMark)
    const result = await fn()
    this.mark(endMark)
    this.measure(name, startMark, endMark)
    return result
  }

  // Красивый отчёт
  report() {
    console.log('\n=== Performance Report ===')
    const metrics = this.getMetrics()
    metrics.forEach(m => {
      console.log(`${m.name}:`)
      console.log(`  count=${m.count}, avg=${m.avg.toFixed(1)}мс, min=${m.min}мс, max=${m.max}мс, p95=${m.p95}мс`)
    })
  }
}

// Демо: измеряем разные операции
const tracker = new PerformanceTracker()

// Симулируем операции с разным временем
async function simulateOperation(minMs, maxMs) {
  const delay = minMs + Math.random() * (maxMs - minMs)
  await new Promise(r => setTimeout(r, Math.round(delay)))
}

// Несколько итераций для статистики
console.log('Измеряем производительность...')

for (let i = 0; i < 5; i++) {
  tracker.mark(`api-start-${i}`)
  await simulateOperation(10, 50)
  tracker.mark(`api-end-${i}`)
  tracker.measure('api-call', `api-start-${i}`, `api-end-${i}`)
}

for (let i = 0; i < 3; i++) {
  tracker.mark(`render-start-${i}`)
  await simulateOperation(5, 20)
  tracker.mark(`render-end-${i}`)
  tracker.measure('render', `render-start-${i}`, `render-end-${i}`)
}

tracker.report()

Performance API: измерение скорости

Оптимизировать то, что не измерено — значит гадать. Performance API даёт точные инструменты для измерения времени выполнения кода, загрузки ресурсов и взаимодействия пользователя с интерфейсом.

performance.now()

Высокоточный таймер с разрешением до микросекунд. Отсчитывает время от момента загрузки страницы (не от 1970 года как Date.now()):

const start = performance.now()

// ... что-то выполняется ...

const end = performance.now()
console.log(`Выполнено за ${end - start} мс`)
// Например: 1.2300000190734863 мс

Преимущество перед Date.now(): не зависит от системных часов и не изменяется при смене времени.

User Timing API: mark и measure

// Ставим метки
performance.mark('parse-start')
parseHugeJSON(data)
performance.mark('parse-end')

// Измеряем промежуток между метками
performance.measure('json-parsing', 'parse-start', 'parse-end')

// Получаем результат
const [measure] = performance.getEntriesByName('json-parsing')
console.log(`Парсинг JSON: ${measure.duration.toFixed(2)} мс`)

// Очистка
performance.clearMarks()
performance.clearMeasures()

PerformanceObserver

Подписка на появление новых записей производительности:

const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach(entry => {
    console.log(`${entry.name}: ${entry.duration.toFixed(2)} мс`)
  })
})

// Наблюдаем за пользовательскими метриками
observer.observe({ type: 'measure', buffered: true })

// Наблюдаем за LCP
observer.observe({ type: 'largest-contentful-paint', buffered: true })

Core Web Vitals через PerformanceObserver

LCP (Largest Contentful Paint):

new PerformanceObserver((list) => {
  const entries = list.getEntries()
  const lcp = entries[entries.length - 1]
  console.log('LCP:', lcp.startTime.toFixed(0), 'мс')
}).observe({ type: 'largest-contentful-paint', buffered: true })

FID/INP:

new PerformanceObserver((list) => {
  list.getEntries().forEach(entry => {
    console.log('Задержка взаимодействия:', entry.processingStart - entry.startTime, 'мс')
  })
}).observe({ type: 'event', durationThreshold: 16 })

CLS:

let cumulativeCLS = 0
new PerformanceObserver((list) => {
  list.getEntries().forEach(entry => {
    if (!entry.hadRecentInput) cumulativeCLS += entry.value
  })
  console.log('CLS:', cumulativeCLS.toFixed(4))
}).observe({ type: 'layout-shift', buffered: true })

Navigation Timing API

Детальная хронология загрузки страницы:

const nav = performance.getEntriesByType('navigation')[0]
console.log('DNS:', nav.domainLookupEnd - nav.domainLookupStart, 'мс')
console.log('TCP:', nav.connectEnd - nav.connectStart, 'мс')
console.log('TTFB:', nav.responseStart - nav.requestStart, 'мс')
console.log('DOM parsing:', nav.domInteractive - nav.responseEnd, 'мс')
console.log('Полная загрузка:', nav.loadEventEnd - nav.navigationStart, 'мс')

Resource Timing API

Тайминги для каждого загруженного ресурса:

const resources = performance.getEntriesByType('resource')
resources.forEach(res => {
  console.log(`${res.name}: ${res.duration.toFixed(0)} мс (${res.transferSize} байт)`)
})

Статистические функции

function percentile(arr, p) {
  const sorted = [...arr].sort((a, b) => a - b)
  const index = Math.ceil(sorted.length * p / 100) - 1
  return sorted[Math.max(0, index)]
}

const durations = [12, 45, 8, 234, 15, 90, 11, 67]
console.log('P50:', percentile(durations, 50))   // медиана
console.log('P95:', percentile(durations, 95))   // 95-й перцентиль
console.log('P99:', percentile(durations, 99))   // 99-й перцентиль

Оптимизация на основе данных

Собирай метрики → анализируй → оптимизируй → измеряй снова. Отправляй данные на сервер через navigator.sendBeacon() (не блокирует закрытие страницы):

window.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    const metrics = JSON.stringify(collectMetrics())
    navigator.sendBeacon('/analytics', metrics)
  }
})

Примеры

Система измерения производительности с метками, мерами и статистическим анализом

// Полная система Performance Tracking без браузерных API

class PerformanceTracker {
  constructor() {
    this._marks = new Map()         // name → timestamp
    this._measurements = new Map()  // name → number[]
  }

  // Аналог performance.now()
  _now() { return Date.now() }

  // Ставим метку времени
  mark(name) {
    this._marks.set(name, this._now())
    console.log(`[Mark] ${name} @ ${this._marks.get(name)} мс`)
  }

  // Измеряем промежуток между двумя метками
  measure(name, startMark, endMark) {
    const start = this._marks.get(startMark)
    const end = this._marks.get(endMark)

    if (start === undefined) throw new Error(`Метка не найдена: ${startMark}`)
    if (end === undefined) throw new Error(`Метка не найдена: ${endMark}`)

    const duration = end - start

    if (!this._measurements.has(name)) {
      this._measurements.set(name, [])
    }
    this._measurements.get(name).push(duration)

    console.log(`[Measure] ${name}: ${duration} мс`)
    return duration
  }

  // Вычисление p95
  _percentile(arr, p) {
    const sorted = [...arr].sort((a, b) => a - b)
    const index = Math.ceil(sorted.length * p / 100) - 1
    return sorted[Math.max(0, index)]
  }

  // Полная статистика по всем измерениям
  getMetrics() {
    const result = []

    for (const [name, durations] of this._measurements) {
      const count = durations.length
      const sum = durations.reduce((a, b) => a + b, 0)
      const avg = sum / count
      const min = Math.min(...durations)
      const max = Math.max(...durations)
      const p95 = this._percentile(durations, 95)

      result.push({ name, duration: durations[durations.length - 1], count, avg, min, max, p95 })
    }

    return result
  }

  // Удобная обёртка для измерения функции
  async time(name, fn) {
    const startMark = `${name}-start-${Date.now()}`
    const endMark = `${name}-end-${Date.now()}`
    this.mark(startMark)
    const result = await fn()
    this.mark(endMark)
    this.measure(name, startMark, endMark)
    return result
  }

  // Красивый отчёт
  report() {
    console.log('\n=== Performance Report ===')
    const metrics = this.getMetrics()
    metrics.forEach(m => {
      console.log(`${m.name}:`)
      console.log(`  count=${m.count}, avg=${m.avg.toFixed(1)}мс, min=${m.min}мс, max=${m.max}мс, p95=${m.p95}мс`)
    })
  }
}

// Демо: измеряем разные операции
const tracker = new PerformanceTracker()

// Симулируем операции с разным временем
async function simulateOperation(minMs, maxMs) {
  const delay = minMs + Math.random() * (maxMs - minMs)
  await new Promise(r => setTimeout(r, Math.round(delay)))
}

// Несколько итераций для статистики
console.log('Измеряем производительность...')

for (let i = 0; i < 5; i++) {
  tracker.mark(`api-start-${i}`)
  await simulateOperation(10, 50)
  tracker.mark(`api-end-${i}`)
  tracker.measure('api-call', `api-start-${i}`, `api-end-${i}`)
}

for (let i = 0; i < 3; i++) {
  tracker.mark(`render-start-${i}`)
  await simulateOperation(5, 20)
  tracker.mark(`render-end-${i}`)
  tracker.measure('render', `render-start-${i}`, `render-end-${i}`)
}

tracker.report()

Задание

Реализуй createPerformanceTracker() с методами: mark(name) сохраняет текущее время, measure(name, startMark, endMark) вычисляет продолжительность и сохраняет (поддерживает несколько измерений с одним именем), getMetrics() возвращает массив объектов { name, duration (последнее), count, avg, min, max, p95 } для каждого уникального имени измерения.

Подсказка

marks.set(name, Date.now()) сохраняет метку. В measure() duration = end - start. measurements.get(name).push(duration) накапливает значения. В getMetrics() avg = sum / count. Math.min(...durations) и Math.max(...durations) находят минимум и максимум. percentile получает индекс Math.max(0, index).

Загружаем среду выполнения...
Загружаем AI-помощника...