JavaScript профилирование с инструментами разработчика в Google Chrome

JavaScript профилирование с инструментами разработчика в Google Chrome

После достижения работоспособности сайта, самое время позаботиться о его ускорении. Стоит обратить внимание на две вещи: на сколько быстро загружается страница и на сколько быстро работает клиентский код.

Существует много способов ускорить загрузку страницы, например, минимизация и сжатие javascript или использование сети доставки данных (CDN), но меня интересует, каким образом можно ускорить работу скрипта на стороне клиента.

Перевод статьи «Javascript profiling with the Chrome Developer Tools».

Небольшие изменения в коде могут оказать большое влияние на работоспособность сайта. Всего несколько строчек кода могут либо значительно ускорить сайт, либо продемонстрировать «кривые руки» разработчика. Эта статья, надеюсь, поможет научиться обнаруживать критические участки кода, используя панель инструментов разработчика для браузера Google Chrome.

Определение скорости отрисовки страницы

Рассмотрим пример простого приложения для перетаскивания цветов. Оно представляет собой таблицу из различных цветов, которые можно перетаскивать. Каждая ячейка представляет собой тег div с небольшим CSS кодом, с помощью которого она превращается в круг.

Приложение для перетаскивания цветов

Генерировать цвета радуги было лень, поэтому я воспользовался статьей «Making Annoying Rainbows in JavaScript».

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

Улучшение производительности приложения лучше всего начинать с анализа скорости загрузки. Использование baseline позволит узнать, эффективны ли улучшения, и поможет найти удачное решение. В этой статье я постараюсь рассмотреть инструменты разработчика в браузере Chrome.

Профилировщик входит в состав интрументов разработчика браузера Google Chrome. Что бы их открыть, я нажал на пункт меню «Инструменты», который находиться в списке под маленьким гаечным ключом. Firebug тоже имеет некоторые инструменты профилирования, но с помощью WebKit браузеров (Chrome и Safari) гораздо удобнее профилировать код, к тому же они показывают таймлайны. Кроме того, в Chrome есть отличный инструмент для трассировки событий, который называется Speed Tracer.

Для анализа скорости загрузки сайта, я перешел на вкладку «Timeline» и выполнил следующие действия:

  1. Нажал на маленький черный кружочек «Record», после этого он стал красным;
  2. Перезагрузил страницу;
  3. Снова нажал на маленький кружочек, чтобы остановить запись.

Я повторил эти действия три раза и взял средний результат, на случай если компьютер тормозил во время первого теста.

Определение скорости отрисовки страницы

Мой средний показатель для baseline — т.е. время между первым запросом и окончательной отрисовки страницы в браузере — 1.25 секунды. Это не плохо, но все же много для такой небольшой страницы. Я хочу заставить клиентский код работать быстрее, но не знаю, что именно замедляет его работу. Профилировщик поможет мне в поиске.

Создание профиля

График показал сколько времени занимает исполнение клиентского кода. Но он не покажет, что происходит во время его работы. Конечно, можно менять код наобум и запускать график снова и снова надеясь, что клиентский код начнет работать быстрее, но это малоэффективное занятие. Поэтому я воспользуюсь вкладкой «Profiles».

Чтобы узнать, какие функции занимают наибольшее время, необходимо создать профиль. Для этого я переключился на вкладку «Profiles» — здесь предлагается на выбор три типа профилирования:

  1. JavaScript CPU profile
    Показывает сколько процессорного времени тратиться на выполнение JavaScript.
  2. CSS selector profile
    Показывает сколько процессорного времени занимает обработка CSS селекторов.
  3. Heap snapshot
    Показывает сколько памяти используют JavaScript объекты.

Судя по всему мне нужен Javascript CPU profile. Чтобы его запустить я нажал кнопку «Start», перезагрузил страницу и нажал на «Stop». Вот что получилось:

Первый профиль

