Производительность анимации и частота кадров - Web Performance

Производительность анимации и частота кадров
============================================

Анимация в Вебе может быть сделана с помощью `SVG` (en-US), `JavaScript`, включая `<canvas>` и `WebGL`, CSS `animation`, `<video>`, анимированных GIF и даже с помощью анимированных PNG и других типов изображений. Производительность CSS-анимации может отличаться от одного CSS-свойства к другому, а попытка анимировать некоторые "дорогие" CSS-свойства может привести к зависаниям (jank (en-US)), даже несмотря на то, что браузер борется за то, чтобы смягчить частоту смены кадров frame rate.

Для анимированных медиа, таких как видео и GIF, основная проблема производительности - это размер файлов. Скачивание больших по объёму файлов не может не повлиять на производительность системы или на то, как эту систему воспринимает пользователь.

Анимации, основанные на коде, будь то CSS, SVG, <canvas>, webGL или другие JavaScript анимации, могут нести проблемы производительности сами в себе, даже если файлы этого кода скачиваются быстро. Такие анимации могут потреблять всё время CPU и приводить к зависаниям.

Несомненно, производительность каждой конкретной системы - очень чувствительная тема. Улучшив клиентскую производительность, вы сможете не только ускорить работу приложения, но даже затронете физический аспект - сможете сэкономить заряд батареи мобильных устройств и / или понизите температуру устройства. Поэтому очень важно владеть инструментами для измерения производительности. Они помогут вам понять всю работу, которую проводит браузер, пока рендерит ваше приложение и поможет избежать и диагностировать проблемы, когда они происходят.

Пользователи ожидают, что взаимодействие с интерфейсом будет плавным, а интерфейс будет отзывчивым. Анимация помогает улучшить восприятие приложения, сделав его быстрым и отзывчивым; но анимация так же может замедлить его и привести к зависаниям, если она сделана неумело. Отзывчивые интерфейсы должны иметь частоту смены кадров, равную 60 кадров в секунду (fps) (en-US). В то время, как не всегда возможно поддерживать такую частоту, очень важно поддерживать быструю и устойчивую смену кадров для анимации.

Мы рассмотрим, как можно использовать инструменты браузера для инспектирования частоты смены кадров. Так же, мы обсудим некоторые подсказки, как организовать и поддерживать быструю и стабильную смену кадров.

Графики frame rate (en-US) и waterfall из встроенных инструментов браузера дают информацию о том, как браузер выполняет работу по анимации. Используя эти инструменты, вы можете измерить fps приложения и диагностировать узкие места, в которых fps уменьшается.

С помощью CSS-анимации (en-US) вы указываете ключевые кадры (keyframes) (en-US), каждый из которых использует определённые CSS-свойства, чтобы определить внешний вид элемента в конкретный (ключевой) момент анимации. Браузер создаёт анимации с помощью плавных переходов от одного ключевого кадра к следующему.

Если сравнивать анимацию с помощью JavaScript и CSS, вы увидите, что CSS-анимации проще создать. Более того, CSS-анимации гарантируют лучшую производительность, так как они автоматически делегируют некоторые задачи браузеру. Например, в случае CSS браузер сам решает, когда нужно отрендерить кадр, а когда пропустить кадр, если это необходимо.

Однако, стоимость изменения разных CSS-свойств варьируется. Общепринято, что 60 кадров в секунду - это достаточная частота, чтобы анимация выглядела мягкой и плавной. Несложный подсчёт говорит, что при частоте 60 кадров в секунду, браузер имеет лишь 16.7 миллисекунд, чтобы выполнить все скрипты, пересчитать стили, скомпоновать слои и отрисовать новый кадр. Отсюда следует, что медленные скрипты и анимация дорогих CSS-свойств может может привести к зависаниям (en-US), так как браузер все ещё будет пытаться вычислить все 60 кадров.

Стоит заметить, что 60 кадров в секунду - это стандартная частота обновления экрана. Существуют экраны с гораздо большим FPS. Например, экраны игровых ноутбуков или iPad Pro 2018 имеют частоту смены кадров, равную 120 fps и выше. Для таких устройств производители браузеров ограничивают частоту 60-ю кадрами в секунду, но с помощью некоторых опций этот лимит можно убрать. И в этом случае, на формирование каждого кадра устройство будет отводить лишь 8.6 миллисекунд.

Этапы рендеринга
----------------

Процесс, используемый браузером для отображения анимации CSS-свойств, может быть представлен как последовательность этапов из следующего изображения:

![](/ru/docs/Web/Performance/Animation_performance_and_frame_rate/css-rendering-waterfall.png)

1. **Recalculate Style**: когда любое CSS-свойство для элемента изменяется, браузер должен заново вычислить результирующий набор свойств.
2. **Layout**: затем браузер использует вычисленные стили для того, чтобы понять позицию и геометрию элементов - как изменённого, так и рядом лежащих. Эта операция называется "layout", но иногда её так же называют "reflow".
3. **Paint**: наконец, браузер должен перерисовать элементы на экране. Но этот этап не обязательно должен быть простым, как на изображении. Страница может быть разделена на слои, каждый из которых перерисовывается независимо, а только после этого они комбинируются в процессе, который называется композицией "Composition".

Процессы, которые браузер использует для отрисовывания изменений на элементе <canvas> отличаются. В случае <canvas>, Layout не происходит. Скорее, страница будет перерисована с помощью JavaScript canvas API.

В любом случае, вычисление каждого следующего кадра должно происходить достаточно быстро, чтобы успеть попасть в частоту обновления экрана, чтобы не было зависаний.

Стоимость CSS-свойств
---------------------

На всех этапах рендеринга изменение некоторых свойств является более затратным, других - менее:

| Тип свойства | Стоимость | Примеры |
| --- | --- | --- |
| Свойства, затрагивающие геометрию или позицию элемента, запускают **весь** процесс заново: новое вычисление стилей, layout и перерисовку. |

 | `left` (en-US) `max-width` `border-width` `margin-left` `font-size` |
| Свойства, не затрагивающие геометрию и позиционирование элементов, но не лежащие в отдельном слое, запускают только вычисление стилей и перерисовку, но не Layout. |

 | `color` (en-US) |
| Свойства, которые рендерятся в отдельном слое не запускают даже repaint, так как результат обновления обрабатывается на этапе композиции. |

 | `transform` `opacity` |

**Примечание:** На Веб-сайте CSS Triggers хорошо показано, какие CSS-свойства вызывают те или иные этапы обновления в разных браузерах.

Пример: margin против transform
-------------------------------

В этом разделе мы увидим, как инструмент Waterfall может указать на разницу между анимацией `margin` и `transform`.

Задумка этого сценария не в том, чтобы убедить вас, что анимация через `margin` - это всегда плохая идея. Сценарий нужен, чтобы продемонстрировать, как инструменты могут помочь вам понять работу браузера и как вы можете применить эти знания для оптимизации.

Если вы хотите самостоятельно разобраться с этим примером, вы можете найти демо здесь. Демо выглядит так:

![](/ru/docs/Web/Performance/Animation_performance_and_frame_rate/css-animations-demo.png)На экране всего два контрола: кнопка "start / stop" для запуска и остановки анимации и радио-кнопки для выбора свойства, с помощью которого происходит анимация: `margin`, или `transform`.

Так же на странице есть некоторое количество элементов со свойствами `linear-gradient` (en-US) и `box-shadow` Мы обращаем внимание именно на эти два свойства, так как они относительно дорогие.

Так же существует видео-версия анализа и оптимизации этой страницы.

### Анимация свойства margin

Оставим включённой опцию "Use margin" и начнём анимацию. В это же время откроем "Performance tool" и нажмём кнопку "записать" (make a recording). Нам понадобится лишь пара секунд записи.

Откройте первую запись. Точное содержимое, которое вы увидите, зависит от вашего устройства, системной нагрузки и окружения, но, в целом это должно выглядеть так:

![](/ru/docs/Web/Performance/Animation_performance_and_frame_rate/margin-recording.png)

На экране показаны три отдельных секции: (a) обзор этапов рендеринга (Waterfall), (b) частота кадров, и (c) детали на временной шкалы.

#### Обзор этапов рендеринга на временной шкале (Waterfall)

![](/ru/docs/Web/Performance/Animation_performance_and_frame_rate/margin-timeline-overview.png)

Сейчас здесь показаны ужатые этапы рендеринга Waterfall. Как видите, большая часть графика заполнена зелёным цветом - это говорит нам о том, что мы тратим много ресурсов на отрисовывание (en-US).

#### Частота кадров (Frame Rate)

![](/ru/docs/Web/Performance/Animation_performance_and_frame_rate/margin-frame-rate.png)

Эта секция показывает частоту кадров (en-US). Средняя частота на примере - 46.67fps. Это ниже, чем желаемые 60fps. Однако, ещё хуже то, что частота кадров нестабильна - есть этапы, где частота кадров снижается до 20 и даже до 10 fps. Маловероятно, что вы увидите здесь плавную анимацию, особенно если добавите какое-то взаимодействие с пользователем.

#### Этапы рендеринга в деталях (Waterfall)

Оставшаяся часть записей показа в секции "Waterfall view". Если вы пролистаете этот список, вы увидите что-то наподобие этого:

![](/ru/docs/Web/Performance/Animation_performance_and_frame_rate/margin-timeline.png)

