Анализ веб-производительности можно разделить на три основные категории:

  1. Время загрузки страницы
  2. Производительность приложения во время выполнения (рывок, отзывчивость)
  3. Потребление памяти

Хотя производительность приложения и потребление памяти иногда смешиваются (например, сбои при сборке мусора влияют на производительность и также связаны с утечками памяти), каждая из них имеет достаточно подкатегорий, чтобы занять свое место в приведенном выше списке.

В этой статье я хотел бы сосредоточиться на аспекте второй категории - производительности во время выполнения. Для ясности, вот мое определение «производительности во время выполнения»: я определяю производительность во время выполнения как производительность визуальных элементов в приложении, когда пользователь взаимодействует с ними. Итак, если пользователь нажимает кнопку и должен подождать секунду, прежде чем что-то произойдет, это время выполнения.

Если у вас есть более подходящее название для такого выступления, обязательно дайте мне знать в комментариях ниже. Признаюсь, я не особо задумывался над названием, так как полностью понимаю, что я имею в виду в 90% случаев :)

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

К концу этой статьи вы будете знать:

  • Что делает приложение отзывчивым / не реагирующим
  • Как измерить скорость отклика вашего приложения
  • Как измерить время работы ваших функций
  • Как отслеживать и обрабатывать трудоемкие методы

Теория

Большинство наших приложений работают с циклом рендеринга 60 Гц. Это происходит потому, что на экране отображается 60 кадров в секунду (= 16,667 миллисекунд (мс) на кадр), и наше приложение ограничено этим временным интервалом. Эти 16,667 мс распределяются между нашим кодом JavaScript (JS), нашей разметкой HTML, нашими стилями CSS и их синтаксическим анализом. Все вместе получается Критический путь рендеринга (CRP).

