Оптимизация FCP/LCP за счет кэширования поиска модулей raw-view

Я пытаюсь улучшить First Contentful Paint (FCP) и Largest Contentful Paint (LCP) с помощью двух следующих PR:

https://github.com/discourse/discourse/pull/15440

Меня очень интересует реальное влияние этих изменений — пожалуйста, протестируйте и дайте обратную связь.

И, конечно, любая помощь в тестировании, рефакторинге и повышении покрытия тестами будет более чем приветствоваться.

Первое выглядит как простая оптимизация, которая, на мой взгляд, имеет смысл. @david и @eviltrout потратили время на оптимизацию этой области, поэтому мне будет очень интересно узнать их мнение.

Второе кажется немного более хрупким в долгосрочной перспективе. Я полностью понимаю желание оптимизировать, но это немного беспокоит, так как это будет область, которую нам нужно будет поддерживать.

Привет @rrit — спасибо за PR. Первый выглядит как хорошее улучшение. Удавалось ли вам измерить влияние на производительность? Сколько времени это экономит?

Как заметил @sam, поддерживаемость второго вызывает небольшие опасения. Похоже, это копирование/вставка из исходного кода Ember? Вы что-то изменили для улучшения производительности?

патч lookupView

Теперь реализован через Map вместо Array.

Время, затрачиваемое на запуск приложения в lookupView (для экземпляра разработки):

  • без патча: 124 мс
    Firefox Profiler (стек вызовов отфильтрован по lookupView)

  • с патчем: 9 мс
    Firefox Profiler (стек вызовов отфильтрован по lookupView)

Экономия времени: ~115 мс

Это сокращает время, затрачиваемое внутри appendOutletView, с 1,083 мс до 946 мс (для экземпляра разработки).


патч для необработанных хелперов Handlebars

Да, это фактически копирование с одним изменением: используется недорогая проверка isPath.

      // заменяет @ember/-internals/utils isPath
      // @see: https://github.com/emberjs/ember.js/blob/3537670c14883346e11e841fcb71333384fcbc87/packages/%40ember/-internals/metal/lib/path_cache.ts#L5-L7
      // @see: https://github.com/emberjs/ember.js/blob/255a0dd3c7de1187f4a2f61a97cf78bfff8f66a8/packages/%40ember/-internals/glimmer/lib/utils/bindings.ts#L70
      let isPath = context.indexOf('.') > -1;

Например, renderTopicListItem в конечном итоге вызывает множество вызовов _getPath (ещё 50–100 мс экономии):
Firefox Profiler (стек вызовов отфильтрован по _getPath внутри renderTopicListItem)

Возможно, дорогие вызовы _getPath — это то, что нужно оптимизировать в Ember.js, а не в Discourse.


Также обратите внимание на Firefox Profiler, чтобы получить представление о выполнении JavaScript:

Спасибо за патчи. Второй, кажется, немного хрупкий.

Запускаются ли ваши бенчмарки в режиме разработки или в режиме продакшена? Ember имеет довольно разные профили в обоих случаях.

@david нашёл отличный способ исправить эту проблему — см. его комментарий на GitHub.

Время вызовов renderTopicListItem на странице «latest» сократилось с 348 мс до 201 мс в сборке Ember в режиме «production».

Предыдущие бенчмарки ещё работали в режиме разработки.


Как запустить бенчмарки в режиме production в Ember.js?

# Запуск Ember в режиме production
d/ember-cli server --environment="production"

К сожалению, мне не удалось воспроизвести столь значительное ускорение. В Firefox и Chrome (macOS) я не наблюдаю никакого измеримого улучшения. Chrome тратит около 23 мс на renderTopicListItem, Firefox — 30 мс. На старом устройстве Android (Pixel 3) время составляет около 108 мс. Цифры не меняются до и после внесения изменений.

Кстати, я измерял эти показатели с помощью Performance API. Я добавил performance.mark("rtli-start") в начало функции renderTopicListItem, а затем performance.measure("rtli", "rtli-start") в её конец.

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

performance.getEntriesByName("rtli").reduce((v, m) => v + m.duration, 0);