Это шаги рендеринга (rendering waterfall (en-US)). Для каждого кадра анимации мы вычисляем стили для каждого элемента, потом вычисляем Layout, а затем перерисовываем все элементы.

Из таблицы видно, что особый урон производительности наносит перерисовка Paint (зелёные полосы). Например, выделенный этап Paint занял 13.11мс. Учитывая, что весь бюджет рендеринга - 16.7мс, неудивительно, что мы увидели падения fps.

Вы можете поэкспериментировать с некоторыми свойствами. Например, попробуйте убрать box-shadow с помощью инспектора страницы (Page / Element Inspector), замерьте производительность и посмотрите, как это отразилось на производительности. Затраты на Paint уменьшатся значительно. Но они все ещё есть. Мы ещё вернёмся к этому вопросу позже, когда будем изучать использование `transform` вместо `margin`. Вы увидите, что от затрат на этот этап можно избавиться полностью.

### Анимация свойства transform

Теперь, переключитесь на "Use transform" и запишите новые данные. Это должно выглядеть примерно так:

![](/ru/docs/Web/Performance/Animation_performance_and_frame_rate/transform-recording.png)

#### Обзор этапов рендеринга на временной шкале (Waterfall)

![](/ru/docs/Web/Performance/Animation_performance_and_frame_rate/transform-timeline-overview.png)

В сравнении с версией, которая использует margin (en-US), мы видим намного меньше зелёного, но намного больше фиолетового цвета. Это говорит о том, что вместо paint мы теперь тратим ресурсы на этапы layout или style recalculation.

#### Частота кадров (Frame Rate)

![](/ru/docs/Web/Performance/Animation_performance_and_frame_rate/transform-frame-rate.png)

В сравнении с версией, которая использует margin (en-US), показатели fps здесь выглядят достаточно хорошо. Средняя частота кадров близка к 60fps, а стабильность fps, за исключением падения fps в начале значительно выросла.

#### Этапы рендеринга в деталях (Waterfall)

В этой секции мы видим объяснения тому, что fps значительно улучшился. Мы больше не тратим время на layout и перерисовку элементов:

![](/ru/docs/Web/Performance/Animation_performance_and_frame_rate/transform-timeline.png)

Здесь, используя `transform`, мы заметно улучшили производительность приложения. А инструменты разработчика помогли нам это сделать.


Производительность CSS и JavaScript анимации - Web Performance

Производительность CSS и JavaScript анимации
============================================

Анимация является критичным инструментом для улучшения пользовательского опыта во многих приложениях. Существует много путей создания анимации в web, например, основанные на CSS-свойствах `transitions`/`animations` или на JavaScript (using `requestAnimationFrame()`). В этой статье мы проанализируем производительность CSS и JavaScript анимаций и сравним их.

CSS transition и animation
--------------------------

Оба этих свойства могут использоваться для создания анимации. Каждое из них имеет своё специфичное назначение:

* CSS `transitions` предоставляет простой способ создать анимацию, которая происходит при переходе от текущего состояния к конечному, например, переход от обычной кнопки к кнопке в состоянии hover. Даже если элемент в середине перехода от одного стиля к другому, новый эффект transition стартует немедленно, вместо того, чтобы дожидаться, пока запущенный ранее эффект завершится. Подробнее здесь: Использование CSS transitions (en-US).
* CSS `animations`, с другой стороны, позволяет разработчикам создавать анимацию, основанную на ключевых кадрах (keyframes), которые указывают этапы, которые должна пройти анимация от начального до финального состояния. CSS animation состоит из двух компонент: описание свойства, которое указывает на анимацию, а так же набор ключевых кадров, которые указывают начальное, финальное и промежуточные состояния элемента. Подробнее здесь: Использование CSS animations (en-US).

Если говорить о производительности - между этими двумя подходами нет разницы. Оба подхода основаны на одном и том же механизме, которые описаны далее.

requestAnimationFrame
---------------------

API `requestAnimationFrame()` предоставляет эффективный способ создания анимаций в JavaScript. Функция (callback), которую вы передаёте в этот метод, будет вызываться перед каждой следующей отрисовкой нового фрейма. Главное отличие от `setTimeout()`/`setInterval()` (en-US) в том, что здесь вам не нужно указывать время, через которое функция запустится. `requestAnimationFrame()` работает гораздо эффективнее, учитывая частоту кадров и производительность системы. Разработчики могут создавать анимацию, просто изменяя стили элемента каждый раз, когда происходит подготовка нового кадра (или когда обновляется полотно Canvas или в других случаях).

**Примечание:** Подобно CSS transition и animation, `requestAnimationFrame()` приостанавливает работу, когда текущий таб переводится в фоновый режим (например, при смене фокуса)

Для подробностей ознакомьтесь с анимирование с JavaScript: от setinterval до requestAnimationFrame.

Сравнение производительности:transitions и requestAnimationFrame
----------------------------------------------------------------

По факту, в большинстве случаев, производительность анимаций CSS практически идентична анимациям на JavaScript. По крайней мере в Firefox. Авторы некоторых JavaScript библиотек для анимации, например GSAP или Velocity.JS, даже берутся утверждать, что их решения могут работать быстрее, чем аналогичные решения на CSS. Такое возможно, потому что CSS transitions/animations просто заново вычисляют стили элементов в основном потоке процессора сразу перед тем, как срабатывает событие repaint, что примерно то же самое, что вычислять стили заново с помощью `requestAnimationFrame()`. Если обе анимации выполняются в одном потоке, то разницы в производительности не будет.

В следующей секции мы пройдёмся по тестам производительности, используя Firefox, чтобы увидеть, какие методы анимации работают эффективнее.

### Включение измерения частоты кадров FPS

Для начала нам нужно включить инструменты измерения частоты кадров (FPS Tools), чтобы иметь возможность видеть текущую частоту кадров

1. В поле ввода URL наберите *about:config*; Нажмите на кнопку *I'll be careful, I promise!, чтобы войти на страницу конфигурации*.

 ![](/ru/docs/Web/Performance/CSS_JavaScript_animation_performance/pic1.png)
2. В поле поиска введите `layers.acceleration.draw-fps`.
3. Нажмите два раза на ячейку, чтобы присвоить значение `true`. Теперь вы видите три розовых блока в верхнем левом углу окна. Первый блок указывает FPS.

 ![](/ru/docs/Web/Performance/CSS_JavaScript_animation_performance/pic2.png)
### Запуск теста

Для начала, в нашем тесте мы будем анимировать 1000 элементов `<div>` с помощью CSS.

Нажав на кнопку, вы можете переключить метод анимации на `requestAnimationFrame()`.

Попробуйте запустить оба метода и сравнить FPS. Скорее всего, вы увидите, что частота кадров отличается - анимации с CSS заметно быстрее. В следующей главе мы разберём - почему.

### Анимация вне основного потока процесса

Браузерный JavaScript является строго однопоточным языком, то есть он не может одновременно работать над двумя задачами. В этом кроется проблема анимации с помощью JavaScript. Выполняя такую анимацию, вы занимаете процессор, который мог бы в это время заниматься другими функциями. В противоположность этому, CSS-анимации могут быть выделены в отдельный поток, то есть при выполнении таких анимаций браузер не блокирует выполнение других процессов.

Для того, чтобы выделить анимацию CSS в отдельный процесс, нам нужно убедиться, что изменяемые свойства не запускают этапы reflow/repaint (подробнее здесь: CSS triggers). Если изменяемые CSS-свойства не делают этого, то мы можем вынести операции по вычислению стилей в отдельный поток. Наиболее известное свойство - это CSS Transform, которое выводит элемент в отдельный слой. Если элемент представляет из себя отдельный слой, то вычисление каждого следующего кадра может быть сделано на графическом процессоре (GPU). Это радикально улучшает производительность, особенно на мобильных устройства. Подробности здесь: OffMainThreadCompositing.

Вы можете отключить выведение анимации в отдельный поток, чтобы посмотреть, как эта особенность влияет на FPS. Для этого в настройках Firefox найдите флаг `layers.offmainthreadcomposition.async-animations`. И переключите его в `false`.

![](/ru/docs/Web/Performance/CSS_JavaScript_animation_performance/pic3.png)

После выключения этой опции вы увидите, что FPS при использовании CSS стал таким же, как и при использовании JS.

Итог
----

Браузеры способы оптимизировать рендеринг не только программно, но и аппаратно. В целом, вам нужно стараться использовать CSS transitions/animations везде, где это возможно. Если же ваши анимации действительно сложны - помните, что писать анимацию на JavaScript нужно только с использованием `requestAnimationFrame()` .


Использование dns-prefetch - Web Performance

Использование dns-prefetch
==========================

**`DNS-prefetch`** (предзагрузка DNS) - это попытка определить IP адрес по имени домена до того, как ресурс с этого домена будет фактически запрошен. Это может быть загрузчик файлов, который используется позже или ссылка, по которой, вероятно, перейдёт пользователь.

Зачем использовать dns-prefetch?
--------------------------------

Бывают случаи, когда в вашем приложении используются ссылки на домены, отличные от основного домена приложения. Например, внутри вашего приложения на "a.com" есть ссылка на домен "b.org". Для того, чтобы пройти по этой ссылке, пользовательский клиент должен сначала выяснить, какой адрес в интернете соответствует доменному имени "b.org". Этот процесс называется `DNS resolution.` И хотя механизмы кеширования DNS помогают сократить время запросов, DNS Resolution заметно влияет на время ожидания запроса из-за того, что первый клиентский запрос уходит сначала на DNS сервера и дожидается ответа от них. Для приложений, которые открывают соединения ко многим сторонним доменам, влияние DNS Resolution может сильно уменьшить производительность загрузки.

