HeadlinesBriefing favicon HeadlinesBriefing.com

Производительность главного потока браузера

Hacker News •
×

Что приходит в голову, когда вы слышите «оптимизация фронтенда»? Для большинства из нас это такие вещи, как уменьшение сетевых запросов, уменьшение бандла или хорошее использование кэша. Помимо этого, возможно, сокращение ре-рендеров или настройка того, когда загружаются ресурсы. Главный поток обычно не всплывает, и на то есть причина: на большинстве экранов он никогда не становится проблемой. Но на экранах с большим количеством взаимодействий, где данные поступают в реальном времени, а прокрутка, анимация и ввод переплетаются вместе, картина меняется. Как бы вы ни экономили на сети и размере бандла, экран замирает в тот момент, когда главный поток блокируется. Вы, вероятно, встречали веб-сайт, где прокрутка время от времени дергается, кнопка отвечает с легкой задержкой, или буквы, которые вы вводите в поле поиска, появляются с задержкой в полбита. Это не настолько плохо, чтобы раздражать, но это действует на нервы тонким образом. Такая дерганость (jank) — это и есть вид заблокированного главного потока. Когда мы, как разработчики, сталкиваемся с такой дерганостью, обычная реакция — спросить «мой код медленный?» и начать разбирать алгоритмы или искать лишние вычисления. Однако в большинстве случаев скорость кода не является проблемой. Код не медленный. Просто оказывается, что это тот код, который удерживает главный поток. У браузера есть несколько потоков, но почти всё, к чему мы можем получить доступ из кода, сосредоточено в главном потоке. Вычисления, рендеринг, обработка событий, обработка сетевых ответов и внутренности вашего фреймворка — всё это обрабатывается там. Один ресурс, гора работы. Главный поток браузера дорог. Большую часть времени он не вызывает проблем, но как только вы пытаетесь сделать что-то амбициозное, работа с главным потоком становится важной частью. Эта статья о том, как справляться с этим дорогим ресурсом. Начнем с того, что на самом деле делает главный поток. Его работа делится на две широкие категории. Первая — выполнение JavaScript. Код, который мы пишем, вместе с обработчиками событий, таймерами, колбэками сетевых ответов и внутренностями фреймворка — всё это выполняется здесь. Эти задачи выполняются в порядке их поступления в очередь, всякий раз, когда есть пауза, без связи с циклом обновления экрана. Вторая — отрисовка экрана. Когда DOM или стили меняются и экран нужно обновить, браузер проходит примерно через эти шаги, по порядку, чтобы произвести кадр. Запуск колбэков requestAnimationFrame — JavaScript, зарегистрированный для запуска непосредственно перед отрисовкой кадра Расчет стилей — вычисление итоговых CSS-значений для каждого элемента Layout (макет) — вычисление позиции и размера каждого элемента (также называется reflow) Paint (рисование) — генерация команд рисования, описывающих, что рисовать и в каких цветах Если ничего не изменилось, эти шаги пропускаются полностью, поэтому они не обязательно выполняются каждый кадр. Только финальный шаг композитинга, который берет полученный вывод и собирает его на экране, передается в поток композитора. Другими словами, большая часть первой половины конвейера, рисующего экран, — это ответственность главного потока. Конвейер рендеринга для обновления экрана Чтобы экран выглядел плавно, кадры должны отрисовываться с частотой обновления дисплея. На самом распространенном дисплее 60 Гц это означает 60 кадров в секунду, или около 16,6 миллисекунд на кадр. И вы не можете использовать всё это время. После вычитания собственных затрат браузера на обработку практический бюджет обычно считается около 10 миллисекунд, а на устройстве 120 Гц сам бюджет сокращается вдвое. Проблема в том, что описанные выше два вида работы стоят в одной очереди в одном потоке. JavaScript был спроектирован вокруг модели однопоточного цикла событий. Главный поток обрабатывает одну задачу за раз, и пока эта задача выполняется, ничего другого не может произойти. Если одна функция JavaScript выполняется 200 миллисекунд, то в течение этих 200 миллисекунд браузер не может перерисовать экран или принять клик от пользователя. На фоне бюджета кадра около 10 миллисекунд это фатальное количество времени. Задача, которая ру...