Pull to refresh

Исследуем скорость выполнения JS и алгоритм отображения страниц

Reading time4 min
Views7K
Тестирование скорости выполнения JS или отображения страниц — занятие неблагодарное. Любое тестирование отражает действительность только тогда, когда оно выполнено в как можно более одинаковых условиях и тестируются идентичные по функциональности вещи. Ведь на вопрос, что быстрее, грузовик или спорткар, каждый тут же ответит, что спорткар. А если по полю да с прицепом навоза? Победитель в каждом случае будет тот, кто лучше всего приспособлен для выполнения специфических задач.

В этой статье будет немного гипотез и немного фактов. Не будет фанатских речей и призывов сменить браузерную ориентацию.

Итак, наши подопытные кролики:
  • FF 4b7
  • Opera 10.63
  • Chrome 7

Я не тестировал IE9, потому что у меня он установлен на виртуальной машине, а это чревато наличием пенальти по скорости и ощутимым разбросом значений.


JS-движки оптимизировали, оптимизировали, да не выоптимизировали


Гонка за скоростью работы JS-движка сделала благое дело: увеличила отзывчивость браузеров, дала кодерам больше возможностей по управлению отображением страниц и сделал анимацию приятной для глаза. Это не все плюсы, и перечислять их можно долго. Узнали мы про чудеса оптимизации по замечательным бенчмаркам, таким как SunSpider, Dromaeo, V8 Benchmark Suite и другим. Теперь возникает вопрос, а что же намеряли все эти бенчмарки? Скорость выполнения JS? Возможно. А возможно и нет.

Что нам стоит DOM построить?


Давайте возьмем простой, коневакуумосферический пример: некий скрипт в цикле создает DOM-ноды, которые потом добавляет в документ.

Забегу немного вперед. Зная, что Javascript-движки могут существовать отдельно от браузера, я предполагаю, что выполнение нашего тестового скрипта будет разбито на три этапа:

  1. Выполнение JS-функции создания ноды (для упрощения назовем это «JS»)
  2. Создание DOM-объекта в браузере (DOM)
  3. Отрисовка ноды (Rendering)


Последовательное выполнение


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

image

Это не самый плохой способ реализации. Главный его недостаток в том, что нельзя «отрезать» JS-движок от других функциональных частей браузера. Однако есть простой способ это обойти.

Поочередное выполнение задач


А давайте сделаем финт ушами и разделим наши слои некими очередями выполнения. Движок JS просто занесет в очередь набор команд для выполнения и пойдет себе спать, предварительно передав задачу дальше — фабрике DOM-объектов. Фабрика выполнит все нужные команды, попутно создав готовый для рендеринга набор объектов. Заносим все в очередь отрисовки и передаем эстафету системе рендеринга. Вуаля!

image

В такой архитектуре достаточно просто «отрезать» одну часть браузера от другой, общение-то происходит через очередь заданий.

Полный параллелелизм


В предыдущем варианте выполнение этапов идет последовательно. А что если сделать три параллельных системы? Фабрика DOM-объектов следит за очередью заданий, и как только появляется команда, сразу делает DOM-ноду и отправляет в очередь на отрисовку.

image

Страшно? Да, страшно интересно!

Тестирование


Давайте теперь измерим скорость выполнения JS каждой из представленных выше архитектур. Еще не начиная тестирование, уже понятно, что в первом случае мы измерим скорость выполнения всех трех этапов, во втором и третьем — только скорость создания очередей. Но это же не будет отражать действительность!

Чтобы уравнять три подхода, я сделал простой бенчмарк (RapidShare / Yandex / pasteBin). Случайным образом генерируются клоны нескольких заготовок. Если клон последний в строке, то ему присваивается ширина таким образом, чтобы он занимал все доступное свободное пространство. Элементы плавающие и пока не будут отрисованы, не понятно какая позиция и ширина у них. Фактически, я максимально привел все к последовательному исполнению команд.

Тестирование меня реально удивило.

Для начала посмотрим на результаты тестирования без нагрузки.

image
500 750 1000
Firefox 19 30 45
Opera 6 10 14
Chrome 5 9 14

Фокс в три раза отстает от Оперы и Хрома. Хром чуть быстрее Оперы, но с ростом количества итераций отставание сокращается.

А теперь включаем «тормоз»

image
500 750 1000
Firefox 950 2350 4800
Opera 610 1250 2100
Chrome 5700 20500 ~55000

Я не поверил своим глазам. На тысяче итераций Хром впадает в жестокую задумчивость и выдает просьбу не издеваться над его ранимой душой и прекратить страдания путем остановки выполнения скрипта. Вот это палка в колеса!

Опера безоговорочно выиграла соревнования, Фокс второй.

Немного анализа


Если взять отношение результатов с нагрузкой к результатам без оной, то мы видим достаточно интересную картину. Firefox теряет в производительности от 50 до 100 раз. Деградация скорости от количества итераций почти линейная. Картина и поведение браузера очень хорошо ложится в первую схему — последовательное исполнение шагов. Визуально, Фокс не отрисовывает страницу, пока не закончит цикл.

У Оперы деградация скорости при включении нагрузки составляет от 100 до 150 раз. Опера отрисовывает страницу по мере выполнения скрипта, что очень похоже на схему параллельного выполнения трех этапов.

Деградация скорости у Хрома составляет от 1000 до 4000 раз. Хром не отрисовывает страницу, пока не закончит выполнение цикла. Это очень похоже на схему поочередного выполнения этапов.

Забавно выходит.

Заключение


Моя статья носит частично теоретический характер и архитектуры, которые представлены в ней, не подтверждены фактически.

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

UPD. Добавил ссылку на бенчмарк на Yandex.

Многие спрашивают, в какой программе я создаю диаграммы. В Corel Photopaint X5 вручную.

UPD2. Добавил ссылку на pasteBin. Спасибо, друзья!.
Tags:
Hubs:
Total votes 63: ↑56 and ↓7+49
Comments86

Articles