`dns-prefetch` помогает разработчикам замаскировать ожидание DNS resolution. HTML `<link>` элемент предлагает подобную функциональность с помощью атрибута `rel` (en-US), значение которого может содержать `dns-prefetch`. В этом случае, предзагрузка DNS произойдёт для домена, указанного в атрибуте href:

Синтаксис
---------

```html
<link rel="dns-prefetch" href="https://fonts.gstatic.com/" />
```

Примеры
-------

```html
<html>
  <head>
    <link rel="dns-prefetch" href="https://fonts.gstatic.com/" />
    <!-- and all other head elements -->
  </head>
  <body>
    <!-- your page content -->
  </body>
</html>
```

Вы должны помещать `dns-prefetch` подсказки в элемент `<head>` для каждого уникального стороннего домена, с которого могут быть запрошены ресурсы, но нужно держать некоторые детали в голове.

Лучшие практики
---------------

Необходимо учитывать 3 вещи:

**Во-первых,** `dns-prefetch` эффективен только для выполнения DNS запросов на сторонние cross-origin домены, поэтому следует избегать этой инструкции при попытке загрузить ресурсы с текущего домена. Связано это с тем, что IP вашего домена уже был определён к тому времени, когда браузер увидел эту инструкцию.

**Во-вторых,** существует возможность добавить `dns-prefetch` (и другие подсказки) как HTTP заголовок с помощью HTTP Link field (en-US):
```

Link: <https://fonts.gstatic.com/>; rel=dns-prefetch
```

**В третьих,** хорошей практикой считается использование `dns-prefetch` в паре с `preconnect`. В то время, как `dns-prefetch` срабатывает только при DNS поиске, `preconnect` устанавливает соединение к нужному серверу. Этот процесс включает в себя DNS resolution, установку TCP соединения и установление безопасного соединения (TLS рукопожатие), если оно доступно. Комбинирование этих двух инструкций даёт возможность ещё больше сократить время ожидания для кросс-доменных запросов. Вы можете использовать их вместе таким образом:

```html
<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin />
<link rel="dns-prefetch" href="https://fonts.gstatic.com/" />
```

**Примечание:** если приложению необходимо установить соединение со множеством доменов, использование preconnect контрпродуктивно. `preconnect` лучше использовать для наиболее критических соединений. Для менее важных достаточно использовать `<link rel="dns-prefetch">` для сокращения времени DNS запроса.

В совместном использовании этих двух техник есть логика. Она заключается в том, что dns-prefetch поддерживается большим количеством браузеров, чем preconnect. Клиенты, которые ещё не поддерживают preconnect, все ещё будут получать бонусы от работы dns-prefetch. Так как эти инструкции относятся к HTML, они очень толерантны к ошибкам. Если какой-то устаревший браузер встречает dns-prefetch, ваш сайт не сломается. Вы просто не получите улучшений от этой инструкции.

Некоторые ресурсы, например, шрифты, загружаются в анонимном режиме. В подобных случаях вы должны указывать crossorigin атрибут с инструкцией preconnect. Если вы пропустите её, то браузер выполнит только DNS запрос.

Смотрите также
--------------

* <link>
* HTML атрибут: rel (en-US)
* crossorigin
* Cross-Origin Resource Sharing (CORS)
* HTTP заголовки
* HTTP заголовок Link (en-US)


Оптимизация запуска - Web Performance

Оптимизация запуска
===================

Часто упускаемый из вида аспект разработки приложений - это скорость запуска приложения. Даже если вы прикладываете много усилий к оптимизации работы приложений, именно этот этап может быть пропущен. Как долго запускается ваше приложение? Создаётся ли впечатление, что устройство зависает, пока приложение запускается? Все эти симптомы заставляют пользователя считать, что приложение сломано или что-то идёт не так. Всегда будет не лишним убедиться, что ваше приложение запускается плавно. В этой статье мы поделимся некоторыми подсказками, которые помогут вам оптимизировать запуск приложения, вне зависимости от того, пишете ли вы его с нуля или работаете над уже существующим.

Приятный запуск
---------------

Не имеет значения, какую платформу вы используете, всегда будет правильным обеспечить как можно более быструю загрузку приложения. Так как это наиболее общая проблема, мы не будем заострять на ней внимание здесь. Однако, мы обратим внимание на наибольшую проблему Web-приложений: синхронная загрузка ресурсов. Решением этой проблемы был бы максимальный переход на асинхронную загрузку ресурсов. Это означает, что инициализирующий код не должен запускаться в одном единственном обработчике событий в главном потоке процесса.

Вместо этого, вы можете разбить ваш код так, чтобы часть его обрабатывалась в Web worker, что выделит его в отдельные фоновые неблокирующие треды (например, запросы данных и их обработка). Затем, все, что должно быть выполнено в основном потоке (например, пользовательские события или рендеринг интерфейса) должно быть разбито на небольшие кусочки так, чтобы обработчик браузера выполнял небольшие куски кода итеративно, а не за один подход. Это позволит вашему приложению выглядеть отзывчивым даже во время первоначальной загрузки.

Почему так важно делать все это асинхронно? Помимо причин, перечисленных выше, подумайте о влиянии, которые оказывают зависшие приложения: пользователь не может отменить запуск, даже если он запустил приложение по ошибке. Если приложение запускается в браузере, пользователь даже не сможет закрыть вкладку. В конечном счёте это может привести даже к системным предупреждениям о "медленных скриптах" или "исчерпании памяти". А ведь было время, когда каждая вкладка не работала в отдельном процессе, как сейчас, а потому повисшая вкладка приводила к зависанию всего браузера! Вы должны не просто делать загрузку приложения "мягкой", но и давать пользователю знать о процессе загрузки: показывайте ему прогресс-бары или этапы, которые проходит приложение. Это позволит пользователю убедиться, что приложение не зависло.

### Было бы желание

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

С другой стороны, потребуются некоторые ухищрения, если вы пытаетесь портировать существующее десктопное приложение в Web или привыкли писать такие. Десктопные приложения обычно не нуждаются в написании кода в асинхронной манере, потому что операционная система берёт заботу об этом на себя. В исходниках такого приложения может быть лишь один поток обработки кода, но даже он может быть легко разбит на асинхронные этапы (запуском каждой новой итерации потока по отдельности). В таких приложениях запуск часто представляет собой последовательную монолитную процедуру, которая время от времени обращается к метрикам прогресса и обновляет их.

И хотя вы можете использовать Web workers, чтобы обработать очень большие и "тяжёлые" скрипты асинхронно, вы должны учитывать некоторые ограничения. Web Worker-ы не имеют доступа к некоторым API браузера: DOM, WebGL или audio, они не могут посылать синхронные сообщения в основной поток, вы даже не можете проксировать некоторые из этих API в основной поток. Это всё означает, что вы можете поместить в Web Worker-ы только "чистые функции", но вам всё равно придётся вычислять огромную часть данных в основном потоке. Поэтому очень полезно разрабатывать систему так, чтобы в ней было как можно больше чистых функций - так их будет проще делегировать в последствии.

Тем не менее, даже код в основном потоке можно сделать асинхронным, приложив лишь небольшие усилия.

Переход к асинхронности
-----------------------

Есть несколько предложений, как сделать процесс загрузки асинхронным:

* Используйте атрибуты `defer` или `async` на тегах script. Это позволит HTML парсерам продолжать парсинг документа, вместо того, чтобы переключаться на скачивание / парсинг / выполнение скриптов.
* Если вам нужно декодировать какой-то ресурс (например, JPEG для превращения его в текстуру в WebGL), делайте это в Worker-ах.
* Когда вы имеете дело с данными, поддерживаемыми браузером, используйте встроенные декодеры, а не пишите свои. Это касается, например, декодинга изображений / видео. Производители браузеров стараются применять лучшие техники, наиболее совместимые с их кодовой базой. Кроме того, многие браузеры поддерживают параллельное выполнение таких обработчиков.
* Любая обработка данных, которая может быть сделана параллельно **должна** быть сделана параллельно.
* Не подключайте скрипты и стили, которые не критичны для запуска приложения, в инициализирующий HTML. Загружайте их только тогда, когда это необходимо.
* Сокращайте размеры ваших JavaScript-файлов. Попробуйте отправлять минифицированный код и используйте сжатие, например Gzip или Brotli.
* Используйте дополнительные атрибуты (например preconnect или preload) всегда, когда есть возможность сказать браузеру, что является критическим для вашей системы.

Чем больше вещей вы можете сделать асинхронно, тем большие преимущества многоядерных процессоров вы сможете задействовать.

### Проблемы портирования

Когда первоначальная загрузка завершена и начинается обработка приложения в основном потоке, вполне возможно, что ваше приложение обязано быть однопоточным, особенно если это портированная версия. Очень важно попытаться помочь процессу запуска приложения, порефакторив код, сделав его композитным, состоящим из маленьких кусочков, каждый из которых может быть обработан последовательно основным потоком. В этом случае, в промежутке между выполнением этих кусочков кода браузер сможет выделить время на обработку ввода (например, клик) или на обработку микро-задач.

