Pull to refresh
577.25
Яндекс
Как мы делаем Яндекс

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

Reading time6 min
Views71K

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



Ссылка, если видео не отображается

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



Начну я с примера: одной из промо-страниц Яндекс.Браузера. На этом сайте мы видим слайдер на всю страницу. Сейчас ее уже переделали, но если посмотреть сохраненную копию, там не все так гладко. Заходим в таймлайн, включаем запись и начинаем прокликивать слайды. Видим, что все не очень хорошо — даже в 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-трансформацией.
  • С включенным аппаратным ускорением.
  • 3D (или 2D с включенным аппаратным ускорением).
  • Плагины (flash, silverlight).
  • CSS анимации opacity/transform.
  • CSS фильтры.
  • Элементы, вынесенные над композитным слоем.

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



Если сайт большой и требует более серьезного подхода, подойдет отладка при помощи Chrome Canary. Для этого нам понадобится инструмент tracing и запуск браузера с несколькими флагами:



  • --enable-thereaded-compositing
  • --force-compositing-mode
  • --enable-impl-side-painting
  • --enable-skia-benchmarking
  • --allow-webui-compositing

Я уже говорил, что слой может перемещаться, скрываться и растягиваться без отрисовки. Есть CSS свойства, которые очень хорошо сочетаются с аппаратным ускорением: opacity, transform, filter. При их анимации (речь идет о CSS анимациях) достаточно просто создать слои и загрузить их в GPU. Все дальнейшие действия происходят там, CPU при этом свободен. Другие CSS свойства могут вызвать обновление текстур.



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

Что нужно помнить при использовании аппаратно ускоренного композитинга


Текстуры могут занимать достаточно много места в памяти, что на мобильных устройствах становится проблемой. Кроме того, можно легко забить шину между CPU и GPU. К GPU нужно относиться как к кэшу. Старайтесь подготавливать текстуры заранее и по возможности переиспользовать имеющиеся. Не стоит создавать лишних слоев.



Нельзя забывать и о подводных камнях. Если у нас есть родитель с border-radius: 50% и overflow: hidden (круг, в котором мы начинаем анимировать композитные слои), то позиционирование слоев не всегда срабатывает корректно: нижние слои выносятся над верхним, несмотря на то, что это дети. Тут нужно жестко прописать, что родитель тоже должен быть вынесен в композитный слой. Сделать это можно, просто добавив ему z-index.



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



Есть также проблема с ресайзом SVG в Safari. Если мы выносим картинку с SVG в композитный слой, а потом начинаем ее ресайзить, SVG становится пиксельно-квадратным (как видео при плохом качестве). Выход тут один — не выносить SVG в отдельный слой в Safari.



Если вам стало интересно, по этой теме можно почитать и посмотреть следующие материалы:



Ссылка на презентацию: http://mrsamo.github.io/ha-presentation

Tags:
Hubs:
Total votes 96: ↑95 and ↓1+94
Comments21

Articles

Information

Website
www.ya.ru
Registered
Founded
Employees
over 10,000 employees
Location
Россия