raw-view 모듈 조회 캐싱으로 FCP/LCP 최적화

다음 두 PR를 통해 First Contentful Paint(FCP)와 Largest Contentful Paint(LCP)를 개선하고자 합니다:

이 변경 사항의 실제 영향에 대해 매우 관심이 많으니, 직접 시도해보시고 피드백을 주시면 감사하겠습니다.

물론 테스트, 리팩토링 및 테스트 커버리지 관련 도움도 언제든지 환영합니다.

첫 번째는 합리적이라고 생각되는 비교적 쉬운 최적화처럼 보입니다. @david와 @eviltrout가 이 영역의 최적화에 시간을 들였기 때문에, 두 분의 의견을 매우 궁금해합니다.

두 번째는 장기적으로 다소 취약해 보입니다. 최적화에 대한 욕구는 충분히 이해하지만, 유지보수가 필요한 영역이 될 것이므로 조금 걱정됩니다.

안녕하세요 @rrit - PR을 올려주셔서 감사합니다. 첫 번째 PR은 좋은 개선 사항으로 보입니다. 성능 영향을 측정해 보셨나요? 얼마나 시간을 절약할 수 있나요?

@sam이 말씀하신 대로 두 번째 PR의 유지보수성은 약간 우려스럽습니다. Ember 소스 코드에서 복사/붙여넣기한 것처럼 보이는데, 성능을 개선하기 위해 어떤 부분을 수정하셨나요?

lookupView-patch

이제 Array 대신 Map을 통해 구현되었습니다.

개발 인스턴스에서 lookupView에 소요되는 애플리케이션 부팅 시간:

  • 패치 적용 전: 124 ms
    Firefox Profiler (lookupView로 필터링된 콜스택)

  • 패치 적용 후: 9 ms
    Firefox Profiler (lookupView로 필터링된 콜스택)

절약된 시간: 약 115 ms

이는 개발 인스턴스에서 appendOutletView 내부에 소요되는 시간을 1.083 ms에서 946 ms로 줄여줍니다.


원시 handlebar 헬퍼에 대한 패치

네, 실제로는 한 가지 변경 사항이 있는 복사-붙여넣기입니다: isPath에 대한 저렴한(저비용) 검사를 사용합니다.

      // replaces @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 ms를 절약할 수 있음):
Firefox Profiler (renderTopicListItem 내부의 _getPath로 필터링된 콜스택)

아마도 비용이 많이 드는 _getPath 호출은 Discourse가 아니라 Ember.js에서 최적화해야 할 사항일 수 있습니다.


JavaScript 실행에 대한 내부 정보를 얻으려면 Firefox Profiler를 확인해 보세요:

패치 감사합니다. 두 번째 패치는 조금 취약해 보입니다.

벤치마크를 개발 모드에서 실행 중이신가요, 아니면 프로덕션 모드에서? Ember는 두 모드에서 프로파일이 꽤 다릅니다.

@david가 이 문제를 해결하는 훌륭한 방법을 찾았습니다 - 그의 github 댓글을 참고하세요.

Ember ‘production’ 빌드에서 ‘latest’ 웹페이지의 renderTopicListItem 호출 시간이 348ms에서 201ms로 줄었습니다.

이전 벤치마크는 여전히 개발 모드에서 실행 중이었습니다.


Ember.js 프로덕션 모드에서 벤치마크를 실행하는 방법은?

# 프로덕션 모드로 ember 시작
d/ember-cli server --environment="production"

불행히도 저는 이 큰 속도 향상을 재현하지 못했습니다. Firefox와 Chrome(macOS)에서 측정 가능한 개선 사항을 관찰하지 못했습니다. Chrome은 renderTopicListItem에 약 23ms를 소모하고, Firefox는 30ms를 소모합니다. 구형 안드로이드 기기(Pixel 3)에서는 약 108ms를 확인했습니다. 변경 사항 전후로 숫자가 변하지 않는 것 같습니다.

참고로, 저는 performance API를 사용하여 이러한 수치를 측정했습니다. renderTopicListItem의 시작 부분에 performance.mark("rtli-start")를 추가하고, 끝에 performance.measure("rtli", "rtli-start")를 추가했습니다.

그 다음 브라우저 플러그인을 비활성화하고 개발자 도구를 닫은 상태로 브라우저를 새로고침합니다(개발자 도구와 브라우저 플러그인은 렌더링 성능에 상당한 영향을 미칠 수 있습니다). 로딩이 완료된 후 개발자 도구를 열고 다음 코드를 실행하여 측정값을 합산합니다:

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

이 변경 사항은 분명히 더 나은 구현 방식이므로 반드시 병합할 것입니다. 하지만 렌더링 성능에 눈에 띄는 차이를 가져다줄지는 확실하지 않습니다 :thinking:

Firefox의 프라이빗 모드(Linux)에서 성능 API를 사용하여 여전히 성능 향상을 재현할 수 있습니다.

http://localhost:4200/latest 테스트 결과, renderTopicListItem에 소요되는 시간이 약 290ms에서 약 190ms로 감소했습니다.

제 Discourse 테스트 인스턴스에는 많은 답변과 다양한 작성자를 가진 수많은 주제가 포함되어 있으며, 이는 프로덕션 인스턴스에서 가져온 데이터입니다. 이로 인해 렌더링해야 하는 요소가 매우 많이 생성됩니다.
아마도 이것이 우리의 벤치마크 차이일까요?


접힘 이하 콘텐츠의 사전 렌더링

Discourse는 ‘latest’ 페이지에서 30개의 주제를 사전 렌더링합니다. 그런 다음 콘텐츠가 처음 표시(FCP)됩니다. 접힘 위에는 약 12개의 주제만 표시됩니다.

주제 페이지도 마찬가지입니다. 20개의 게시물이 사전 렌더링되지만, 접힘 위에는 최대 6개의 한 줄 게시물이 표시됩니다.

이것이 FCP 최적화의 또 다른 포인트가 될 수 있습니다.

Firefox와 OS 버전을 공유해 주실 수 있을까요? 290ms라는 수치는 2018년 안드로이드 기기보다 거의 3배 느린 것으로, 다소 놀라운 결과입니다.

그렇다면 일부 차이를 설명할 수 있겠네요. 제 경우에는 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 Build Error

Error [ERR_TLS_CERT_ALTNAME_INVALID]: Hostname/IP does not match certificate's altnames:
Host: localhost. is not in the cert's altnames: DNS:*.cdck-prod-meta.discourse.cloud

http://127.0.0.1:4200/

Discourse Build Error

Error [ERR_TLS_CERT_ALTNAME_INVALID]: Hostname/IP does not match certificate's altnames:
Host: meta.discourse.org. is not in the cert's altnames: DNS:*.cdck-prod-meta.discourse.cloud

벤치마크에 사용된 시스템

Firefox about:support에서 추출

이름 Firefox
버전 95.0.2
Build-ID 20211219102529
Distributions-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
Build-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/에서 추출

크롬 버전 90.0.4430.212 built on Debian 10.9, running on Debian 10.11
OS 버전 Linux: 5.10.0-0.bpo.9-amd64

OS 버전:

# 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 - 좋은 개선 사항입니다!