Edge ненавидит ваши атрибуты

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



Предыстория


Однажды один из моих коллег обратился ко мне с предложением подумать вместе над одной весьма занимательной задачей: демо C3 (библиотека для отображения графиков) при копировании в наше AngularJS приложение становилось настолько медленным при использовании Edge, что начинало целиком и полностью соответствовать поговорке "тише едешь — дальше будешь". Дальше от заказчика, текущего и всех будущих потенциальных проектов.


Первая мысль — проверим в других браузерах. Chrome и Firefox упорно настаивали на том, что у нас все хорошо.
Значит дело в зависимостях. Начали убирать зависимости из angular.module(). После того, как там не осталось ровным счетом ничего наше положение было все таким же бедственным.
Следующим шагом было использование встроенного в dev tools performance tracker'а. К сожалению, мы долгое время смотрели совсем не туда, анализируя снова и снова время вызова различных JS функций, пока, совершенно отчаявшись, мы не взглянули на время обработки CSS. Стали разбираться. Сразу удивило то, что после чистки проекта размер собранного и минифицированного CSS файла все еще был внушительным. Оказалось, что в этом проекте была подключена библиотека flex-attr, которой никто не пользовался и которая просто приехала по наследству от бойлерплейта.


Что же такое могла делать эта библиотека, что она почти что вешала Edge на пару секунд?
Flex-attr — это набор CSS правил для удобной работы с flex'ами прямо из HTML. В первые я столкнулся с этим подходом, когда использовал Angular Material для своего проекта. В последствии с Angular Material было решено распрощаться, но подход мне понравился, и я сделал свой форк, оставив только CSS и SCSS, портировав его под LESS и добавив столь необходимую мне возможность переопределять и добавлять новые постфиксы и размеры экранов (breakpoints).


На свой проект коллега получил эту библиотеку, использовав наш внутренний бойлерплейт, в который я ее самолично добавил немногим ранее. Что это за жизнь, если ты регулярно не стреляешь себе в ноги, не правда ли? Главное — не в голову и не из дробовика как Кобейн, но у меня еще есть время до 27, так что можно не волноваться.


Как следует из описания, эта библиотека — это CSS файл с большим количеством правил для атрибутов ( [your-attribute] ). После ее отключения проект ожил и забегал как пасхальный кролик. Однако осадок остался. Захотелось бенчмарка, чтобы понять глубину проблемы (может, конечно, и потому, что у моя жизнь пуста и только работа может как-то развлечь, но мы же культурные люди и не будем предполагать таких пошлых вещей?). Захотелось ответов. Захотелось понимания того, как необходимо эту библиотеку переписать, чтобы она перестала быть одним из пыточных приборов святой инквизиции.


Бенчмарк


Для измерения производительности был написан простой скрипт на Python. Его задача создать набор html файлов с различным количеством блоков и CSS правил для классов и атрибутов, а также вставить туда JS код для определения времени исполнения.


Структура HTML файла:


<!doctype html>
<html>
<head>
<title>CSS Tag Selctor Test</title>
</head>
<body>
    <style>
        CSS goes here
    </style>
    <script type="text/javascript">var renderTime = Date.now();window.addEventListener("load", () => console.log(Date.now() - renderTime))</script>

    <div>0</div>
    <div>1</div>
    <div>2</div>
    ...
    <div>n</div>
</body>
</html>

Каждый div имеет либо класс,


<div class="test-selector-0">0</div>

либо атрибут


<div test-selector-0>0</div>

Каждое CSS правило задает background-color для блока. Каждому блоку соответствует лишь одно правило. Если количество правил меньше количества блоков, то они применяются снова с начала. Время загрузки (в мс) определяется по событию load и выводится в консоль.


Для тестов была создана новая виртуальная машина с Windows 10 Enterprise с 4Гб RAM и 2 ядрами (если быть точнее, то это одно физическое ядро, но два потока выполнения, благодаря Hyper Threading). Были использованы браузеры Microsoft Edge 38.14393.0.0, Google Chrome 60.0.3112.101, Mozilla Firefox 55.0.2. В каждый момент времени был открыт только один браузер, в которым была открыта только одна вкладка. Во избежания влияния каких-либо системных процессов на результаты бенчмарка каждый тест был запущен по 10 раз для каждого браузера и было взято медианное значение. Исключение составили тесты "10000 блоков/10000 правил для атрибутов", "50000 блоков/10000 правил для атрибутов" для Edge. Было произведено лишь 5 и 3 замера соответственно, т.к. мне было попросту лень ждать рендера страницы по 5-6 минут. В дальнейшем я буду оперировать лишь медианными значениями. С полными результатами вы можете ознакомиться здесь.


