Эта статья представляет собой перевод выступления на конференции BlinkOn9.
существуетBlinkOn9Во время встречи разработчики команды Google Blink Филип Роджерс и Стефан ЗагерСовместное использование «Blink Rendering — перестройка движка во время полета», целью которого является представление основных принципов рендеринга Blink и последних улучшений команды разработчиков в области производительности прокрутки, компоновки рисунков и типографики.
Часть 1: Что такое рендеринг?
Проще говоря, рендеринг — это некоторая базовая функциональность браузера, которая анализирует ваш HTML и CSS в дерево DOM и преобразует его в пиксели на экране.
На рисунке показаноdocumentОсновные этапы жизненного цикла, четыре черных ящика посередине — конвейер рендеринга (render pipeline).
Я всегда думал, что изучение трекеров Chrome помогает понять жизненный цикл документа. Итак, вот панель Chrome Tracker для процесса рендеринга, выделенная область на изображении — это основной поток рендеринга, а небольшая часть внизу принадлежит потоку компоновщика (compositor thread). В начале рендеринга мы можем обрабатывать загрузку ресурсов, запуск JavaScript, изменение дерева DOM и т. д. с периодом простоя между ними для общих задач.
Далее произойдетVSync(вертикальная синхронизация). vsync — это когда браузер просто выводил на экран окно с полным пикселем и начинал генерировать следующее окно с пикселями. Так что для процесса рендеринга это означает, что все готовы генерировать новые пиксели.
сработала вертикальная синхронизацияBeginMainFrame, что является важным методом, которыйУправляет конвейером рендеринга.BeginMainFrameСобытия ввода, такие как прокрутка, сенсорный экран, жесты, мышь и т. д., сначала обрабатываются, а затем запускаютсяrequestAnimationFrameПерезвоните.
Следующим шагом является запуск конвейера рендеринга, как показано ниже, есть четыре шага:
-
Стиль: макетное дерево в дереве DOM, обход дерева для каждого метки узла макета, чья информация о стиле, а затем с информацией о стиле макета дерева передается на следующий этап
-
макет: мы снова пройдемся по дереву макета, аннотируя информацию о его размере и положении для узлов, до сих пор мы дважды аннотировали дерево макета, а затем передаем его на этап композиции.
-
настройка композиции: на этапе настройки композиции мы определяем, сколько слоев композиции необходимо отрисовать (
compositing layers), а также их размер, положение, порядок размещения и т. д. -
рисование: на этапе рисования берутся аннотации дерева макета и информация, записанная на этапе настройки композитинга, и создается «список отображения» необработанных команд рисования, которые инструктируют компоновщика, как рисовать пиксели.
В конце фазы отрисовки основной поток переключается на композитный поток (зеленая область в трекере ниже), разделяя работу по растеризации на несколько «тайлов» и назначая их нескольким рабочим потокам. Как только растеризация будет завершена, мы перейдем к компоновщику Chrome. Этот процесс будет продолжаться и продолжаться.
Выше приведено краткое введение в рендеринг, стоит отметить, что основной поток очень загружен, все действия происходят в основном потоке, скрипт выполняется в основном потоке, а также отвечает за рендеринг и многие другие функции, поэтомуОсновная ветка очень переполнена. После многих лет работы по оптимизации мы обнаружили, что очень эффективным методом оптимизации является разделение работы основного потока и передача ее другим потокам для обработки.
Часть 2: Важность рендеринга и текущие проблемы
Для веб-платформы рендеринг очень важен.
Во-первых, суть динамических веб-страниц состоит в том, чтобы принимать входные данные, созданные пользователем или сценарием, и превращать их в визуальные результаты.Рендеринг лежит в основе процесса, поэтому независимо от того, насколько крута ваша страница, если рендеринг пойдет не так, у пользователя не будет хорошего опыта.
Второй,Рендеринг является основным фактором, определяющим производительность веб-страницы.(воспринимаемом и фактическом), рендеринг не прерывается, и если JavaScript работает слишком долго, страница становится громоздкой, что, конечно же, привлекает внимание пользователя.
В-третьих, современные веб-страницы динамичны — содержимое постоянно модифицируется, загружается и анимируется. Чтобы не отставать от темпа и обеспечивать бесперебойное взаимодействие,Код рендеринга должен быть первоклассным гражданином.
Начнем с описания проблем, которые мы встречались в нашем коде рендеринга, и улучшения, на которые мы работаем, чтобы удовлетворить их.
Прокрутить
Как упоминалось ранее, рендеринг является основным фактором, определяющим производительность веб-страницы.Опыт прокрутки является главным приоритетом. Пользователи очень чувствительны к прокрутке. Прокрутка определяет их восприятие общей производительности страницы. Если прокрутка плохая, независимо от того, насколько крута страница, она не может ее спасти. Код, связанный с прокруткой в Blink, ловко спрятан повсюду, охватывая основной поток рендерера, поток компоновки и даже процесс браузера.
Оглядываясь назад в историю, в 1998 г.KHTMLбыл дан впервые в оригинальной версииdocumentвозможность прокрутки. Впоследствии, в 2003 г.WebKitЭлемент div также можно прокручивать, но обе прокрутки требуют повторного запуска конвейера рендеринга. Сначала код для двух прокруток писался отдельно, в этом не было ничего страшного.
Однако через несколько лет, с добавлением множества функций и оптимизаций к прокрутке, код прокрутки напрямую стал самой сложной и сложной частью Blink. Мы по-прежнему поддерживаем эти два набора скользящего кода, и все функции приходится писать дважды. Мало того, поскольку прокрутка относится к основному коду, ее неизбежно модифицировать для реализации других функций, а сложность возрастает линейно, и ее становится все труднее поддерживать.
Из-за текущего состояния кода прокрутки и того факта, что любые функциональные изменения приходится прописывать дважды, всем нам, разработчикам, приходится тяжело работать, поэтому в 2014 году Стив Кобус и Эллиотт придумали блестящую идею: прокручивать корень слой (Root Layer Scrolling) Для решения этой проблемы.
они решили отменитьdocumentПрокрутка на уровне документа, используйте толькоoverflowРеализуя все функции прокрутки, это решение в основном направлено на снижение сложности кода и повышение качества кода. В дополнение к этому есть и другие преимущества, например, поскольку два набора кода долгое время поддерживались отдельно, их поведение не согласовано. На самом деле, есть заметная разница в поведении прокрутки на уровне документа, потому что у прокрутки на уровне документа и прокрутки div есть ошибки, которые совершенно не связаны, у одной прокрутки есть ошибки, у другой может нет, это бардак.
Достижение прокрутки на корневом уровне также было долгим и трудным процессом, который занял 4 года и был реализован в версии M66.
Если вы хотите изменить макетную часть кода рендеринга в больших масштабах, первым делом нужно пройти около 45 000 тестов макета.Количество неудачных тестов на картинке выше начинается с 1500. Фактически, когда мы впервые начали модифицировать , Около 6000 тестов провалены. Эти тесты необходимо классифицировать и решать один за другим, поэтому в процессе мы также исправили множество исторических ошибок.
Как видно из наших графиков производительности, когда мы впервые начали работать, было значительное снижение производительности, примерно от 40% до 50%, и когда мы углубились в эти ошибки производительности, мы обнаружили, что это глубокая рекурсия к ЦП. код пути, поэтому нам нужно выполнить оптимизацию, связанную с процессором, и модификацию кода части Chrome chromium. Это был очень сложный процесс, и потребовалось множество различных исправлений кода, чтобы фактически вернуть нам базовую производительность.
Поэтому я также должен повторить, что с этим фрагментом кода действительно сложно работать, если мы допустим какие-либо ошибки, пользователь сразу заметит, и эти ошибки также повлияют на все страницы.
Далее давайте взглянем на улучшения, которые мы внесли в рисование и компоновку.
2. Рисование и композиция
Как и скользящий код, часть кода для рисования и компоновки довольно старая, около 16 лет, и разрабатывать новые функции в текущей структуре кода непросто. Теперь есть возможность оптимизировать производительность этой части кода, уменьшить объем памяти, упростить расширение кода и облегчить разработку новых функций. Поэтому мы приступили к комплексному инженерному проекту: уменьшению кода чертежа.
Стоит получить технический обзор того, что такое рисунок, почему он такой классный и какое место мы занимаем в общем проекте. Итак, давайте начнем с того, как работает прокрутка, как упоминалось ранее.
Раньше, если мы хотели прокрутить div, нам нужно было перерисовывать каждый кадр. Это означает, что если пользователь продолжает перетаскивать колесо, нам нужно сгенерировать все пиксели, и пользователю нужно дождаться, пока мы запустим весь конвейер рендеринга, прежде чем продолжить движение.
Вот удивительное новшество под названием синтетическая прокрутка нити (composited threaded scrolling), который состоит из двух частей, одна из которых — компоновка, что очень похоже на вдохновение из видеоигр, идея состоит в том, чтобы нарисовать всю прокручиваемую область в графическом буфере изображения, а затем не перерисовывать движущуюся область в каждом кадре, а копировать подтекстуру в другую текстуру. Второе новшество заключается в отделении операции прокрутки от основного потока.Помните, что я упоминал ранее, насколько ценны ресурсы основного потока.Основная идея здесь заключается в том, что мы можем прокручивать во время работы JavaScript. Объединение этих двух вещей — довольно удивительное новшество, и эту идею композитного рендеринга потоков можно обобщить на любое место, где требуются модификации текстур.
Например, преобразование, непрозрачность, фильтр, клип и т. д. — все это можно реализовать с помощью идеи синтетических потоков. Когда вы работаете на программном обеспечении, рисуя пиксели с помощью ЦП, это быстро, но если вы работаете на графическом процессоре, это молниеносно.
Но вотlair explosion)». Как показано ниже, если мы повернем зеленый прямоугольник с помощью композитного потока, он пройдет через синий прямоугольник. Проблема в том, что нам нужно убедиться, что синий прямоугольник будет нарисован поверх зеленого прямоугольника, так что синее поле также будет составлено. Эта ситуация займет много памяти. Как интерфейсный инженер, вы устанавливаете прозрачность на странице, и вы можете внезапно обнаружить, что память взрывается, потому что другие части страницы также синтезируется.
Давайте взглянем на текущую архитектуру синтезатора, чтобы проиллюстрировать, как работает синтезатор и каков эффект сокращения кода отрисовки.
У нас есть простая древовидная структура DOM с прокручиваемыми элементами div со смайликами emoji. Его жизненный цикл такой же, как и упомянутый выше, поэтому в ссылке для набора мы будем отмечать информацию о размере и положении дерева макета, а затем ссылку для настройки синтеза, мы будем ориентироваться на нее.
a, b, d не прокручиваются, поэтому их три можно отрисовать вместе в один и тот же графический буфер (graphics buffer). Хотя смайлик emoji можно прокручивать, мы не хотим перерисовывать каждый кадр для его прокрутки, поэтому поместите его в отдельный графический буфер. Теперь, когда у нас есть два графических буфера, пришло время рисовать.
В процессе рисования мы фактически просматриваем дерево компоновки и записываем команды рисования. Затем идет растеризация.
На этом этапе мы выполним команды рисования, записанные на этапе рисования, чтобы сгенерировать реальные пиксели.
В конце концов мы соберем их все вместе на странице, и прокрутка смайликов вверх и вниз не вызовет шаг перерисовки.
В существующей архитектурной системе есть две проблемы, одна из нихКомпозиция ограничена определенными поддеревьями. У дерева компоновки есть свойство, которое определяет, можем ли мы компоновать или нет. Не все поддеревья обладают этим свойством, поэтому мы не можем произвольно конвертировать div на странице в графические буферы, что привело к фундаментальной ошибке композиции, впервые обнаруженной в 2014 году.
В то время мы пытались сделать так, чтобы iframe был составным везде, чтобы улучшить производительность прокрутки, и оказалось, что контент на странице исчезал мгновенно, причина в том, что если вы делаете составной iframe, вам также нужно убедиться, что любой отрисовываемый контент поверх него еще и составной. Это была разрушительная ошибка, которая была обнаружена в 2014 году, потому что вы встроили эту специальную логику, чтобы не создавать слишком много графических буферов для обработки подобных вещей. ваши руки, речь идет не о том, чтобы привязать ваши руки к крайнему случаю, это может быть случай (у Gmail была эта проблема с оптимизацией прокрутки, которая не работала), что помешало нам продолжать строить на текущей архитектуре.
Вторая проблема с нашей текущей архитектурой синтеза заключается в том, чтоНастройки композиции выполняются перед рисованием. Мы создаем буферы изображений в начале системы, вам нужно пересчитывать их на этапе отрисовки, поэтому у нас повторяющаяся логика, сложно описать, насколько сложна эта логика, но я могу сказать, что примерно половина кода отрисовки предназначена для этого размера и эффекта. , например клип.
Помимо выполнения этой настройки композитинга перед рисованием, есть проблема, потому что она находится в основном потоке, а это означает, что любые эффекты, которые могут изменить размер нарисованного объекта, должны вернуться в основной поток. Например, если у вас есть два синтезируемых поля, одно из которых можно прокручивать, то во многих случаях вам придется предполагать худшее. Вы должны исходить из того, что компоновщики могут располагаться в любом месте страницы, поэтому вам нужно создавать буферы изображений для многих вещей на странице, что является проблемой взрыва гнезда, которую мы обсуждали ранее, что приводит к реальным проблемам с производительностью.
Проект прореживания кода отрисовки изменил обе эти проблемы во всей нашей архитектуре. Это меняет то, как мы выбираем степень детализации композитинга, так что вы можете комбинировать, превращать любой эффект в буфер изображения, а во-вторых, мы перемещаем настройки композитинга на пост-рисование. Это не только устраняет основные ошибки синтеза, но и позволяет избежать логического дублирования.
Таким образом, новая архитектура композитинга позволяет выполнять композитинг на любых границах, и мы переместили приложение настроек композитинга, чтобы уменьшить нагрузку на основной поток. Это позволяет нам принимать точные композитные решения о перекрывающихся вещах, может делать вещи, которые изменяют размер объектов, отрисованных от основного потока.
В ходе этого проекта мы завершили работу над кэшем отрисовки, которая в настоящее время находится в M67, и только что выпустили версию V1.75 прореживания кода отрисовки. В конце этого года (2018) мы выпустим версию V2, в которой настройки композитинга перенесены на пост-рисование.
3. Макет макета
Есть две основные проблемы с версткой, первая — это проблема веб-платформы, которую мы называемкомбинаторная задача(The Combinatorial Problem). У нас есть тонна веб-стандартов, и мы постоянно добавляем новые, в то время как старые все еще существуют, каждый раз, когда мы определяем новый стандарт CSS, он создает новый набор стандартов, в котором взаимодействуют все существующие стандарты CSS. То, как они сочетаются, немного странное, с этим связано много пограничных случаев, давайте начнем сflexboxПосмотрите в качестве примера:
Очень простые блоки с тремя гибкими элементами, мы добавляем несколько свойств, чтобы увидеть, как меняется макет.
настраиватьdirection: rtlИзменит направление компоновки справа налево.
На этой основе добавитьflex-direction: row-reverse, направление компоновки восстанавливается слева направо.
ПучокdirectionАтрибуты удаляются, и они располагаются справа налево.
flex-directionУстановить какcolumb-reverse, макет изменится на Столбцы.
настраиватьwriting-modeв то же времяflex-directionИзменен на линейный макет, так что направление текста также изменилось.
flex-directionИзменение на обратное, все еще сложные ожидания.
flex-directionПереход на столбец, то же самое. Здесь достаточно примеров, вышесказанное — это сложные ожидания, потому что я потратил три недели на исправление различных ошибок.
В других браузерах ядра это не обязательно так, как показано на рисунке выше, первая цифра — это производительность приведенного выше примера flexbox в хроме, производительность второго браузера в первой строке почти такая же, но третий и четвертый Но это далеко.
Я не собираюсь пренебрегать другими браузерами.В качестве другого функционального примера, возможно, хром - тот, у которого худшая производительность. Я просто хотел подчеркнуть, что эта проблема совместимости действительно существует, и сложные функции CSS продолжают накапливаться.
Второй вопросКод, связанный с компоновкой в Blink, очень древний, наполненный неинкапсулированным, нереентерабельным, непоточно-безопасным спагетти-валуном кода..
Чтобы сначала объяснить код валуна, есть дерево макета, узлы — это объекты макета, предположим, мы изменили CSS для элемента под деревом. Элемент теперь грязный и должен быть перенаправлен. Следующее, что нам нужно сделать, это пометить всю цепочку предков, когда мы хотим выполнить фазу компоновки, мы всегда начинаем с вершины дерева и идем вниз, теперь у нас есть ряд оптимизаций, но оптимизированный они не пропускают много шагов.
Нам по-прежнему приходится выполнять полный обход дерева, что также является ресурсоемким, каждый раз, когда мы выполняем компоновку. Нижний узел может быть в коробке фиксированного размера, он может даже использоватьCSS containment, что является новой функцией, чем-то похожей на контракт браузера, означающей, что это поддерево не влияет ни на что, кроме самого себя, и ничто за пределами поддерева не влияет на него.
Было бы неплохо, если бы у нас уже была вся необходимая информация при разметке этого поддерева, и нам не нужно было искать какую-либо дополнительную информацию за пределами этого поддерева для определения размера и положения. Однако на самом деле мы запускали код макета, чтобы получить дополнительную информацию.
Находясь в этом узле графа, можем ли мы по каким-то причинам перепрыгнуть в другую часть дерева? Нет, это деструктивная операция.
Что касается безопасности потоков, помните конвейер рендеринга, о котором мы узнали в начале? Мы просматриваем дерево компоновки, также аннотируем его и передаем на стадию отрисовки. Когда мы все закончим и будем готовы сгенерировать следующий кадр контента, мы начнем с последнего использованного дерева макета и обновим его с учетом изменений. Ничто здесь не является потокобезопасным, может быть несколько потоков, модифицирующих его.
Для двух вышеуказанных проблем есть два соответствующих решения. противкомбинаторная задача, решение - это пользовательский макет CSS, т.е.Houdini, что означает, что вы можете установить определенные свойства CSS для элемента, а затем определить функцию JavaScript, которая отвечает за размещение этого элемента и его поддерева. Во время обычной компоновки мы приостанавливались, а затем вызывали функцию JavaScript, передавая ей набор информации, необходимой для компоновки элемента, и функция ее потребляла. Я не буду много говорить здесьHoudiniПодробности, если вам интересно, вы можете изучить самостоятельно.
Решение второй проблемы состоитLayout NG, что на самом деле является полным переосмыслением того, как делается макет.Layout NGЕсть два свойства, одно из которых использует макет, управляемый ограничениями, вводит поддерево, чтобы разместить всю информацию, которую мы передаем ему, в макете поддерева, которое ему нужно, и оно не смотрит за пределы поддерева. Этого также нелегко достичь, с помощью обязательного пакета мы позволяем базовому коду макета легче реализовать CSS для настройки только что упомянутого макета. Вторая характеристика заключается в том, что входное (дерево макета) и выходное (дерево фрагментов) деревьянеизменяемый объект, мы создаем новое дерево макета каждый раз, после того, как мы его создадим, дерево будет неизменным, вместо того, чтобы аннотировать это входное дерево, мы копируем его и заменяем поддерево новым, чтобы изменить дерево поддерева, у нас будет свежий копия дерева компоновки.
Реализация этих двух функций позволит провести различные мощные оптимизации макета. Проект все еще находится на ранней стадии, и ожидается, что первая фаза будет выпущена к концу этого и началу следующего года.