В «Profile 1» видно, что на странице происходит много всего интересного. Например, приложение использует библиотеки jQuery и jQuery UI. Наверху списка находятся две функции: decimalToHex и makeColorSorter. Эти две функции занимают 13.2% процессорного времени, их и буду усовершенствовать!

Чтобы просмотреть полный стэк вызовов функции, нужно нажать на стрелочку рядом с этой функцией. Если пройтись по всему стэку, то становится ясно, что функция decimalToHex вызывается из функции makeColorSorter, а функция makeColorSorter вызывается из $(document).ready().

Вот этот код:

<script type="text/javascript">
$(document).ready(function() {
makeColorSorter(.05, .05, .05, 0, 2, 4, 128, 127, 121);
makeSortable();
});
</script>

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

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

Изолирование проблемы

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

Новую функцию я назову testColorSorter и привяжу её к кнопке на событие onClick.

<script type="text/javascript">

function testColorSorter() {
makeColorSorter(.05, .05, .05, 0, 2, 4, 128, 127, 121);
makeSortable();
}
</script>
<button id="clickMe" onclick="testColorSorter();">Click me</button>

Теперь изменения в приложении перед запуском профиля, не смогут неожиданно повлиять на его работу. Такая модификация выглядит довольно безопасной. Но профилирование необходимо запустить снова, чтобы посмотреть не изменил ли я случайно что-нибудь ещё. Для этого я создал новый профиль, запустил его, нажал на кнопку с тестовой функцией и остановил профиль.

 Второй профиль

На скриншоте видно, что функция decimalToHex теперь занимает 4.23% от времени загрузки. Чтобы увидеть сколько времени исполняется код, я создал новую baseline.

Вторая baseline

Перед нажатием на кнопку происходит несколько событий. Однако меня интересует только время нажатия на кнопку и полной отрисовки цветов в браузере. Кнопка мыши была нажата за 390 милисекунд; отрисовка таблицы цветов произошла за 726 милисекунд; 726 минус 390 равно 336 милисекунды. Как и при первом анализе baseline, я повторил анализ три раза и взял средний результат.

Теперь мне известно время работы функций, поэтому можно начинать их оптимизацию.

Оптимизация

Профилировщик указал какая функция является проблемной, таким образом необходимо разобраться в ней и понять что она делает.

<script type="text/javascript">
function decimalToHex(d) {
var hex = Number(d).toString(16);
hex = "00".substr(0, 2 - hex.length) + hex;
console.log('converting ' + d + ' to ' + hex);
return hex;
}
</script>

Каждая точка в сортировщике цветов получает значение фонового цвета в шестнадцатеричном формате, например #86F01B или #2456FE. Эти значения представляют собой красные, зеленые и голубые значения цветов. Например, у точки Blue dot фоновый цвет #2456FE, он подразумевает значения красного цвета 36, зеленого 86 и голубого 254. Каждое значение находиться между 0 и 255.

Функция decimalToHex преобразовывает RGB цвета в шестнадцатеричный формат, который используется на странице. Он довольно прост, но я добавил вывод сообщений в консоль с помощью функции console.log, который после отладки кода будет удален.

Кроме этого Функция decimalToHex добавляет ноль в начале числа. Необходимость в этом возникает, т.к. десятичные числа в десятичной системе соответствуют одной шестнадцатеричной цифре, например цифра 12 соответсвует цифре C, а для записи CSS кода требуется две цифры. Такое преобразования можно ускорить уменьшив функцию. Я переписал ее так:

<script type="text/javascript"> 
function decimalToHex(d) {
var hex = Number(d).toString(16);
return hex.length === 1 ? '0' + hex : hex;
}
</script>

Третья версия функции добавляет ноль только тогда, когда это нужно, и больше не вызывает функцию substr. Время выполнения стало 137 милисекунд. Профилируя код снова, я увидел, что функция decimalToHex теперь занимает 0.04% от общего времени и находиться она теперь внизу списка.

Третий профиль

