Введение
В этой статье мы рассмотрим, как использовать веб-воркеры для повышения производительности при запуске. Примеры будут в приложении Angular 8 — с ним проще начать работать с веб-воркерами. Однако, использование веб-воркеров не специфично для Angular, большинство этих концепций можно применить с любым Javascript или Typescript приложением.
Мы рассмотрим:
- Измерение производительности в Lighthouse.
- Начало работы с веб-воркерами в Angular 8 [1].
- Измерение производительности с веб-воркерами.
- Ограничения и подводные камни веб-воркеров.
Измерение производительности с Lighthouse
Для начала проведем базовое измерение, чтобы оценить производительность приложения при запуске без веб-воркеров. Обратите внимание, Angular приложение запускаем в режиме production— это влияет на производительность.
В Google Chrome Developer Tools ?, используя Lighthouse, измерим производительность веб-страницы при запуске [2]. Я добавил длительную задачу (вычисление) к запуску приложения (построение огромной строки в цикле for).
Когда мы выполним длительную задачу в главном потоке, приложение окажется заблокированным. Главный поток полностью блокируется вычислениями в длительной задаче, поэтому пользователь не может взаимодействовать с приложением, пока процесс не закончится.
Нажав на “View Trace” мы увидим визуализацию времени ЦП при запуске. В нашем примере большая часть времени была потрачена на оценку и выполнение скрипта (задачи). При трассировке мы можем проверить, что код запущен в главном потоке.
Начинаем работу с веб-воркерами
Angular 8 CLI облегчает работу с веб-воркерами. Чтобы создать веб-воркер, просто запустите схему Angular 8 web-worker.
Имя и расположение воркера в основном произвольны. Предостережение: если имя и папка веб-воркеров и компонента совпадут, Angular автоматически добавит последующий код к компоненту. Если нет, добавьте этот код туда, где хотите использовать воркер.
if (typeof Worker !== 'undefined') {
// Создать новый
const worker = new Worker('./web-worker.worker', { type: 'module' });
worker.onmessage = ({ data }) => {
console.log(`page got message: ${data}`);
};
worker.postMessage('hello');
} else {
// Веб-воркеры не поддерживаются в этом окружении.
// Вам нужно добавить запасной вариант, чтобы программа правильно выполнялась.
}
}
Единственный добавленный код здесь — это сам воркер. Вот сюда мы переместим наши длительные вычисления.
/// <reference lib="webworker" />
addEventListener('message', ({ data }) => {
const response = `worker response to ${data}`;
postMessage(response);
});
Главный поток → Веб-воркер → Главный поток
Когда веб-воркер запускает worker.postMessage('hello')
, содержимое слушателя событий 'message'
будет выполняться внутри воркера. Когда задача будет выполнена, postMessage(response)
будет вызван веб-воркером, иworker.onmessage(data)=> {}
будет исполняться внутри нашего компонента в главном потоке.
/// <reference lib="webworker" />
addEventListener('message', ({ data }) => {
// Вот что запускается в веб-воркере
console.log('\nData Size: ' + data);
console.time('Web Worker');
let val = 'a';
for (let k = 0; k <= 10; k++) {
val = 'a';
for (let i = 0; i <= data; i++) {
val += 'a';
}
}
console.timeEnd('Web Worker');
postMessage(val);
});
Производительность веб-воркеров
Когда мы переместим длительную задачу в веб-воркер в addEventListener('message', (data)=> { // Here });
, мы протестируем производительность снова. Различные ограничения исполнения кода в веб-воркере мы рассмотрим позже. Сейчас мы просто перемещаем код из компонента в веб-воркер.
Мы видим, что производительность приложения при запуске заметно улучшилась, так как главный поток завершает оценку JS и рендеринг компонентов на экране всего за 1,8 с.
Когда длительная задача была в главном потоке, мы были вынуждены ждать дополнительное время, чтобы задача завершилась; только после этого приложение становилось интерактивным.
Приложение остается интерактивным все время, пока скрипт или задача запущены в веб-воркере, так как задача выполняется не в главном потоке.
Выполнив “View Trace”, мы можем убедиться, что скрипт или задача выполняется в воркере, а главный поток бездействует.
Ограничения и подводные камни веб-воркеров
Невозможно передать функции веб-воркеру
Функции и методы не могут передаваться в веб-воркеры. Когда объект передается веб-воркеру, все его методы удаляются. Если функция передается веб-воркеру, появляется следующее предупреждение.
worker.postMessage(() => {return 'hello'});
Работа с DOM и window
Веб-воркеры запускаются в другом глобальном контексте, нежели window
. Манипуляции DOM не допускаются, а некоторые методы и свойства window в веб-воркерах недоступны. [3]
Запуск очень больших процессов
В целом, нет большой разницы во времени выполнения задачи в главном потоке или в веб-воркере. Если вы запускаете очень большой процесс в воркере, то он выполняется значительно медленнее, чем в главном потоке.
Если мы сильно увеличим количество итераций в нашем примере, мы увидим, что производительность заметно снижается.
Заключение
- Используйте Lighthouse в Google Chrome Developer Tools для измерения производительности приложения при запуске.
- Длительные задачи и вычисления в главном потоке блокируют пользовательский интерфейс и делают его недоступным для запросов.
- Передавайте длительные задачи и вычисления веб-воркерам для повышения производительности.
- Angular 8 CLI поможет начать работать с веб-воркерами.
- Помните об ограничениях и подводных камнях в работе с веб-воркерами.
Перевод статьи Erxk Verduin: Improve Performance with Web Workers