Angular

Введение

В этой статье мы рассмотрим, как использовать веб-воркеры для повышения производительности при запуске. Примеры будут в приложении 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]

Запуск очень больших процессов

В целом, нет большой разницы во времени выполнения задачи в главном потоке или в веб-воркере. Если вы запускаете очень большой процесс в воркере, то он выполняется значительно медленнее, чем в главном потоке.

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

Меньший объем данных: 19мс| 37мс
Больший объем данных 13мс | 39мс

Заключение

  • Используйте Lighthouse в Google Chrome Developer Tools для измерения производительности приложения при запуске.
  • Длительные задачи и вычисления в главном потоке блокируют пользовательский интерфейс и делают его недоступным для запросов.
  • Передавайте длительные задачи и вычисления веб-воркерам для повышения производительности.
  • Angular 8 CLI поможет начать работать с веб-воркерами.
  • Помните об ограничениях и подводных камнях в работе с веб-воркерами.

Перевод статьи Erxk Verduin: Improve Performance with Web Workers