Я также заметил что функция JQuery e.extend.merge находиться вверху списка. Я не знаю, что эта функция делает, потому что код минимизирован. Можно было развивать версию jQuery, но тут я заметил, что эта функция вызывается из функции makeColorSorter, поэтому ее и будем улучшать.

Минимизация изменений контента

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

<script type="text/javascript">
function makeColorSorter(frequency1, frequency2, frequency3, phase1, phase2, phase3, center, width, len) {

for (var i = 0; i < len; ++i) {
var red = Math.floor(Math.sin(frequency1 * i + phase1) * width + center);
var green = Math.floor(Math.sin(frequency2 * i + phase2) * width + center);
var blue = Math.floor(Math.sin(frequency3 * i + phase3) * width + center);

console.log('red: ' + decimalToHex(red));
console.log('green: ' + decimalToHex(green));
console.log('blue: ' + decimalToHex(blue));

var div = $('<div class="colorBlock"></div>');
div.css('background-color', '#' + decimalToHex(red) + decimalToHex(green) + decimalToHex(blue));
$('#colors').append(div);
}
}
</script>

Я мог бы логировать больше функций в console.log, но это требует вызова функции decimalToHex дважды для каждого цвета, что может замедлить выполнение.

Функция часто изменяет DOM. При каждой итерации цикла добавляется новый тег div в контейнер с идентификатором colors. Это навело меня на мысль, что именно на этот процесс тратится время работы e.extend.merge. Профилировщик поможет это выяснить с помощью простого эксперимента.

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

<script type="text/javascript">
function makeColorSorter(frequency1, frequency2, frequency3, phase1, phase2, phase3, center, width, len) {

var colors = "";
for (var i = 0; i < len; ++i) {
var red = Math.floor(Math.sin(frequency1 * i + phase1) * width + center);
var green = Math.floor(Math.sin(frequency2 * i + phase2) * width + center);
var blue = Math.floor(Math.sin(frequency3 * i + phase3) * width + center);

colors += '<div class="colorBlock" style="background-color: #' + decimalToHex(red) + decimalToHex(green) + decimalToHex(blue) + '"></div>';
}

$('#colors').append(colors);
}
</script>

Такое небольшое изменение позволит изменить объектную модель DOM всего один раз. Тестируя с помощью timeline, я увидел, что время выполнения между кликом и отрисовкой теперь занимает 31 милисекунду. Такое изменение в четвертой версии уменьшили время на 87%. Снова запустив профилировщик я увидел, что функция e.extend.merge в настоящее время занимает такой малый процент времени, что теперь она не показывается в списке.

Функцию decimalToHex можно удалить полностью. Т.к. CSS поддерживает RGB цвета, поэтому не нужно преобразовывать их в шестнадцатеричный формат. Теперь функция makeColorSorter выглядит таким образом:

<script type="text/javascript">
function makeColorSorter(frequency1, frequency2, frequency3, phase1, phase2, phase3, center, width, len) {

var colors = "";
for (var i = 0; i < len; ++i) {
var red = Math.floor(Math.sin(frequency1 * i + phase1) * width + center);
var green = Math.floor(Math.sin(frequency2 * i + phase2) * width + center);
var blue = Math.floor(Math.sin(frequency3 * i + phase3) * width + center);

colors += '<div class="colorBlock" style="background-color: rgb(' + red + ',' + green + ',' + blue + ')"></div>';
}
$('#colors').append(colors);
}
</script>

Пятая версия работает 26 милисекунд и занимает 17 строк кода вместо 28 предыдущих.

JavaScript профилирование в приложении

В реальном мире приложения гораздо более сложны, но профилирование выполняется по тем же основным шагам:

  1. Определить baseline с помощью timeline, чтобы знать откуда начинать.
  2. Изолирование проблемы от остального кода.
  3. Оптимизация кода в контролируемой среде с помощью частых графиков времени и профилей.