Результаты для 100 CSS правил


Blocks Chrome
(classes)
Firefox
(classes)
Edge
(classes)
Chrome
(attributes)
Firefox
(attributes)
Edge
(attributes)
100 4 16.5 30 4 18 23.5
1000 16.5 95.5 44 19 125.5 139.5
10000 437.5 653 382 452 585 1338.5
50000 3489 2565 5176 3566.5 2865.5 7061



Результаты для 1000 CSS правил


Blocks Chrome
(classes)
Firefox
(classes)
Edge
(classes)
Chrome
(attributes)
Firefox
(attributes)
Edge
(attributes)
100 12.5 20.5 16.5 17.5 38.5 128
1000 40.5 115.5 127.5 96.5 198.5 647.5
10000 479 618.5 974 960 1242 4578
50000 3719.5 2877.5 5358.5 5936 6582.5 26597.5



Результаты для 10000 CSS правил


Blocks Chrome
(classes)
Firefox
(classes)
Edge
(classes)
Chrome
(attributes)
Firefox
(attributes)
Edge
(attributes)
100 99 33 24 156.5 134.5 1034.5
1000 149 85 77.5 618 8760 5185.5
10000 655 625.5 788 5466 11020.5 46623
50000 3681.5 3183.5 4743.5 36779 50663.5 326838



Учитывая тот факт, что библиотека, послужившая первопричиной данного исследования, была подключена к исходному приложению, но ни одного класса из нее не было использовано, было решено провести еще один тест: сравнить производительность для тех случаев, когда CSS правила действительно применяются в HTML и когда нет (т.е. в CSS они будут, но никто их использовать не будет). Для этого был добавлен параметр --no_apply_css в первоначальный бенчмарк.


Результаты для 10000 CSS правил (если в скобках ничего не указано, то CSS правила были использованы в HTML)


Blocks Chrome Firefox Edge Chrome
(No CSS applied)
Firefox
(No CSS applied)
Edge
(No CSS applied)
100 156.5 134.5 1034.5 177 84.5 1213.5
1000 618 8760 5185.5 689 562 6037
10000 5466 11020.5 46623 6700 5156 76401
50000 36779 50663.5 326838 38750 34351 431700



Вывод


Как можно было заметить, производительность всех браузеров деградирует при использовании атрибутов, начиная с определенного размера CSS файла. Все же Edge и тут отличился и не посрамил своей репутации самого быстрого браузера на нашем голубом шарике под названием Земля. Что с этим делать? Не использовать атрибуты для написания вашего CSS. Также стоит очень внимательно смотреть на те библиотеки, которые вы подключаете: они могут привезти с собой пачку столь нежеланных правил для атрибутов. И как показал последний эксперимент, даже если вы не используете ни одного правила для атритутов на вашей страниц, где вы решили вывести огромный список или таблицу (вопрос "зачем?" оставим за рамками этого обсуждения), производительность все равно пострадает. Если по какой-то причине использования большого количества атрибут-селекторов не избежать, то стоит подумать о том, чтобы собирать их в отдельный CSS файл, и загружать этот файл только в случае крайней необходимости. Для себя я решил попросту перевезти flex-attr на классы и порефакторить существующие проекты, когда работа будет завершена.


Если вам интересно посмотреть на экспериментальные данные в каком-то другом разрезе, то скопируйте гуглодок и агрегируйте данные по своему усмотрению.


