Яндекс corporate blog
Website development
CSS
October 2014 5

Аппаратное ускорение в жизни верстальщика. Семинар в Яндексе

Привет! Меня зовут Александр Завьялов. В Яндексе я занимаюсь разработкой интерфейсов. Недавно я выступил перед коллегами с докладом об аппаратном ускорении в жизни верстальщика, где также коснулся смежных тем. Рассказал о производительности веб-страниц, о том, как она измеряется и к чему она может стремиться.



На основе доклада я подготовил этот пост. Я расскажу о том, как браузеры оптимизировали процесс отрисовки: с чего начинали и до чего докатились. Что сейчас можно сделать, чтобы жизнь верстальщиков и пользователей стала немного лучше. Я надеюсь, что кого-нибудь натолкну на какие-нибудь улучшения. Мне бы это было приятно.

Начну я с примера: одной из промо-страниц Яндекс.Браузера. На этом сайте мы видим слайдер на всю страницу. Сейчас ее уже переделали, но если посмотреть сохраненную копию, там не все так гладко. Заходим в таймлайн, включаем запись и начинаем прокликивать слайды. Видим, что все не очень хорошо — даже в 30 fps мы укладываемся не всегда. Достаточно добавить одно CSS свойство — знаменитый среди верстальщиков «нулевой хак» transform: translateZ(0), чтобы ускорить отрисовку в два раза.

На этом можно было бы остановиться, но я увидел у слайда CSS свойство background-size: cover. Это свойство растягивает картинку на всю площадь блока. Зачем это было сделано остается тайной, т.к. высота блока фиксирована. Так что свойство не делало ничего, кроме ресайза картинки с 600 до 540 px по высоте. Ресайз картинки — это очень дорогостоящая операция, поэтому я отключил это CSS свойство и все стало совсем хорошо. Вот так буквально за пару минут мы в 4 раза увеличили fps. Все эти изменения вошли в новую версию страницы.

Я уже несколько раз упомянул fps — frames per second. Это частота, с которой меняется изображение на дисплее. На большинстве экранов она равна 60 Гц, т.е. картинка меняется за секунду 60 раз. Путем сложных математических вычислений приходим к выводу, что каждый фрейм у нас создается раз в 16.6 миллисекунд. Если у нас на странице есть какая-нибудь анимация, нам нужно, чтобы браузер успел создать и отдать новый фрейм за 16 мс. Если он перестает это делать (а мы виртуозно умеем мешать ему в этом), браузер начинает фреймы пропускать. Количество fps падает, плавность уменьшается.

Вот примеры из таймлайна. Каждому цвету здесь соответствует свой процесс. Думаю, они многим знакомы: script, render, paint/composite. Виновник проблем у нас чаще всего последний, который отвечает за отрисовку и составление страницы.



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

От разметки к изображению


Допустим у нас есть простая страничка с вот таким html-кодом:

<!DOCTYPE html>
<html>
<head>
    <title>Пиар самого себя</title>
    <meta charset="utf-8">
    <link rel="stylesheet" href="main.css">
</head>
<body>
<section>
    <h1>Всем привет!</h1>
    <img src="i.jpg" alt="Я">
    <p>Я занимаюсь фронтенд разработкой.</p>
    <p>И это круто!</p>
</section>
<script src="main.js"></script>
</body>
</html>

Выглядеть она будет следующим образом:



Первое, что делает браузер — выделяет теги и строит из них дерево DOM:



Паралельно парсится CSS, накладывается на DOM, получается RenderTree. Это дерево отображения, содержащее Render Objects — объекты, которые нам надо показать пользователю. Дерево рендера для этой страницы выглядит так:



Оно похоже на дерево DOM с некоторыми отличиями. В нем могут быть псевдоэлементы, которых нет в дереве DOM. Здесь также отсутствуют некоторые узлы из DOM: секция head, тэги link, script, элементы с display: none — все, что нам не надо показывать. Мы можем добавить в CSS некоторый код, от которого DOM не изменится, а в Render Tree появятся новые элементы.

После этого начинается компоновка (layout/reflow). Когда браузер скомпоновал элементы по странице, ему осталось заполнить все это пикселями. Тут нужно немного рассказать про растеризацию: перевод изображения из вектора в конечную сетку пикселей, которую браузер покажет пользователю. В Хроме этим занимается графическая библиотека Skia.



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

Весь процесс отображения страницы браузером выглядит так: парсинг HTML, парсинг CSS, DOM Tree, Render Tree, компоновка и отрисовка. Причем, изначально viewport представлял собой один растр. Вот так иллюстрируют этот процесс разработчики Chromium:



В разделяемой памяти лежал конечный битмап страницы. C помощью оконного API данный битмап отрисовывался в нужное окно браузера. В процессе скроллинга страницы появлялись ранее невидимые Render Objects, которые нужно показать. Браузер создавал и отрисовывал новый растр вьюпорта. Это неоптимально, т.к. при скроллинге не хочется ждать пока все новое будет растеризовано. Хочется двигаться по уже готовой странице. Разработчики браузеров подумали об этом раньше, чем я, и ввели новый процесс — compositing.

Теперь страница делится на слои. Изначально это был один слой, соответствовавший всему документу. Этот слой растеризовывался, сохранялся в памяти и выводился на экран. В этом случае при скроллинге браузеру не нужно было заниматься растеризацией, он оперировал уже готовым изображением. Процесс размещения на экране готового растра называется compositing (композитинг). Этот процесс выполнялся силами CPU. Производительность заметно улучшилась, но разработчики решили еще немного оптимизировать процесс, перенеся композитинг на GPU.

Аппаратное ускорение


И они это сделали. Первыми об этом начали говорить Microsoft, они пообещали эту функцию в IE9. По факту же первым выстрелил Firefox в четвертой версии. Потом вышел IE9, а в течение года подтянулись и все остальные браузеры. На текущий момент все основные браузеры, включая мобильные, отображают страницы с ускоренным при помощи GPU композитингом. Что происходит в этом случае? Страница точно так же делится на слои, которые растризуются в текстуры и отправляются в GPU, где они хранятся в видеопамяти. Тут в дело вступает новая часть системы — compositor. Она инструктирует GPU о том, как собрать конечное изображение.



Слои


Слой (композитный слой) — это часть страницы, поддерево DOM. Он отрисовывается независимо и компонуется в GPU. Он может растягиваться, перемещаться, скрываться (через прозрачность) без отрисовки.

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

  • Элементы с 3D-трансформацией.
+94
66.2k 674
Comments 21
Top of the day