Более длинные кадры приводят к видимым задержкам в анимации и быстродействии. Это два основных симптома низкой производительности во время выполнения:

  1. Анимация Джанк - ваша анимация движется так, как будто в ней было на три стопки слишком много текилы, и она пытается взобраться на Скалистые горы. Вот хорошая игра, демонстрирующая джанк: http://jakearchibald.github.io/jank-invaders/
    Это происходит, когда у вас кадры длиннее 16,667 мс.
  2. Отзывчивость - во время выполнения вашего JS-кода (или в любое время во время CRP) приложение зависает. Не работает не только ваш код. Пользователь не может нажимать, прокручивать, печатать и т. Д. В некоторых реализациях даже анимация GIF не перемещается. Это НЕ хорошо… Хотя это не всегда так заметно, как дрожание анимации, обычно вы не хотите, чтобы ваш пользователь ждал более 100 мс, чтобы что-то произошло (см. Http://theixdlibrary.com/pdf/Miller1968.pdf И, конечно же, высший источник истины - гугл ).

Подводя итог вышесказанному: анимациям нужен наш код и стиль, чтобы завершить рендеринг менее чем за 16,677 мс. Что касается отзывчивости, нам бы хотелось, чтобы время реакции нашего приложения на ввод пользователя не превышало (а желательно намного короче) 100 мс.

Как мы можем определить, ЕСТЬ ли проблема? Как мы можем определить, в чем проблема? А если есть проблема, как ее исправить, чтобы наше приложение реагировало на ввод пользователя менее чем за 100 мс? Все это и многое другое в ближайшее время!

«Живой» вариант использования

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

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

Теперь - при условии, что у нас есть веская причина (клиент жалуется на отсутствие реакции, наш босс хочет, чтобы мы проверили определенный сценарий, который, по его мнению, может быть проблематичным, или вы понимаете, что ваше приложение может получать больше данных в будущем (масштабирование?)) - мы можно перейти к поиску проблемы.

Билет

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

При более тщательной проверке вы обнаруживаете, что у позвонившего клиента более 800 результатов поиска возвращаются с сервера. В вашем наборе данных тестирования было около 50. Это означает, что у этого клиента результатов поиска на 750 больше, чем у вас, и каждый борется за участок недвижимости в дереве DOM.

Поскольку вы так хорошо знакомы с вашим кодом и можете складывать 1 и 1, вы полагаете, что проблема должна быть где-то в обработке результатов поиска.

Но как найти часть кода, которая вызывает проблему?

DevTools спешат на помощь!

Как упоминалось в разделе Теория, мы говорим о частоте кадров - это означает, что мы хотели бы узнать, сколько времени требуется для рендеринга кадров. К счастью, в Chrome DevTools есть инструмент повышения производительности, который делает именно это - он сообщает нам, сколько времени требуется для рендеринга каждого кадра. Он делает даже больше - он сообщает нам, какие функции выполняются в каждом кадре и сколько каждая функция нам стоит!

Взволнованный моей новой игрушкой, я начинаю процесс мониторинга:

  1. Загрузите мое приложение в Chrome
  2. Подготовьте панель поиска, чтобы она была видимой
  3. Загрузите DevTools (есть много способов сделать это - F12; щелкните правой кнопкой мыши - ›проверить элемент и т. Д.)
  4. Перейдите на вкладку "Производительность".
  5. Начать запись
  6. Поиск с помощью супер умной панели поиска
  7. Подождите, пока вернутся результаты
  8. Очистить супер умную панель поиска
  9. Искать снова
  10. Подождите, пока вернутся результаты
  11. Очистить супер умную панель поиска
  12. Остановить запись

… А когда оседает пыль, у меня получается что-то вроде этого:

Хотя в этом случае легко определить, когда я сам выполнял поиск (цветные части на верхней панели), существует множество способов узнать, что произошло в данный момент времени. Chrome сообщает нам, где он обнаруживает проблемы с производительностью, и отмечает их красным цветом. Давайте сосредоточимся на одной такой части:

Здесь я выделил проблемный временной диапазон на диаграмме вверху. С высоты птичьего полета видно, что процесс занял более 100 мс - 114,9 мс, если быть точным. И это было примерно от 50 до 100 результатов (в зависимости от моего запроса). Если у пользователей клиента их 800, загрузка может занять более одной секунды. Если машина конечного пользователя немного медленная, фоновые процессы потребляют ресурсы и т. Д., Это может занять еще больше времени. Это не хорошо. Можем ли мы это улучшить? Посмотрим.

Щелчок по длинной рамке позволяет нам увидеть, что произошло в его CRP. После щелчка я выбираю вкладку Дерево вызовов на нижней панели и вижу следующее изображение:

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

При детализации мы обнаруживаем, что, как и ожидалось, performSearch требует больше всего времени для выполнения, но, хотя его общее время составляет 106,8 мс, его собственное время составляет 0 мс. Это означает, что один или несколько его «дочерних» методов неисправны, поэтому мы продолжаем углубляться, пока не дойдем до вилки.

По мере углубления мы переходим к методам removeLoadingbar и appendSearchResults. Это два последних метода в цепочке, которые являются «моим кодом». Взглянув на removeLoadingBar, мы получаем следующую картину:

Поскольку наш код является виджетом (который содержит панель поиска), мы видим, что для запуска стороннего метода (в файле maketutorial_lib.js) требуется 50,5 мс. Что делает этот метод?

Я не могу показать вам код, но вот что происходит: removeLoadingbar вызывает метод, который в конечном итоге вызывает этот метод reinitialize. Глядя на код, кажется, что reinitialize - это вызов сторонней библиотеки, которая устанавливает панель прокрутки. Он вызывается почти при каждом изменении области результатов поиска виджетов. Это означает, что этот метод на самом деле не занимает много времени - он просто вызывается слишком много раз ...

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

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

Готовы к результатам?

Наш метод removeLoadingbar теперь занимает всего 0,5 мс, потому что его вызовы для повторной инициализации отклоняются. Более того, вызовы внутри appendSearchResults также обрабатываются, поэтому в конечном итоге мы получаем только один вызов метода, что сокращает общее время appendResults до 25 мс, а общее событие поиска - до 60,6 мс. Это намного ниже нашей цели в 100 мс!

Вывод

Подведем итоги тому, что мы узнали:

  • Мы увидели, что отзывчивость приложения можно измерить, посмотрев на время кадра.
  • Мы измерили время кадра с помощью DevTools Chrome, записав «подозрительное» событие.
  • Мы исследовали события, изучив затраты времени на выполнение наших функций.
  • В конце концов мы нашли бесполезный код.
  • Мы улучшили код, используя технику debounce

ПРИМЕЧАНИЕ: при использовании метода противодействия не забывайте, что код теперь является асинхронным, потому что противодействие работает как тайм-аут. Если у вас есть код, который полагается на то, что ваша дебаунтированная функция возвращает немедленные результаты, вы можете компенсировать дебаунс, добавив к функции параметр «force»; параметр force заставляет функцию немедленно возвращать значение.

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