В случае, если вы портируете ваше приложение, вы наверняка знаете о Emscripten. Это решение предоставляет API, которое поможет с подобным рефакторингом. Например, вы можете использовать `emscripten_push_main_loop_blocker()`, чтобы определить функцию, как выполняемую после того, как основной поток разрешит продолжить работу. Создавая такие функции, создавая очередь, которая должна выполниться в определённом порядке, вы можете с лёгкостью разгрузить основной поток.

Конечно, все это не отменяет необходимости рефакторинга кода так, чтобы он работал лучше и это займёт время. Но для старта этого может оказаться достаточно.

### Насколько далеко я должен зайти?

Стоит держать в голове, что браузер начинает беспокоиться о вашем скрипте в том случае, если он блокирует основной поток больше, чем на 10 секунд. В идеале, вы не должны блокировать работу страницы так долго. И пока вы держите показатели загрузки приложения ниже этих значение - все должно быть ок. Но не забывайте, что если кто-то имеет не такой мощный компьютер или мобильное устройство, как у вас, то код, выполняющийся у вас за 2 секунды, у этого пользователя может занять 10 секунд. Для этого полезно использовать CPU Throttling, который предоставляется средствами разработчиков в некоторых браузерах.

Другие предложения
------------------

Существуют другие вещи, которые могут влиять на скорость запуска приложения, и одна лишь асинхронность не спасёт от всего. Вот несколько из них:

Время загрузки

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

Влияние GPU

Компиляция шейдеров или загрузка текстур в GPU может занять время, особенно для по-настоящему сложных WebGL приложений. Давайте пользователю знать о том, что загрузка или обработка данных продолжается фоном, чтобы он знал, когда лучше переходить в загруженные части приложения.

Размер данных

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

Субъективные факторы

Любая вещь, которую вы предлагаете пользователю во время запуска приложения и с которой он может взаимодействовать, заставляет время течь быстрее. Это забавно, но прогресс-бар, который показывается 3 секунды ощущается более быстрым, чем пустая страница в течение 1 секунды. Добавляйте подсказки к вашему приложению, рассказывайте интересные факты или просто показывайте что-то красивое - это уже улучшит восприятие.

Смотрите также
--------------

* Apps (en-US)
* Games
Информация об оригинальном документе
------------------------------------

* Автор: Alon Zakai
* Исходник: BananaBread (or any compiled codebase) Startup Experience


Критические этапы рендеринга - Web Performance

Критические этапы рендеринга
============================

Критические этапы рендеринга (Critical Rendering Path) - это последовательность шагов, которые выполняет браузер, когда преобразуется HTML, CSS и JavaScript в пиксели, которые вы видите на экране. Оптимизация этих шагов улучшает производительность рендера. Эти этапы включают в себя работу с Document Object Model(DOM), CSS Object Model(CSSOM), деревом рендера (render tree) и компоновкой объектов (layout)

Объектная модель документа DOM создаётся в тот момент, когда браузер парсит HTML. Этот HTML может запрашивать JavaScript, который может модифицировать DOM. HTML может запросить стили, которые участвуют в создании CSS Object Model. Движок браузера комбинирует эти две объектные модели, чтобы создать дерево рендера (render tree). Компоновка (layout) определяет размеры и позицию каждого элемента на странице. Как только компоновка определена - пиксели отрисовываются на экране.

Оптимизация критических этапов рендеринга улучшает время до первого рендера. Понимание и оптимизация этих этапов чрезвычайно важны для того, чтобы рендерить приложение с нужной частотой кадров (60 кадров в секунду, fps) и предоставить пользователю удобный, плавный интерфейс.

Понимание этапов (CRP)
----------------------

Производительность Web-приложений включает в себя запросы к серверу, получение ответов, загрузку, парсинг и выполнение скриптов, рендеринг, компоновку и отрисовку пикселей.

Загрузка веб-страницы или приложения начинается с запроса HTML. Сервер возвращает HTTP-ответ, состоящий из заголовков (headers) и тела запроса. Именно в теле запроса содержится HTML-документ. Браузер начинает парсить загружаемый HTML, преобразуя полученные байты документа в DOM-дерево. Браузер создаёт новый запрос каждый раз, когда он находит ссылки на внешние ресурсы, будь то файлы стилей, скриптов или ссылки на изображения. Некоторые запросы являются блокирующими. Это означает, что пока такие запросы выполняются - другие запросы приостанавливаются. Браузер продолжает парсить HTML и создавать DOM до тех пор, пока запрос на получение HTML не подходит к концу. После завершения парсинга DOM, браузер конструирует CSS модель. Как только эти модели сформированы, браузер строит дерево рендера (render tree), в котором вычисляет стили для каждого видимого элемента страницы. После формирования дерева происходит компоновка (layout), которая определяет положение и размеры элементов этого дерева. Как только этап завершён - страница рендерится. Или "отрисовывается" (paint) на экране.

### Document Object Model

Построение DOM инкрементально. Ответ в виде HTML превращается в токены, которые превращаются в узлы (nodes), которые формируют DOM дерево. Простейший узел начинается с startTag-токена и заканчивается токеном endTag. Узлы содержат всю необходимую информацию об HTML-элементе, соответствующем этому узлу. Узлы (nodes) связаны с Render Tree с помощью иерархии токенов: если какой-то набор startTag и endTag-токенов появляется между уже существующим набором токенов, мы получаем узел (node) внутри узла (node), то есть получаем иерархию дерева DOM.

Чем больше количество узлов (node) имеет приложение, тем дольше происходит формирование DOM tree, а значит дольше происходит обработка критических этапов рендеринга. Измеряйте! Несколько лишних узлов (node) не сделают погоды, но divitis обязательно приведёт к подвисаниям.

### CSS Object Model

DOM несёт в себе всё содержимое страницы. CSSOM содержит все стили страницы, то есть данные о том, как стилизовать DOM. CSSOM похож на DOM, но всё же отличается. Если формирование DOM инкрементально, CSSOM - нет. CSS блокирует рендер: браузер блокирует рендеринг страницы до тех пор, пока не получит и не обработает все CSS-правила. CSS блокирует рендеринг, потому что правила могут быть перезаписаны, а значит, необходимо дождаться построения CSSOM, чтобы убедиться в отсутствии дополнительных переопределений.

У CSS имеются свои правила валидации токенов. Помните, что C в CSS означает "Cascade". CSS-правила ниспадают каскадом. Иными словами, когда парсер преобразует токены в узлы (nodes), вложенные узлы наследуют стили от родительских. Инкрементальная обработка недоступна для CSS, потому что набор следующих правил может перезаписать предыдущие. Объектная модель CSS (CSSOM) строится по мере парсинга CSS, но она не может быть использована для построения дерева рендера (render tree), потому что может оказаться так, что следующий набор правил может сделать какой-либо из узлов дерева невидимым на экране. Это может привести к лишнему вызову компоновки и перерасчёта стилей.

Говоря о производительности селекторов (selector), наименее специфичные селекторы срабатывают быстрее. Например, `.foo {}` сработает быстрее `.bar .foo {}`. В первом случае, условно, понадобится одна операция, чтобы найти элемент `.foo`, во втором случае, сначала будут найдены все `.foo`, а **потом** браузер пройдёт вверх по дереву в поисках родительского элемента `.bar`. Более специфичные селекторы требуют от браузера большего количества работы, но эти проблемы, вероятно, не стоят их оптимизации.

Если вы измерите время, требуемое на парсинг CSS, вы будете удивлены тем, как быстро работают браузеры. Более специфичные правила более затратны, потому что требуют обхода большего числа узлов в DOM дереве, но эта дороговизна обходится довольно дёшево, особенно в сравнении с другими узкими местами производительности. **Сначала измеряйте. Потом оптимизируйте, если это действительно необходимо.** Вероятно, специфичность селекторов не то, что действительно затормаживает ваше приложение. Когда дело доходит до оптимизации CSS, улучшение производительность селекторов ускоряет рендеринг лишь на микросекунды. Существуют другие пути оптимизации CSS (en-US), такие как унификация, разделение CSS-файлов на разные файлы на основе медиавыражений.

### Дерево рендера (Render Tree)

Дерево рендера охватывает сразу и содержимое страницы, и стили: это место, где DOM и CSSOM деревья комбинируются в одно дерево. Для построения дерева рендера браузер проверяет каждый узел (node) DOM, начиная от корневого (root) и определяет, какие CSS-правила нужно присоединить к этому узлу.

Дерево рендера охватывает только видимое содержимое. Например, секция head (в основном) не содержит никакой видимой информации, а потому может не включаться в дерево. Кроме того, если у какого-то узла стоит свойство `display: none`, оно так же не включается в дерево (как и потомки этого узла).

### Компоновка (Layout)

В тот момент, когда дерево рендера (render tree) построено, становится возможным этап компоновки (layout). Компоновка зависит от размеров экрана. Этот этап определяет, где и как на странице будут спозиционированы элементы и каковы связи между элементами.

Что насчёт ширины элемента? Блочные элементы по определению имеют ширину в 100% от ширины их родителя. Элемент с шириной в 50% будет иметь ширину в два раза меньше родительской. Если не указано иного, элемент body имеет ширину 100%, то есть 100% ширины родителя - видимой области viewport (окна документа).