Мы определённо примем это изменение — оно явно является более удачной реализацией. Но я не уверен, что оно даст заметное улучшение производительности рендеринга :thinking:

Я всё ещё могу воспроизвести преимущества производительности, используя Performance API в приватном режиме Firefox (Linux).

Тестирование http://localhost:4200/latest
Время выполнения renderTopicListItem сократилось с ~290 мс до ~190 мс.

Мой тестовый экземпляр Discourse содержит множество тем с большим количеством ответов и множеством разных авторов — данные взяты из рабочей инстансии. Это приводит к отрисовке большого количества элементов.
Возможно, именно в этом заключается разница в наших бенчмарках?


Предварительная отрисовка контента ниже сгиба

Discourse предварительно отрисовывает 30 тем на странице «Последние». Затем контент отображается впервые (FCP). Выше сгиба видно только около 12 тем.

То же самое для страницы темы: предварительно отрисовывается 20 постов, но выше сгиба одновременно видно максимум 6 однострочных постов.

Это может стать ещё одним направлением для оптимизации FCP.

Не могли бы вы указать версию Firefox и операционной системы? Показатель в 290 мс почти в 3 раза медленнее, чем на Android-устройстве 2018 года, что немного удивительно.

Да, это может частично объяснить разницу. В моём случае я запускал их с использованием живых данных от Meta:

bin/ember-cli --environment production --proxy https://meta.discourse.org

Да, это возможное улучшение. Однако нам нужно быть очень осторожными, чтобы макет и/или прокрутка не скакали (например, если пользователь обновляет страницу, когда уже прокрутил её наполовину). Определение «ниже линии сгиба» также варьируется в зависимости от устройства, браузера и темы.

Прокси на meta.discourse.org

К сожалению, запуск ember с прокси у меня не работает:

d/ember-cli --environment production --proxy https://meta.discourse.org

http://localhost:4200/

Ошибка сборки Discourse

Ошибка [ERR_TLS_CERT_ALTNAME_INVALID]: Имя хоста/IP не соответствует альтернативным именам сертификата:
Хост: localhost. отсутствует в альтернативных именах сертификата: DNS:*.cdck-prod-meta.discourse.cloud

http://127.0.0.1:4200/

Ошибка сборки Discourse

Ошибка [ERR_TLS_CERT_ALTNAME_INVALID]: Имя хоста/IP не соответствует альтернативным именам сертификата:
Хост: meta.discourse.org. отсутствует в альтернативных именах сертификата: DNS:*.cdck-prod-meta.discourse.cloud

Система, использованная для тестов

Данные извлечены из Firefox about:support

Название Firefox
Версия 95.0.2
ID сборки 20211219102529
ID дистрибуции canonical-002
User-Agent Mozilla/5.0 (X11; Linux x86_64; rv:95.0) Gecko/20100101 Firefox/95.0
Операционная система Linux 5.10.0-0.bpo.9-amd64 #1 SMP Debian 5.10.70-1~bpo10+1 (2021-10-10)
Тема ОС Adwaita-dark / Adwaita
Исполняемый файл приложения /snap/firefox/777/usr/lib/firefox/firefox
Название Firefox Developer Edition
Версия 96.0b10
ID сборки 20211228195952
Каталог обновлений /opt/firefox-dev-autoinstall
Канал обновлений aurora
User-Agent Mozilla/5.0 (X11; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0
Операционная система Linux 5.10.0-0.bpo.9-amd64 #1 SMP Debian 5.10.70-1~bpo10+1 (2021-10-10)
Тема ОС Adwaita-dark / Adwaita
Исполняемый файл приложения /opt/firefox-dev-autoinstall/firefox-bin

Данные извлечены из Chromium chrome://system/

ВЕРСИЯ CHROME 90.0.4430.212 собран на Debian 10.9, работает на Debian 10.11
ВЕРСИЯ ОС Linux: 5.10.0-0.bpo.9-amd64

Версия ОС:

# cat /etc/os-release 
PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=debian

PR с рефакторингом теперь слит:

Спасибо, что подняли этот вопрос @rrit — это отличное улучшение!