UPD 21/08/2017: Отправил баг-репорт в Microsoft https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/13348719/

Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 26
  • 0

    Кажется, в angular material перешли на классы именно по этой причине.

    • 0
      Честно говоря, не нашел, где они перепилили свою CSS либу на классы. Можете поделиться ссылкой? В этом случае не пришлось бы переписывать самому.
      • 0
        При рендере angularjs дублирует эти атрибуты в классы, а в самих стилях нашёл коммит от 22 августа 2015: github.com/angular/material/commit/372d911a7893214cfe0c47db4dafb19470221cad
        • +1
          Уточню, в доке они пишут:
          Due to performance problems when using Attribute Selectors with Internet Explorer browsers…
          ...Layout directives dynamically generate class selectors at runtime…
          Developers should continue to use Layout directives in the HTML markup as the classes may change between releases.

          Вольно переведу:
          Из-за проблем с производительностью Internet Explorer при использовании атрибутов…
          … Директивы разметки динамически генерируют классы во время выполнения…
          Разработчикам следует продолжать использовать декларативный подход, поскольку классы могут быть изменены между версиями.
    • +1
      А data-* атрибуты случаем не тестировали?
      • 0
        Нет. Сомневаюсь, что есть принципиальная разница.
      • –2

        А отправить but report Microsoft не думали?

        • +12
          Butt report?
          какое интересное определение
          • 0
            Я так понимаю, эта проблема была актуальна еще у дедушки IE. Когда писал статью, то всплывали смутные воспоминания о том, как я де-то когда-то мельком видел инфу, что в IE аттрибуты тормозят. Тем не менее отправить баг репорт стоит. Спаибо за мысль.
          • +3

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


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

            • +1

              А откуда вы заранее узнаете, какие правила используются? Особенно если атрибуты изменяются, что вполне типично для интерактивного UI.


              Графики кстати странные, я бы не был так уверен, что кривая экспоненциальная (хотя исключать скажем полиномиальную зависимость тоже бы не стал бы). Может автор ответит, почему от 0 до 500 и от 500 до 1000 разное расстояние по оси X?

              • +1

                Я не об экспоненциальности как таковой, а о том, что человек сделал отличный анализ ситуации и было бы неплохо чтобы разработчики это исправили.

                • 0

                  Не, ну понятно что практически это все равно, и то и другое плохо. Но все-таки было бы интересно понять, насколько плохо.


                  Насчет исправили — написать баг репорт конечно же стоит, но я просто смотрю практически. Чтобы поиск по атрибуту был быстрее, нужно строить некий индекс по значениям атрибута. А он будет занимать память. И еще его нужно обновлять, потому что атрибуты (да и набор правил CSS) могут меняться, на что тоже будут тратиться ресурсы. Ну т.е. я это к чему — любая такая оптимизация, она есть компромисс. Нельзя оптимизировать все операции. Нельзя оптимизировать на базе изменения одной операции.

                • 0
                  Ось логарифмическая. Если не удобно смотреть в таком представлении, то вы можете скопировать себе гуглодок по ссылке и отредактировать графики.
                  • 0

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

                  • +1
                    Если делать ось не логарифмической, то значения в начале сбиваются в кучу и их не удобно читать
                • –2

                  Автор, что у вас за сетка такая на графиках странная? Ось X неравномерная как будто, вводит в заблуждение.

                  • +6

                    Она всего лишь логарифмическая.

                    • 0

                      Погодите, погодите. Логарифмическая или линейная — но на оси все-же принято рисовать штрихи через одинаковые промежутки. Ну т.е. между 10^1, 10^2 и 10^3 рисуют отрезок одинакового размера.


                      А на этих графиках отрезок от 0 до 500 выглядит вдвое длиннее, чем от 500 до 1000. При этом в обоих отрезках, если верить написанному, по 500 единиц. Вот я о чем. Ну т.е. да, скорее всего оси логарифмические, но разные размеры отрезков сбивают все-таки с толку.

                      • 0

                        Замечу, что там не от 0 шкала, а от 100. То есть штрихи на 100, 500, 1000, 5000. log(500/100)~= 0.7. log(1000/500) ~= 0.3. log(5000/1000)~= 0.7. log(10000/5000) ~= 0.3. Т.е. как раз получается, что там интервалы более чем двойной длины чередуются с короткими.


                        На логарифмической шкале штрихи получаются равномерными, если их привязывать к "круглым" (по основанию) числам. Например 1, 10, 100, 1000 (для основания 10). А если возникает желание поставить промежуточные деления, но с привязкой к круглым числам, то шкала сразу же перестаёт быть равномерной. И в этом нет ничего необычного для тех, кто привычен к логарифмической шкале.

                  • 0
                    А у вас начальство требует обязательной поддержки Edge? Он же вроде не особо популярен…
                    • 0
                      У нас начальство, например, не нём (Edge) и сидит…
                      • 0
                        Определенные заказчики на определенных проектах требуют. По дефолту нет.
                      • 0
                        По последней таблице получается, что Chrome и Edge работают даже медленнее, если правила не применялись. Это действительно так, или столбцы перепутаны?
                        • 0
                          Нет. Все именно так и обстоит. Возможно, разницу можно списать на параллельные случайные процессы в системе, и предположить, что время одинаково примерно. Другой вариант — для каждого отдельного элемента в DOM дереве браузер пытается вычислить окончательные стили в CSS дереве. В случае если стили могут быть найдены для конкретного элемента, то браузеру достаточно обойти только часть дерева, а если их нет, то он обходитвсе дерево, чтобы удостовериться, что подходящих стилей нет. Повторюсь, что это только предположение.
                          • 0

                            Вполне нормальное предположение. Скорее всего есть перебор элементов и перебор правил CSS. Если вы что-то нашли в CSS, то скорее всего можно на этом перебор закончить (особенно если правила у вас отсортированы до этого). Если нет — то перебираете до конца таблицы.

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