Время загрузки веб-проекта часто играет решающую роль. Если сайт грузится слишком долго, поисковики будут понижать его в выдаче, а пользователи закрывать, не дождавшись. Мы перевели рассказ разработчика Lucidchart, в котором он описывает, как его команда снизила время загрузки веб-приложения до двух секунд. Хотите так же? Читайте дальше!
Рассказывает Бен Дилтс, разработчик из Lucidchart
Недавно моей команде поручили встроить ограниченную версию разрабатываемого нами приложения Lucidchart в другое приложение. Т.к. в этом приложении Lucidchart представлял только небольшую часть функциональности, видимой пользователю, мы не хотели сильно увеличить общее время загрузки.
Общий лимит времени для загрузки приложения и отображения пользовательского контента составлял 2 секунды. Это примерно половина медианного времени загрузки нашего полноценного приложения.
Примерно месяц спустя мы достигли своей цели. И вот как мы это сделали.
Надёжно замеряем прогресс
Время загрузки сложного веб-приложения зависит от головокружительного множества факторов за пределами ресурсов, отправляемых в браузер. Эти факторы включают:
- пропускную способность сети;
- латентность сети и её надежность;
- состояние кэша браузера;
- доступную мощность процессора.
Чтобы удостовериться в том, насколько хорошо сработала определённая попытка оптимизации, нам нужно было уменьшить влияние этих факторов на замеряемую производительность. С этой целью мы определили процесс бенчмаркинга следующим образом:
- Тестирование проводится на стандартном Linux-ноутбуке для разработки Lenovo P50, i7, 32GB RAM, SSD.
- Тестирование должно проводиться в только что запущенном Chrome.
- В инструментах разработчика Chrome:
- Отключить кэш браузера.
- Установить пропускную способность сети на уровень «Fast 3G».
- Замедлить процессор в 4 раза.
Уменьшение пропускной способности сети и замедление процессора делает результаты тестов соизмеримыми с реальной жизнью, а также сопоставимыми друг с другом. Каждый раз, когда мы хотели замерить производительность, мы выполняли весь процесс (закрывали Chrome, открывали его, разбирались с инструментами разработчика, загружали страницу) по три раза, чтобы убедиться, что в процессе замера не было никаких аномалий.
Чтобы достичь загрузки за 2 секунды на полной скорости, мы поставили цель в 9 секунд при замедленном процессоре, пропускной способности и отключенном кэше. После первых замеров результат был около 13 секунд.
Оптимизация времени загрузки веб-продукта, безусловно, важна. Однако одной скоростью сыт не будешь. Предлагаем посмотреть на наши советы по созданию качественного веб-продукта.
Убираем лишнее
Основным инструментом, который мы использовали для анализа производительности загрузки, была вкладка «Perfomance» инструментов разработчика Chrome, которая подробно показывает как сетевую активность, так и активность процессора на одном графике:
Первое, что мы здесь заметили, это то, что мы тратили около 6,5 секунд из нашего 9-секундного лимита просто на загрузку приложения. Большую часть этих ресурсов потреблял JavaScript.
Сначала мы решили уменьшить общий вес кода, убрав зависимости, которые нам были не нужны. Например, мы знали, что в приложении не пригодятся наши старые UI на первом Angular, поэтому мы спокойно убрали весь Angular 1 из зависимостей. В конечном итоге мы уменьшили зависимости наполовину и сократили вес на 150 КБ в сжатом виде.
Затем мы взялись за код самого приложения. Мы использовали отличный проект source-map-explorer для изучения наших скомпилированных JavaScript-пакетов. Этот инструмент даёт визуальное представление того, какой конечный вес (не сжатый) имеет код из каждого файла и директории.
Изучив код, мы определили ряд элементов интерфейса, которые не понадобятся в приложении, и переделали наши TypeScript-модули так, чтобы многие из них не требовались конкретно в этой сборке. Постепенно мы убрали сотни килобайт ненужного кода.
Мы также выяснили, что довольно много нашего несжатого кода исходит от индекса поиска всех фигур Lucidchart. Так как нам не нужен этот индекс, пока пользователь не начнёт искать фигуры, мы поместили его в отдельный ресурс, который мы могли бы загрузить как раз в момент начала поиска.
Brotli спешит на помощь
После всех этих действий вес кода существенно уменьшился, и те 6,5 секунд на загрузку приложения превратились в 5,1 секунды. Но чтобы достичь поставленной цели, нам нужно было ещё немного улучшить наш результат. При этом у нас больше не осталось большого количества кода, который можно было бы просто опустить или отложить его выполнение.
Джеймс Джадд предложил нам использовать brotli вместо gzip для сжатия наших статических ресурсов, так как почти все поддерживаемые нами браузеры умеют работать с brotli.
Brotli — формат сжатия без потерь с открытым исходным кодом, созданный Google. Он довольно медленно сжимает, но распаковывает со скоростью, сравнимой с gzip. Вы никогда не будете использовать brotli для работы с динамически генерируемым контентом, но зато он отлично подойдёт для работы с JavaScript и CSS-ресурсами.
В результате использования brotli вместо gzip общий размер ресурсов уменьшился на 15–20 % и время загрузки снизилось до 4,5 секунд, оставляя нам 4,5 секунды для парсинга/компиляции/выполнения скриптов, загрузки и отображения пользовательского контента.
Железо не должно сидеть без дела
Есть два основных ресурса, которые вы ждёте при загрузке приложения: сеть и процессор. После основательной работы по снижению общего веса загрузки, наш профиль времени загрузки выглядел так:
После загрузки основных данных мы тратили около 1,1 секунды на парсинг и выполнение скрипта и ещё 1,8 секунды на вызов конструкторов основных классов приложения. Затем процессор в основном простаивал примерно 1,3 секунды, прежде чем окончательно загрузить и отобразить пользовательский документ.
Оказалось, одной из первых вещей, которые классы делали после инициализации, была проверка того, какие библиотеки фигур были нужны для отображения текущего документа, и загрузка кода для этих фигур. Примерно в то же время начинается загрузка директории с доступными шрифтами, а также конкретных шрифтов, используемых в документе.
В ожидании загрузки этих ресурсов процессор просто ничего делал, так как у приложения не было достаточно информации для выполнения своей работы. В качестве улучшения мы дали приложению возможность определять, какие из этих ресурсов загружать, что позволило сети работать, пока мы инициализируем остальную часть приложения на процессоре.
Тем не менее всё ещё оставался существенный простой, т.к. раз уж теперь мы одновременно загружали библиотеки шрифтов и фигур, на это требовалось больше времени, чем обычно. Мы решили, что можем встроить какую-то информацию о шрифтах в основное приложение и использовать браузерное хранилище вроде IndexedDB, чтобы нам лишь изредка приходилось загружать какую-либо информацию о шрифтах.
В итоге нам удалось сократить время простоя процессора с 1,3 секунды до 0,2 секунды.
Всё подождёт
К этому времени мы приближались к нашей главной цели. Но нам всё ещё нужно было что-то сделать, а каких-то больших и очевидных целей в профиле производительности уже не было. Поэтому мы начали смотреть на цели поменьше.
Мы рассмотрели каждый сетевой запрос, исходящий во время инициализации приложения, и решили, что почти все из них могут подождать. Загрузка крошечного API YouTube только на случай, если пользователь решит встроить в диаграмму видео на YouTube? Подождём до тех пор, пока не проверим, что присутствует видео с YouTube. Проверка списка каналов в Slack в случае, если они интегрированы с Slack для обмена диаграммами? Подождём, пока они откроют диалог обмена. Загрузка словаря переносов? Подождём, пока пользователь включит расстановку переносов. Подписка на канал обсуждения диаграммы? Это может подождать несколько секунд, пока документ не будет интерактивным.
Затем мы стали думать, какую работу процессора можно отложить. Инициализация элементов пользовательского интерфейса вроде некоторых диалоговых окон могла подождать до тех пор, пока они не станут видны. Создание экземпляров классов, которые предоставляют данные элементам интерфейса (например, должна ли кнопка быть активирована), можно отложить до первого обращения к ним.
Успех
В конце концов, нам удалось снизить время загрузки до нашей 9-секундной цели.
Когда мы отключили всё замедление и запустили приложение на полной мощности, результаты наших усилий были впечатляющими. Мы без проблем достигли цели в 2 секунды загрузки. И знаете, что самое приятное? Многие из этих достижений было просто применить к нашему полноценному редактору Lucidchart, что снизило среднее время загрузки на lucidchart.com более чем на 10 %.
TL;DR
Уменьшение времени загрузки сложных веб-приложений часто требует улучшений в нескольких разных областях, и тщательные замеры могут помочь вам понять, что вы на верном пути. Что следует взять на заметку:
- Улучшайте точность ваших замеров, проводя их на распространённом оборудовании с уменьшенной пропускной способностью сети и процессором.
- Уменьшение количества кода чрезвычайно важно. Проверьте, можно ли что-нибудь сделать с вашим кодом и зависимостями.
- Сжатие с помощью brotli может помочь сильно уменьшить объёмы загрузок для таких статических ресурсов, как код.
- Выполняйте сетевые запросы как можно раньше и как можно реже, чтобы снизить время простоя процессора.
- Откладывайте как можно больше работы, пока приложение не будет загружено и не станет интерактивным.
Удачи на полях оптимизации!
Перевод статьи «The Critical Path: Optimizing Load Times With the Chrome DevTools»
Никита Прияцелюк, последний центурион
Добавить комментарий