Мета тэг viewport, который вы можете указать в Head страницы, определяет ширину видимой области и влияет на компоновку. Без этого тэга браузеры используют ширину "по умолчанию", которая обычно составляет 960px. В браузерах, открывающихся по умолчанию в полноэкранном режиме, например, в браузере телефона, установка тега `<meta name="viewport" content="width=device-width">` установит ширину видимой области в 100% от ширины экрана устройства, вместо того, чтобы использовать ширину по умолчанию. Эта ширина (`device-width)` изменяется каждый раз, когда пользователь поворачивает телефон. Это приводит к запуску этапа компоновки. Равно как и при изменении размеров окна в обычном браузере.

На производительность компоновки (layout) непосредственно влияет DOM - чем больше узлов (nodes) в вашем документе, тем больше времени понадобится на перерасчёт позиций и размеров всех элементов. Компоновка может стать узким местом, ведущим к зависаниям, особенно если выполняется одновременно со скроллом или другой анимацией. И хотя задержка 20мс при загрузке или переориентации экрана может быть приемлемой, это всё равно может привести к подвисаниям при анимации и скролле. Каждый раз, когда дерево рендера (render tree) модифицируется, например, из-за добавления узла (node), его модификации или при изменении стилей box-модели, запускается компоновка.

Для уменьшения частоты и продолжительности этого этапа, группируйте обновления экрана и избегайте анимации свойств, связанных с box-моделью элементов.

### Отрисовка (Paint)

Последний этап в нашем списке - отрисовка (paint) пикселей на экране. Когда дерево рендера (render tree) создано, компоновка (layout) произошла, пиксели могут быть отрисованы. При первичной загрузке документа (onload) весь экран будет отрисован. После этого будут перерисовываться только необходимые к обновлению части экрана, так как браузер старается оптимизировать процесс отрисовки, избегая ненужной работы. Так, если у вас в документе есть два элемента, перерисовываться будет только тот, который вы изменили. Время отрисовки зависит от того, какой тип обновления применился к дереву рендера (render tree). И хотя отрисовка - это очень быстрый процесс, и он, вероятно, слабо влияет на производительность, очень важно помнить, что оба этапа - компоновка (layout) и отрисовка (paint) должны работать сообща и укладываться в частоту обновления кадров. Каждое CSS-свойство, применяемое к любому узлу (node) увеличивает время отрисовки, но полное удаление стиля, способное сэкономить вам 0.001мс, вряд ли даст вам желаемый результат, но зато с лёгкостью ухудшит пользовательский опыт. **Помните - сначала нужно измерять, а потом оптимизировать только необходимое!**

Оптимизация CRP
---------------

Улучшайте загрузку страницы с помощью приоритизации ресурсов, с помощью контролирования порядка, в котором они грузятся и с помощью уменьшения размеров файлов. Главные подсказки здесь:

1. Уменьшайте количество критических ресурсов, откладывая их загрузку, помечая их как async и/или группируя их;
2. Оптимизируйте количество необходимых запросов, а так же размеры файлов;
3. Оптимизируйте порядок так, чтобы критические ресурсы загружались в первую очередь, сокращая таким образом длину критических этапов рендеринга.


Время загрузки страницы и ресурсов - Web Performance

Время загрузки страницы и ресурсов
==================================

**Тайминги навигации (Navigation timings)** - это показатели, указывающие временные метки, в которые произошли события навигации. **Тайминги ресурсов (Resource timings)** - это детальные показатели по времени загрузки ресурсов.

В этой статье мы рассмотрим как Performance Timing API (en-US), так и Performance Entry API. И хотя первый API считается устаревшим, он все ещё поддерживается всеми браузерами, он прост и о нем полезно знать. В свою очередь, Performance Entry API является более продвинутым инструментом, который позволяет не только получить более сложные данные, но и позволяет разработчику измерять другие показатели, в дополнение к данным о навигации и загрузке ресурсов.

**Performance Timing** API
--------------------------

PerformanceTiming API (en-US) - это JavaScript API для измерения времени загрузки страницы. Этот API считается устаревшим, но поддерживается во всех браузерах. На текущий момент рекомендуется использовать performanceNavigationTiming (en-US) API.

PerformanceTiming API (en-US) предоставляет собой read only данные в виде объекта, где значениями полей являются числа, указывающие на количество миллисекунд, которые прошли к моменту срабатывания того или иного события. Как показано на изображении ниже, процесс навигации можно разбить на следующие этапы: `navigationStart` (en-US), `unloadEventStart` (en-US), `unloadEventEnd` (en-US), `redirectStart` (en-US), `redirectEnd` (en-US), `fetchStart` (en-US), `domainLookupStart` (en-US), `domainLookupEnd` (en-US), `connectStart` (en-US), `connectEnd` (en-US), `secureConnectionStart` (en-US), `requestStart` (en-US), `responseStart` (en-US), `responseEnd` (en-US), `domLoading` (en-US), `domInteractive` (en-US), `domContentLoadedEventStart` (en-US), `domContentLoadedEventEnd` (en-US), `domComplete` (en-US), `loadEventStart` (en-US), и `loadEventEnd` (en-US).

![Navigation Timing event metrics](/ru/docs/Web/Performance/Navigation_and_resource_timings/screen_shot_2019-05-03_at_1.06.27_pm.png)

Благодаря этим метрикам и небольшим вычислениям мы можем определить важные показатели, например время до первого байта (time to first byte), скорость загрузки страницы, поиска записи dns и даже узнать, является ли соединение безопасным.

Чтобы получить доступ к этим данным, обратитесь к следующему объекту:
```

let time = window.performance.timing
```

Мы можем использовать эти данные, чтобы понять, как быстро работает приложение:

![entering window.performance.timing in the console lists all the timings in the PerformanceNavigationTiming interface](/ru/docs/Web/Performance/Navigation_and_resource_timings/navigatortiming.png)

Описание показателей:

| Показатель | Пояснение |
| --- | --- |
| `navigationStart` (en-US) | Момент, когда предыдущий документ в том же контексте (табе) запускает событие unload. Если предыдущего документа не было, значение этого показателя будет таким же, как и `PerformanceTiming.fetchStart`. |
| `secureConnectionStart` (en-US) | Началась установка (handshake) безопасного соединения. Если безопасного соединения нет, то значение равно `0`. |
| `redirectStart` (en-US) | Начало первого HTTP редиректа. Если никаких редиректов не было, или один из редиректов перевёл документ на другой origin, то значение равно `0`. |
| `redirectEnd` (en-US) | Последний HTTP редирект завершён, то есть последний байт HTTP-ответа был получен. Если никаких редиректов не было, или один из редиректов перевёл документ на другой origin, то значение равно `0`. |
| `connectStart` (en-US) | Запрос на открытие соединения отправлен в сеть. Если транспортный уровень модели OSI сообщает об ошибке и установка соединения запускаются заново, то возвращается время старта последней попытки соединения. Если используется постоянное соединение, то значение показателя будет таким же, как и `PerformanceTiming.fetchStart`. |
| `connectEnd` (en-US) | Момент, когда соединение открыто для передачи данных. Если транспортный уровень модели OSI сообщает об ошибке и установка соединения запускаются заново, то возвращается время завершения последней попытки соединения. Если используется постоянное соединение, то значение показателя будет таким же, как и `PerformanceTiming.fetchStart`. Соединение считается открытым, когда завершены все этапы установление безопасного соединения, например TLS Handshake или SOCKS Authentication. |
| `domainLookupEnd` (en-US) | Поиск домена завершён. Если используется постоянное соединение, или используются данные, сохранённые в локальном кеше, то значение показателя будет таким же, как и `PerformanceTiming.fetchStart`. |
| `domainLookupStart` (en-US) | Начался поиск домена. Если используется постоянное соединение, или используются данные, сохранённые в локальном кеше, то значение показателя будет таким же, как и `PerformanceTiming.fetchStart`. |
| `fetchStart` (en-US) | Браузер готов к загрузке документа с помощью HTTP-запроса. Этот этап всегда **срабатывает до проверки** кеша приложения. |
| `requestStart` (en-US) | Браузер посылает запрос на получение документа с сервера или из кеша. Если транспортный уровень сообщает об ошибке отправки запроса, а соединение переоткрывается - этот показатель будет перезаписан данными нового запроса. |
| `responseStart` (en-US) | Браузер получает первый байт ответа от сервера, кеша или локального ресурса. |
| `responseEnd` (en-US) | Браузер получает последний байт ответа от сервера, кеша или локального ресурса. Если соединение закрывается раньше получения последнего байта - значение параметра указывает на момент закрытия соединения. |
| `domLoading` (en-US) | Парсер HTML начинает работу. В этот момент `Document.readyState` изменяется на `'loading'` и срабатывает событие `readystatechange`. |
| `unloadEventStart` (en-US) | Срабатывает событие `unload`>, что говорит о времени, когда предыдущий документ начал выгружаться. Если предыдущего документа не было или переход к текущей странице подразумевал изменение origin (в т.ч. из-за редиректов), значение параметра равно `0`. |
| `unloadEventEnd` (en-US) | Обработчик события `unload` (en-US) завершил свою работу. Если предыдущего документа не было или переход к текущей странице подразумевал изменение origin (в т.ч. из-за редиректов), значение параметра равно `0`. |
| `domInteractive` (en-US) | HTML парсер завершил работу над основным документом. В этот момент `Document.readyState` изменяется на `'interactive'` и срабатывает событие `readystatechange` |
| `domContentLoadedEventStart` (en-US) | Момент сразу перед тем, как парсер запускает событие `DOMContentLoaded` (en-US). Это событие запускается после того, как все скрипты, которые должны исполниться сразу после парсинга, выполнены. |
| `domContentLoadedEventEnd` (en-US) | Момент сразу после исполнения всех скриптов, которые должны были исполниться. |
| `domComplete` (en-US) | Парсер HTML завершил работу над основным документом. В этот момент `Document.readyState` изменяется на `'complete'` и срабатывает событие `readystatechange`. |
| `loadEventStart` (en-US) | Событие `load` было отправлено текущему документу. Если это событие на момент измерения не было отправлено документу, значение параметра равно `0.` |
| `loadEventEnd` (en-US) | Обработка события `load` завершена, то есть загрузка завершена. Если это событие ещё не произошло или не было послано документу, значение параметра равно `0`. |

### Вычисление таймингов

Мы можем использовать все эти значения, чтобы вычислить, сколько времени потребовалось на тот или иной этап:

```js
let
    dns  = time.domainLookupEnd - time.domainLookupStart,
    tcp  = time.connectEnd - time.connectStart,
    ssl != time.secureConnectionStart,
```

### Время до первого байта

Время до первого байта (Time to First Byte) - это время между `navigationStart` и `responseStart` (момент, когда получен первый байт от сервера / кеша). Доступно в `performanceTiming` API

```js
let ttfb = time.responseStart - time.navigationStart;
```

### Время загрузки страницы

Время загрузки страницы (Page load time) (en-US) - это время между `navigationStart` и моментом, когда событие `load` отправлено текущему документу. Доступно только в `performanceTiming` API

```js
let pageloadtime = time.loadEventStart - time.navigationStart;
```

### Время поиска записи DNS

Время поиска записи DNS (DNS lookup) - это время между `domainLookupStart` (en-US) и `domainLookupEnd` (en-US). Оба эти параметра доступны как в `performanceTiming`, так и в `performanceNavigationTiming`.

```js
let dns = time.domainLookupEnd - time.domainLookupStart;
```

### TCP

Время установки соединения TCP - это время между началом и окончанием попытки соединения:

```js
tcp = time.connectEnd - time.connectStart;
```

### Установка безопасного подключения (SSL negotiation)

`secureConnectionStart` (en-US) будет равен `undefined`, если SSL не доступен, `0` если https не используется или если временная метка доступна и используется. Другими словами, если безопасное соединение было использовано, то значение `secureConnectionStart` будет правдиво (truthy), а время между `secureConnectionStart` и `requestStart` будет больше 0.

```js
ssl = time.requestStart - time.secureConnectionStart;
```

Performance Entry API
---------------------

Основные показатели производительности, рассмотренные выше, считаются устаревшими, но полностью поддерживаются современными браузерами. Взамен предлагается использовать `Performance Entry API` (en-US), который предоставляет инструмент для пометок и измерений времени одновременно с событиями navigation и загрузкой resource. Вы также можете создавать свои маркеры:

```js
performance.getEntriesByType("navigation").forEach((navigation) => {
  console.dir(navigation);
});

performance.getEntriesByType("resource").forEach((resource) => {
  console.dir(resource);
});

performance.getEntriesByType("mark").forEach((mark) => {
  console.dir(mark);
});

performance.getEntriesByType("measure").forEach((measure) => {
  console.dir(measure);
});

performance.getEntriesByType("paint").forEach((paint) => {
  console.dir(paint);
});

performance.getEntriesByType("frame").forEach((frame) => {
  console.dir(frame);
});
```

В некоторых браузерах вы можете использовать `performance.getEntriesByType('paint')`, чтобы запросить измерения для `first-paint` и `first-contentful-paint`. Мы используем `performance.getEntriesByType('navigation')` и `performance.getEntriesByType('resource')` для запроса данных по навигации и загрузки ресурсов, соответственно.

Navigation
----------

Когда пользователь запрашивает веб-приложение, браузер должен получить некоторые мета-данные (en-US), чтобы начать загрузку. Для этого пользовательский агент проходит серию шагов, такие как поиск записи DNS (DNS lookup), TCP рукопожатие TCP handshake (en-US), и установку безопасного соединения (SSL negotiation). Как только браузер установил соединение, происходит первый полезный запрос данных на сервера. Как только начинают поступать данные от сервера, браузер начинает парсить полученные данные, строит DOM, CSSOM, создаёт деревья рендера (render trees), чтобы в конце концов отрендерить страницу. В тот момент, когда браузер перестаёт парсить входящие данные, документ переходит в интерактивную стадию. Если в документе существуют отложенные к загрузке ресурсы (deferred scripts), которые должны быть обработаны, браузер парсит их. После этого запускается событие DOMContentLoaded, после которого готовность страницы завершена. Теперь документ может обрабатывать пост-загрузочные задачи. После этого документ маркируется, как полностью загруженный.
```

let navigationTimings = performance.getEntriesByType('navigation');
```

Метод `performance.getEntriesByType('navigation')` возвращает массив PerformanceEntry (en-US), в котором содержатся объекты Navigation Timing.

![The results of when performance.getEntriesByType('navigation'); is entered into the console for this document](/ru/docs/Web/Performance/Navigation_and_resource_timings/perfgentrybytypenavigation.png)

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

let timing = performance.getEntriesByType('navigation')[0];
```

### Протокол

Мы можем проверить протокол, который используется дл получения ресурсов:
```

let protocol = timing.nextHopProtocol
```

В текущем случае в ответ будет `h2` для `http/2`.

### Сжатие

Чтобы узнать, как эффективно сжимаются данные при передаче, мы можем разделить `transferSize` на `decodedBodySize`, а затем вычесть результат из 100%. Для текущей страницы сжатие составляет до 74%.
```

let compressionSavings = 1 - (timing.transferSize / timing.decodedBodySize)
```

Мы могли бы использовать
```

let compressionSavings = 1 - (timing.encodedBodySize / timing.decodedBodySize)
```

но `transfersize` так же включает в себя байты заголовков.

Для сравнение, мы можем посмотреть на вкладку Network, где увидим, что было передано 22.04KB для файла, который в разархивированном виде занимает 87.24KB.

![View of the bytes transferred and the size via the network tab](/ru/docs/Web/Performance/Navigation_and_resource_timings/bytesdownloaded.png)

Если мы проверим вычисления, то результат получится схожим: `1 - (22.04 / 87.24) = 0.747`. Тайминги навигации позволяют нам получить такие данные программно.

Обратите внимание, что это данные для одного единственно документа, а не для всех ресурсов вместе взятых. В то же время, длительность загрузки, события-обработчики и тайминги построения DOM / CSSOM влияют на продолжительность загрузки всего приложения, не только одного конкретного ресурса. Клиентские приложения, выполняющиеся в браузере, могут выглядеть быстрее, если данные объёмом 300КБ вы передаёте сжатыми до 100КБ, но это все не значит, что JavaScript, CSS или другие медиа-ресурсы не раздувают приложение и не делают его медленнее. Проверка уровня сжатия - это очень важно, но не менее важно проверять длительность парсинга ресурсов и время между тем, как завершён DOMContentLoaded и DOM готов к работе. Может случиться так, что время парсинга скриптов и обработка скриптами результатов в основном потоке (main thread) приведёт к зависанию интерфейса.

### Время запроса

API не предоставляет все измерения, которые разработчик хочет получить. Например, как долго продлилось выполнение запроса? Отдельного поля в объекте данных нет. Однако, мы можем использовать измерения, чтобы вычислить то, что нам нужно.

Чтобы определить время ответа, вычтите время старта запроса из времени старта получения ответа. Запрос стартует ровно в тот момент, когда клиент запрашивает ресурс с сервера (или из кеша). Ответ начинается ровно в тот момент, когда клиент получает первый байт.

```js
request = timing.responseStart - timing.requestStart;
```

### Длительность события загрузки

```js
load = timing.loadEventEnd - timing.loadEventStart;
```

### DOMContentLoaded event

Длительность события DOMContentLoaded определяется разностью моментов, когда клиент запускает событие DOMContentLoaded и когда это событие завершено. Старайтесь держать эту величину меньше 50ms - тогда ваш интерфейс будет отзывчивым.

```js
DOMContentLoaded =
  timing.domContentLoadedEventEnd - timing.domContentLoadedEventStart;
```

### Длительность (Duration)

В объекте данных есть поле Длительность (`Duration`). Длительность - это разница между PerformanceNavigationTiming.loadEventEnd (en-US) и PerformanceEntry.startTime (en-US) properties.

Интерфейс PerformanceNavigationTiming, кроме того, даёт информацию о том, какой тип навигации вы измеряете, возвращая `navigate`, `reload`, `back_forward` или `prerender`.

Resource
--------

В то время, как тайминги навигации измеряют производительность загрузки и парсинга основного файла HTML, этот файл служит лишь точкой входа для загрузки других ресурсов. Поэтому нам так же важно знать, как быстро загружаются дополнительные ресурсы. Для измерения этих данных нужно использовать Resource Timing. Большая часть измерений в этом объекте похожи: здесь и поиск домена в DNS, и TCP установка соединения и т.д.

![Graphic of Resource Timing timestamps](/ru/docs/Web/Performance/Navigation_and_resource_timings/resourcetiming-timestamps.jpg)

Для того, чтобы получить эти данные, выполните команду:
```

performance.getEntriesByType("resource")
```

Смотрите также
--------------

* `PerformanceNavigationTiming` (en-US)
* `PerformanceResourceTiming` (en-US),
* `PerformanceMark` (en-US)
* `PerformanceMeasure` (en-US)
* `PerformancePaintTiming` (en-US)


Тайминги производительности: как долго - долго? - Web Performance

Тайминги производительности: как долго - долго?
===============================================

Не существует чётко установленного набора правил, который определяет медленную скорость загрузки страниц, но существуют особые руководства, которые рекомендуют определённые тайминги: загрузка контента - 1 секунда, ожидание - 50мс, анимация - 16,7мс, ответ на действия пользователя - от 50 до 200мс.

### Загрузка контента

Понятие "до секунды" часто считается оптимальным для загрузки. Но что это означает? Правило секунды должно рассматриваться как правило максимального времени, за которое пользователь поймёт, что запрос был отправлен и будет загружен. Например, когда браузер принимает заголовок страницы или фоновый цвет и показывает её пользователю.

Как правило, первый ресурс, который получает клиент - это HTML документ, который затем делает запрос на загрузку остальных ресурсов. Как было подмечено в статье о критическом пути рендеринга - как только браузер получает данные, он сразу начинает их обработку, вместо того, чтобы дожидаться загрузки всех ресурсов.

Да, одна секунда на загрузку — это хорошая цель. Но лишь немногие приложения достигают этой скорости. Всё зависит от ожиданий. Например, от приложения «Hello World», работающего в корпоративной (локальной) сети, будет ожидаться загрузка за миллисекунды. Пользователь из северной Сибири, пользующийся EDGE-мобильной сетью и устройством 5-летней давности, вероятно, посчитает даже 20-секундную загрузку быстрой. Однако, в среднем, если вы позволяете приложению не отвечать на запрос 3 или 4 секунды, вы, вероятно, потеряете пользователя. И, что ещё хуже, этот пользователь вряд ли вернётся к вам в ближайшее время.

В деле оптимизации производительности рекомендуется обозначить амбициозные задачи по первичной загрузке контента. Например, 5 секунд для 3G сетей и 1.5 секунды для офисного Т1 канала. Для навигации внутри приложения цели должны быть ещё строже. К счастью, для оптимизации можно использовать Service Workers и кеширование.

### Ожидание

Браузеры однопоточны (хотя существуют фоновые потоки, доступные с помощью web worker-ов). Это означает, что взаимодействие с пользователем, отрисовка и выполнение скриптов выполняются в одном потоке. Если поток занят вычислением сложной JavaScript логики, этот поток не сможет обрабатывать пользовательский ввод (например, нажатие кнопок). По этой причине важно разбивать выполнение скриптов на небольшие части, каждый из которых выполняется не больше 50мс. Это позволит потоку своевременно реагировать на пользовательский ввод.

### Анимация

Для того, чтобы прокрутка и другие анимации выглядели плавно, контент страницы должен обновляться с частотой 60 кадров в секунду (60 fps), то есть один кадр раз в 16.7мс. В эти 16.7мс. должны входить выполнение скриптов, компоновка и отрисовка. Делайте приложение таким, чтобы приложение могло использовать 6мс на отрисовку контента, а в остальные 10мс могло заниматься оставшимися вычислениями. Любая другая частота кадров, особенно если она отрывистая и непостоянная, создаёт впечатление зависающего приложения.

### Отзывчивость

Когда пользователь взаимодействует с контентом - очень важно давать обратную связь пользователю. Время реакции не должно составлять больше 100мс, а лучше - 50мс. 50мс ощущаются пользователем как "мгновенно". Реакция на пользовательское взаимодействие должно чаще чувствоваться мгновенным (например, наведение курсора на кнопку). Но это не означает, что реакция должна быть одномоментной. В то время, как задержка в 100мс может вызвать чувство бессвязности действий пользователя и реакции системы, плавный переход между состояниями за время в 100-200мс позволит пользователю заметить, что взаимодействие началось и обрабатывается. Если ответ на действие пользователя занимает больше 100мс, предоставьте некоторую реакцию, которая скажет пользователю, что взаимодействие уже случилось и обрабатывается.


Время ожидания (latency) - Web Performance

Время ожидания (latency)
========================

**Latency** (дальше - **время ожидания**) - это время, которое требуется на доставку пакета данных от источника к пункту назначения. В деле оптимизации производительности важно сокращать проблемы, которые приводят к длительному времени ожидания. Также необходимо научиться тестировать работу приложения в условиях плохого соединения. В этой статье мы рассмотрим само понятие времени ожидание, его причины, способы детекции и исправления.

Что такое "время ожидания"?
---------------------------

Время ожидания - это промежуток времени, который проходит с момента старта запроса до момента получения ответа. При запросе первых 14КБ время ожидания дольше, потому что весь запрос включает в себя DNS запрос, соединение TCP handshake (en-US), установление безопасного соединения TLS. Последующие запросы будут иметь меньшее время ожидания, так как соединение с сервером уже установлено.

Время ожидания описывает величину задержки сети или, в частности, интернет-соединения. Малое время ожидания подразумевает едва заметные задержки. Большое время ожидания подразумевает большие задержки. Одно из основных усилий в оптимизации приложения должно быть направлено на снижение этого времени ожидания.

Улучшить время ожидания, связанное только с одним ресурсом, например, с HTML кодом страницы - это довольно просто. Но приложения, как правило, состоят из множества запросов: CSS, JavaScript-файлы, медиа. Чем больше запросов исходит со страницы, тем большее влияние оказывает время ожидания на общие впечатления пользователя.

При соединении с малым временем ожидания ресурсы появляются практически мгновенно. При соединении с большим временем ожидания задержка между запросом и ответом становится заметной. Мы можем определить время ожидания, измеряя скорость, с которой данные проходят из одной сети к другой.

Время ожидания может быть измерено для одного направления (например, сколько времени проходит до отправки запроса) или для полного пути обмена сообщениями (сколько времени заняли запрос и получение ответов).

Эмуляция времени ожидания
-------------------------

Для эмуляции большой времени задержки вы можете использовать средства разработчика. В англоязычных интерфейсах эмуляция называется "Throttling".

![Emulate latency by emulating throttling](/ru/docs/Web/Performance/Understanding_latency/emulate_latency.png)

В Firefox кнопка переключения находится на панели Network (Сеть), в заголовке таблицы. С помощью инструмента вы можете испытать работу приложения в сетях от GPRS до высокоскоростных. Также вы можете сами указать скорость загрузки и время ожидания. Браузеры предоставляют разные наборы значений, но, как правило, все они покрывают перечисленные ниже случаи:

| Selection | Download speed | Upload speed | Minimum latency (ms) |
| --- | --- | --- | --- |
| GPRS | 50 Kbps | 20 Kbps | 500 |
| Regular 2G | 250 Kbps | 50 Kbps | 300 |
| Good 2G | 450 Kbps | 150 Kbps | 150 |
| Regular 3G | 750 Kbps | 250 Kbps | 100 |
| Good 3G | 1.5 Mbps | 750 Kbps | 40 |
| Regular 4G/LTE | 4 Mbps | 3 Mbps | 20 |
| DSL | 2 Mbps | 1 Mbps | 5 |
| Wi-Fi | 30 Mbps | 15 Mbps | 2 |

Временные показатели сети
-------------------------

Кроме того, во вкладке Network (Сеть), вы можете изучить, как много времени занял тот или иной запрос. На изображении ниже - пример загрузка SVG файла размером 267.5КБ.

![The time it took for a large SVG asset to load.](/ru/docs/Web/Performance/Understanding_latency/latencymlw.png)

Когда запрос находится в очереди, ожидая установки соединения с сервером, он считается заблокированным. Блокировка случается, когда браузер уже обрабатывает слишком много соединений одновременно (в большей степени это относится к предыдущему поколению HTTP1.1, который позволял поддерживать до 6 соединений за раз). Пока все соединения обрабатываются, браузер не может загружать другие ресурсы. Как только загрузка ресурса завершается - соединение освобождается.

**DNS поиск (DNS resolution)** - это время, которое требуется на DNS lookup. Это процесс, когда браузер находит соответствие между именем домена и его IP-адресом. Если вы обращаетесь ко многим доменам, DNS поиск занимает больше времени.

**Установка соединения (Connecting)** - это время, требуемое на TCP handshake (en-US). Это процесс SYN - ACK, когда браузер и сервер договариваются о формате соединения. Как и DNS, чем больше соединений к разным серверам вы используете, тем больше время создания соединений.

**TLS handshake** - это процесс установки безопасного соединения. И хотя установка безопасного соединения занимает дополнительное время, ни в коем случае не стоит отказываться от него, жертвуя безопасностью пользователей.

**Отправка** **(Sending)** - время, необходимое для отправки HTTP запроса на сервер.

**Ожидание (Waiting**) - это ожидание обработки запроса сервером. Например, вы хотите получить изображение. В этом случае сервер должен обратиться к файловой системе, найти файл, передать его HTTP соединению. В случае сложных динамических запросов процесс может занимать секунды и минуты. Это ещё одно место для оптимизации, но уже не клиентское, а серверное.

**Получение (Receiving**) - это время на загрузку ответа. Этот показатель - комбинация скорости сети и размера файла. Если изображение было закешировано, то его загрузка может произойти мгновенно. Но если не использовать кеш и включить эмуляцию медленной сети, изображение может загружаться 43 секунды!

Измерение времени ожидания
--------------------------

**Время ожидания сети (Network latency**) - это время, которое требуется на отправку запроса и полную загрузку ответа.

**Время ожидания диска (Disk latency**) - это время, которое требуется устройству на то, чтобы получить запрос, понять его, найти файл в файловой системе и вернуть ответ.


Бюджет производительности - Web Performance

Бюджет производительности
=========================

Бюджет производительности - это лимит для предотвращения регрессий. Этот бюджет может быть применён к файлам, типам файлов, всем ресурсам приложения, определённым общим показателям (например, Время до интерактивности) пользовательским показателям (например, Время до главного элемента) или к пороговым значениям к определённым точкам во времени.

Зачем нужен бюджет?
-------------------

Бюджет существует для отражения желаемых вами целей. С бюджетом вы сможете осознанно выстраивать компромиссы между пользовательским опытом и объективным индикаторами эффективности приложения (например, конверсией)

Эти цели можно разбить на категории:

* Временные (например, Время до интерактивности, Первая отрисовка контента).
* Количественный (например, размер загруженных JS файлов, количество изображений).
* Определённые правилами (например. индекс Pagespeed, баллы Lighthouse).

Главная цель такого подхода - сокращение регрессии. Но этот подход может помочь предсказать поведение приложения в будущем. Например, в Сентябре 50% месячного бюджета было использовано за неделю - значит, нужно ждать увеличения потребления контента, нагрузки на сервера и т.д.

Кроме того, подход может раскрыть некоторые нужды разработчиков (например, может оказаться, что в финальном коде вашего приложения половину объёма занимает огромная библиотека, из которой вы используете только мизерную часть функциональности).

Как определить бюджет?
----------------------

Бюджет должен включать 2 уровня:

* Предупреждение
* Ошибка

Уровень предупреждения позволяет вам быть проактивным и заниматься техническим долгом, не блокируя разработку нового функциональности

Уровень ошибки - это граница, по достижении которой приложение воспринимается негативно.

Для начала, вам нужно выяснить, какими технологиями пользуются ваши пользователи. Например, большая часть пользователей приходят с Anroid-телефонов бюджетного уровня и подсоединяются через 3G). Для подобного исследования существует множество инструментов (en-US). Эти метрики должны стать базой для бюджета размера файлов.

Базовая цель - достигнуть показателя "Время до интерактивности" до 5 секунд при 3G/4G, и до 2 секунд для последующих загрузок. Однако, вы можете придумать свои цели, основанные на контенте приложения, географии пользователей и технологиях.

Например, для приложения с большим количеством текста (блоги, новостные сайты), показатель Первая отрисовка контента (First Contentful Paint) будет гораздо лучше показывать, с чем сталкивается пользователь. Иными словами, этот показатель покажет, как быстро пользователь начинает читать. И этот показатель должен быть включён в специфичные бюджеты, например, бюджет шрифтов, где вы можете применять разные техники для оптимизации. Например, font-display, чтобы улучшить Субъективно Ощущаемую производительность (en-US)).

Но самая главная цель таких бюджетов - это возможность корреляции Производительности и Бизнес-целей. Когда вы определяете какие-то показатели, вы должны сфокусироваться на пользовательском опыте. Только он может диктовать, как мы должны изменять приложение таким образом, чтобы не просто улучшить конверсию, но и предсказать вероятность того, что пользователь вернётся.

Как создать бюджет?
-------------------

Во время разработки вы можете использовать несколько инструментов, чтобы проверять некоторые показатели:

* Сборщики (например, webpack), из коробки содержат инструменты оценки производительности, которые сообщат вам, если какой-то из файлов превысил допустимые размеры.
* Bundlesize, позволяет вам определить и проверять размеры файлов каждый раз при интеграции вашего кода с помощью CI.

Проверка размеров файлов - это лишь первый рубеж защиты от регрессий. Преобразование этих показателей во временные может быть сложным, потому что во время разработки окружение разработчика может не включать в себя сторонние библиотеки или оптимизации, которые обычно присутствуют в Production сборках.

Поэтому, рекомендуется определить базовые линии для каждой метрики бюджета с учётом разницы между окружением разработчика и боевым окружением.

С этим может помочь, например, Lighthouse Bot, который можно встроить в Travis CI и использовать для получения аналитики Lighthouse и Webpage Test. Этот бот будет сообщать об ошибке или успешном прохождении тестов на основе определённых минимальных оценок.

Как я могу усилить влияние бюджета?
-----------------------------------

Чем раньше вы сможете определить новую трату к бюджету, тем лучше вы сможете оценить текущее состояние приложения и указать на необходимые оптимизации.

Кроме того, лучше иметь несколько бюджетов и быть проактивным. Бюджеты должны отражать ваши текущие цели, но не должны мешать экспериментам. Например, вы можете привнести функциональность, которая увеличит общее время загрузки приложения, но попытается увеличить пользовательскую вовлечённость (например, как долго пользователь остаётся на странице).

Бюджет помогает вам сохранить оптимальное поведение ваших текущих пользователей, когда вы пытаетесь выйти на новые рынки или привнести что-то новое в ваше приложение.

Смотрите также
--------------

* Start Performance Budgeting by Addy Osmani
* Performance Budgets 101 by Milica Mihajlija
* Performance Budgets That Stick by Tim Kadlec


Мониторинг производительности: реальные пользователи и их эмуляция - Web Performance

Мониторинг производительности: реальные пользователи и их эмуляция
==================================================================

**Синтетический мониторинг** и **мониторинг реальных пользователей (Real User Monitoring, RUM)** - два способа мониторинга и получения данных о веб-производительности. Эти два подхода дают два разных обзора производительности и каждый имеет свои преимущества, области применения и минусы. RUM, в основном, лучше подходит для понимания долгосрочных трендов, в то время как синтетический мониторинг хорошо подходит для тестирования регрессий, их быстрого обнаружения и быстрой реакции на них в процессе разработки. В этой статье мы определим и сравним оба подхода.

Синтетический мониторинг
------------------------

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

Пример такого мониторинга - WebPageTest.org. Ресурс предоставляет контролируемое окружение, где вы определяете географию, сеть, устройства, браузеры и кешированные данные. Сервис предоставляет Waterfall график для каждого ресурса, который используется в вашем приложении и грузится сторонними библиотеками (например, рекламными или аналитическими инструментами).

Контролируя переменные окружения, вы можете понять, где в производительности узкие места и каковы их источники. Однако, все эти данные не отражают реальный пользовательский опыт, особенно если поведение пользователя не ограничивается только лишь просмотром страницы.

Синтетический мониторинг может быть важным компонентом тестирования регрессий и средством мониторинга выпущенного и работающего приложения. Ухудшение базовых метрик производительности в ходе CI/CD-процесса должно вести к приостановке релиза, по крайней мере до тех пор, пока не выяснится, почему метрики ухудшились. Если проблема возникает только в Production-режиме, синтетические тесты предоставят информацию, которая поможет идентифицировать, изолировать и решить проблему, прежде чем она отразится на пользователях.

Мониторинг реальных пользователей (RUM)
---------------------------------------

**Мониторинг реальных пользователей (Real User Monitoring, RUM)** измеряет производительность приложения на устройствах конечных пользователей. В основе подхода - сторонний скрипт, который вставляет другие скрипты на каждую страницу. Эти дополнительные скрипты измеряют производительность и предоставляют отчёты о ней. Этот подход помогает не только узнать, насколько производительно приложение, но и даёт информацию об использовании приложения, например, о географии, распределении пользователей или влиянии такого распределения на пользовательский опыт.

В отличие от синтетического мониторинга, RUM собирает данные от настоящих пользователей, вне зависимости от их устройств, браузеров, сети или геолокации. Пока пользователь взаимодействует с приложением, тайминги такого взаимодействия записываются, вне зависимости от того, какое действие выполняется в конкретный момент. Такой мониторинг собирает данные о реальном использовании приложения, а не о том поведении, которое ожидают разработчики или, скажем, отдел маркетинга. Это особенно важно для больших веб-сайтов или сложных приложений, где функциональность или содержимое постоянно меняются, а количество пользователей может очень сильно расти, создавая новые нагрузки и требования.

Используя RUM, бизнес может лучше понять своих клиентов и определить зоны сайта, которые требуют большего внимания. Более того, RUM может помочь понять географию или канал распространения приложения. Знание своих пользователей и трендов поможет вам выстроить бизнес-планы и думать наперёд, позволяя вам определить приоритетные зоны, которые требует оптимизации и улучшения производительности.

Сравнение подходов
------------------

Синтетический мониторинг хорошо подходит для отлавливания регрессий в ходе разработки приложения. Особенно полезным может оказаться занижение скорости сети (network throttling (en-US)). Такой подход довольно прост, недорог и великолепно подходит для тестирования определённых точек приложения по мере того, как вы вносите изменения в код. Но он даёт лишь узкий обзор производительности и не говорит о том, что испытывает пользователь.

Тестирование на реальных пользователях, в свою очередь, даёт информацию о настоящих пользователях, которые используют приложение или веб-сайт. И хотя получение и обработка таких данных обходится дороже и не так проста, такой подход даёт жизненно важные данные о пользовательском опыте.

API для измерения производительности
------------------------------------

Существует множество сервисов мониторинга. Если вы хотите создать свой сервис, взгляните на следующие API: не только `PerformanceNavigationTiming` (en-US) и `PerformanceResourceTiming` (en-US), но также `PerformanceMark` (en-US), `PerformanceMeasure` (en-US), и `PerformancePaintTiming` (en-US).