Оптимизация запуска
Часто упускаемый из вида аспект разработки приложений - это скорость запуска приложения. Даже если вы прикладываете много усилий к оптимизации работы приложений, именно этот этап может быть пропущен. Как долго запускается ваше приложение? Создаётся ли впечатление, что устройство зависает, пока приложение запускается? Все эти симптомы заставляют пользователя считать, что приложение сломано или что-то идёт не так. Всегда будет не лишним убедиться, что ваше приложение запускается плавно. В этой статье мы поделимся некоторыми подсказками, которые помогут вам оптимизировать запуск приложения, вне зависимости от того, пишете ли вы его с нуля или работаете над уже существующим.
Приятный запуск
Не имеет значения, какую платформу вы используете, всегда будет правильным обеспечить как можно более быструю загрузку приложения. Так как это наиболее общая проблема, мы не будем заострять на ней внимание здесь. Однако, мы обратим внимание на наибольшую проблему Web-приложений: синхронная загрузка ресурсов. Решением этой проблемы был бы максимальный переход на асинхронную загрузку ресурсов. Это означает, что инициализирующий код не должен запускаться в одном единственном обработчике событий в главном потоке процесса.
Вместо этого, вы можете разбить ваш код так, чтобы часть его обрабатывалась в Web worker, что выделит его в отдельные фоновые неблокирующие треды (например, запросы данных и их обработка). Затем, все, что должно быть выполнено в основном потоке (например, пользовательские события или рендеринг интерфейса) должно быть разбито на небольшие кусочки так, чтобы обработчик браузера выполнял небольшие куски кода итеративно, а не за один подход. Это позволит вашему приложению выглядеть отзывчивым даже во время первоначальной загрузки.
Почему так важно делать все это асинхронно? Помимо причин, перечисленных выше, подумайте о влиянии, которые оказывают зависшие приложения: пользователь не может отменить запуск, даже если он запустил приложение по ошибке. Если приложение запускается в браузере, пользователь даже не сможет закрыть вкладку. В конечном счёте это может привести даже к системным предупреждениям о "медленных скриптах" или "исчерпании памяти". А ведь было время, когда каждая вкладка не работала в отдельном процессе, как сейчас, а потому повисшая вкладка приводила к зависанию всего браузера! Вы должны не просто делать загрузку приложения "мягкой", но и давать пользователю знать о процессе загрузки: показывайте ему прогресс-бары или этапы, которые проходит приложение. Это позволит пользователю убедиться, что приложение не зависло.
Было бы желание
Если вы начинаете ваш проект с нуля, обычно очень легко начать писать код "правильно", делая все изначально асинхронным. Все вычисления при запуске должны выполняться в фоновых потоках, в то время как основной поток должен держаться максимально очищенным от лишних функций. Включайте индикатор прогресса для того, чтобы пользователь знал, что сейчас происходит и не думал, что приложение сломано. В теории, если вы только начинаете разработку приложения - это все должно быть очень просто.
С другой стороны, потребуются некоторые ухищрения, если вы пытаетесь портировать существующее десктопное приложение в Web или привыкли писать такие. Десктопные приложения обычно не нуждаются в написании кода в асинхронной манере, потому что операционная система берёт заботу об этом на себя. В исходниках такого приложения может быть лишь один поток обработки кода, но даже он может быть легко разбит на асинхронные этапы (запуском каждой новой итерации потока по отдельности). В таких приложениях запуск часто представляет собой последовательную монолитную процедуру, которая время от времени обращается к метрикам прогресса и обновляет их.
И хотя вы можете использовать Web workers, чтобы обработать очень большие и "тяжёлые" скрипты асинхронно, вы должны учитывать некоторые ограничения. Web Worker-ы не имеют доступа к некоторым API браузера: DOM, WebGL или audio, они не могут посылать синхронные сообщения в основной поток, вы даже не можете проксировать некоторые из этих API в основной поток. Это всё означает, что вы можете поместить в Web Worker-ы только "чистые функции", но вам всё равно придётся вычислять огромную часть данных в основном потоке. Поэтому очень полезно разрабатывать систему так, чтобы в ней было как можно больше чистых функций - так их будет проще делегировать в последствии.
Тем не менее, даже код в основном потоке можно сделать асинхронным, приложив лишь небольшие усилия.
Переход к асинхронности
Есть несколько предложений, как сделать процесс загрузки асинхронным:
- Используйте атрибуты
defer
илиasync
на тегах script. Это позволит HTML парсерам продолжать парсинг документа, вместо того, чтобы переключаться на скачивание / парсинг / выполнение скриптов. - Если вам нужно декодировать какой-то ресурс (например, JPEG для превращения его в текстуру в WebGL), делайте это в Worker-ах.
- Когда вы имеете дело с данными, поддерживаемыми браузером, используйте встроенные декодеры, а не пишите свои. Это касается, например, декодинга изображений / видео. Производители браузеров стараются применять лучшие техники, наиболее совместимые с их кодовой базой. Кроме того, многие браузеры поддерживают параллельное выполнение таких обработчиков.
- Любая обработка данных, которая может быть сделана параллельно должна быть сделана параллельно.
- Не подключайте скрипты и стили, которые не критичны для запуска приложения, в инициализирующий HTML. Загружайте их только тогда, когда это необходимо.
- Сокращайте размеры ваших JavaScript-файлов. Попробуйте отправлять минифицированный код и используйте сжатие, например Gzip или Brotli.
- Используйте дополнительные атрибуты (например preconnect или preload) всегда, когда есть возможность сказать браузеру, что является критическим для вашей системы.
Чем больше вещей вы можете сделать асинхронно, тем большие преимущества многоядерных процессоров вы сможете задействовать.
Проблемы портирования
Когда первоначальная загрузка завершена и начинается обработка приложения в основном потоке, вполне возможно, что ваше приложение обязано быть однопоточным, особенно если это портированная версия. Очень важно попытаться помочь процессу запуска приложения, порефакторив код, сделав его композитным, состоящим из маленьких кусочков, каждый из которых может быть обработан последовательно основным потоком. В этом случае, в промежутке между выполнением этих кусочков кода браузер сможет выделить время на обработку ввода (например, клик) или на обработку микро-задач.
В случае, если вы портируете ваше приложение, вы наверняка знаете о Emscripten. Это решение предоставляет API, которое поможет с подобным рефакторингом. Например, вы можете использовать emscripten_push_main_loop_blocker()
, чтобы определить функцию, как выполняемую после того, как основной поток разрешит продолжить работу. Создавая такие функции, создавая очередь, которая должна выполниться в определённом порядке, вы можете с лёгкостью разгрузить основной поток.
Конечно, все это не отменяет необходимости рефакторинга кода так, чтобы он работал лучше и это займёт время. Но для старта этого может оказаться достаточно.
Насколько далеко я должен зайти?
Стоит держать в голове, что браузер начинает беспокоиться о вашем скрипте в том случае, если он блокирует основной поток больше, чем на 10 секунд. В идеале, вы не должны блокировать работу страницы так долго. И пока вы держите показатели загрузки приложения ниже этих значение - все должно быть ок. Но не забывайте, что если кто-то имеет не такой мощный компьютер или мобильное устройство, как у вас, то код, выполняющийся у вас за 2 секунды, у этого пользователя может занять 10 секунд. Для этого полезно использовать CPU Throttling, который предоставляется средствами разработчиков в некоторых браузерах.
Другие предложения
Существуют другие вещи, которые могут влиять на скорость запуска приложения, и одна лишь асинхронность не спасёт от всего. Вот несколько из них:
- Время загрузки
-
Держите в голове, как много времени занимает загрузка данных вашего приложения. Если приложение действительно большое, популярное и должно обновлять данные часто, вы должны использовать как можно более быстрый хостинг (или даже несколько хостингов для разных регионов). Так же не забывайте сжимать данные.
- Влияние GPU
-
Компиляция шейдеров или загрузка текстур в GPU может занять время, особенно для по-настоящему сложных WebGL приложений. Давайте пользователю знать о том, что загрузка или обработка данных продолжается фоном, чтобы он знал, когда лучше переходить в загруженные части приложения.
- Размер данных
-
Делайте всё, чтобы избежать лишних данных. Чем меньше лишних данных запрашивается каждый раз, тем работает приложение. Используйте нормализацию данных и кеширование.
- Субъективные факторы
-
Любая вещь, которую вы предлагаете пользователю во время запуска приложения и с которой он может взаимодействовать, заставляет время течь быстрее. Это забавно, но прогресс-бар, который показывается 3 секунды ощущается более быстрым, чем пустая страница в течение 1 секунды. Добавляйте подсказки к вашему приложению, рассказывайте интересные факты или просто показывайте что-то красивое - это уже улучшит восприятие.
Смотрите также
Информация об оригинальном документе
- Автор: Alon Zakai
- Исходник: BananaBread (or any compiled codebase) Startup Experience