Вот еще несколько правил, которых следует придерживаться при настройке производительности:

  1. В первую очередь надо начинать с наиболее медленных частей, это позволит получить наибольший выигрыш за время проведенное за настройкой.
  2. Контролировать среду. Если вы делаете изменения, всегда создавайте новый график времени.
  3. Повторять анализ для предотвращения искажения результатов.

Все владельцы сайтов хотят, чтобы их сайты работали еще быстрее. На сайт нужно добавлять новые функции, но обычно они и замедляют работу сайта. Поэтому инвестирование в оптимизацию производительности окупит себя. Профилирование и оптимизация финальной версии приложения сократили время выполнения более чем на 92%. А на сколько быстрее может работать ваш сайт?

Расскажите друзьям

Оцените статью:
  • 1
  • 2
  • 3
  • 4
  • 5

Комментарии — 15

HtmlMan
Очень познавательно и актуально! Спасибо.
Владимир Старков
Уважаемый Евгений, а вы до написания статьи не знали, что операции с DOM деревом ужасно дорогие?
Евгений Земченков
Уважаемый Владимир, я знал) Просто не думал, что настолько дорогие)
#
Евгений Земченков  
Женя
эх, как по мне, так хром ваще дебри, причем нереально запущенные
ольга
Спасибо за замечательный пример отладки, теперь все стало понятно :)
Евгений Земченков
:D
#
Евгений Земченков  
Виталий
Очень познавательно и актуально! Спасибо.
Alexander
Я понимаю, что это дело привычки, но мне кажется, что Опера в этом плане удобнее :)
serj
спасибо за статью помогла
#
serj
A21

Assis dans sa voiture, Walt ouvre le haut pour r?v?ler un replique montres Tag Heuer Monaco. Il ne est pas s?r de ce que d'?tiqueter de celui-ci, sans doute. Jusqu'? pr?sent, le personnage de Walt a ?t? porteur de la montre un peu co?teux (mais fiable) num?rique Casio Rolex Pearlmaster Royaume-Uni. Montage montre pour son caract?re? Pas s?r pour ?tre honn?te. Mais il est assez bon pour Steve McQueen (Monaco montre de film Le Mans a vendu environ 800 000 $,), et Stephen Colbert. Je me demande o? Jesse a eu l'id?e? La montre pr?cise comp?tent pour Wally Breitling blanc dans le spectacle est le Tag Heuer Monaco Calibre 12 ref. CAW2111. FC6183
#
A21
ivrewat
This function is frequently changing DOM. In each iteration of the loop is added to the color of the label to the ID of the div container. This leads me to believe. Fake airking
#
ivrewat
Alison
I always looking for finding new information and you help me with sharing this article with everyone. Essay Writers UK
#
Alison
Jackson
Thanks for writing such a good article, I stumbled onto your blog and read a few post. I like your style of writing. best vpn netflix
#
Jackson
sunny
к тому же они показывают таймлайны. Кроме того, в Chrome есть отличный инструмент для трассировки событий, который называется rolex

Новый комментарий

как выглядит какой тег
жирный текст <b>жирный текст</b>
курсивный тект <i>курсивный тект</i>
зачеркнутый текст <s>зачеркнутый текст</s>
подчеркнутый текст <u>подчеркнутый текст</u>
ссылка <a href="адрес">ссылка</a>
function foo() { ... }
<pre><code>function foo() { ... } </code></pre>
разрешенные теги или посмотреть как будет выглядеть
как выглядит какой тег
жирный текст <b>жирный текст</b>
курсивный тект <i>курсивный тект</i>
зачеркнутый текст <s>зачеркнутый текст</s>
подчеркнутый текст <u>подчеркнутый текст</u>
ссылка <a href="адрес">ссылка</a>
function foo() { ... }
<pre><code>function foo() { ... } </code></pre>
разрешенные теги или посмотреть как будет выглядеть

metin2 pvp metin2 pvp serverler pvp serverler