본문 바로가기

FrontEnd

프론트엔드 성능 최적화 가이드 2장 스터디

반응형

프론트엔드 성능 최적화 가이드 2장을 기반으로 진행한 스터디 입니다.

 

프론트엔드 성능 최적화 가이드

웹이 가벼워지면 사용자의 만족도는 올라간다 웹사이트를 방문했는데 페이지가 늦게 로드되면 금세 ‘뒤로 가기’ 버튼에 손이 갑니다. 구글의 연구에 따르면 페이지 표시 시간이 1초에서 3초

blog.insightbook.co.kr

올림픽 통계 서비스 최적화

이전 시리즈 보기

2022.11.15 - [분류 전체보기] - 프론트엔드 성능 최적화 가이드 1장 스터디

 

프론트엔드 성능 최적화 가이드 1장 스터디

해당 도서의 1장을 기반으로 진행한 스터디 입니다. 프론트엔드 성능 최적화 가이드 웹이 가벼워지면 사용자의 만족도는 올라간다 웹사이트를 방문했는데 페이지가 늦게 로드되면 금세 ‘뒤로

itchallenger.tistory.com


이 장에서 학습할 최적화 기법

  • CSS 애니메이션 최적화
  • 컴포넌트 지연 로딩
  • 컴포넌트 사전 로딩
  • 이미지 사전 로딩

애니메이션 최적화

애니메이션의 버벅임을 해결하기 위해 

  • 브라우저의 렌더링 과정을 학습하고
  • 이를 바탕으로 해결책을 찾아 적용해봄

컴포넌트 지연 로딩

페이지 대신 컴포넌트 단위로 레이지 로딩을 적용함

컴포넌트 사전 로딩

  • 지연 로딩을 적용하면 필요할 때에만 리소스를 다운로드 가능
    • 이외의 리소스를 빠르게 처리에 화면에 보여줄 수 있음 (첫 화면 빠르게)
  • 지연로딩의 단점
    • 분할된 컴포넌트를 사용하려 할 때 다운로드되어 있지 않은 코드를 추가 다운로드하는 시간만큼 서비스 이용에 지연이 발생함
  • 절충안
    • 첫 화면 진입 시 다운로드 하지 않음
    • 필요한 시점 보다는 먼저 코드를 로드함

이미지 사전 로딩

컴포넌트 사전 로딩과 마찬가지로 필요한 시점보다는 먼저 다운로드, 처음 로딩 시에는 다운로드하지 않음

실습 대상

이미지 로딩이 느려서 가져오기 전까지 모달에 이미지가 안보임 

> Cumulative Layout Shift의 원인

  • 이미지의 경우
    • 이미지 자체가 로딩이 오래 걸려 사용자에게 늦게 보일 수 있으며
    • 이미지 자체가 더 중요한 리소스 로딩을 방해할 수도 있음
  • 외부 라이브러리를 사용할 경우
    • 해당 라이브러리 사이즈 만큼 최종 번들링된 자바스크립트 사이즈도 커짐
    • 서비스 자바스크립트 로드 시간이 증가함
    • 코드 스플리팅의 기회

막대 길이 변경 애니메이션이 뚝뚝 끊김.

만약 직접 실행해보는 경우 해당 영역을 클릭하면 데이터가 변경됨

애니메이션 최적화

문제의 애니매이션 찾기

퍼포먼스 패널의 CPU 설정에 쓰로틀링을 적용하면 끊김(jank)현상 발생

애니메이션의 원리

  • 여러 장의 이미지를 빠르게 전환하여 우리 눈에 잔상을 남기고, 그로 인해 연속된 이미지가 움직이는 것처럼 느껴지게 하는 것

이 이미지 중에 하나가 빠지면 버벅이는 느낌을 받게 됨

막대 그래프 애니메이션 쟁크 현상 발생 원인은 브라우저가 60FPS를 확보하지 못했기 때문.

버벅임을 방지하려면 최소 60FPS(Frame Per Second)를 확보해야 함

CPU가 다른일을 바빠서 초당 60프레임을 그리지 못했기 때문

왜 못했을까?

브라우저 렌더링 과정

브라우저는 아래와 같은 단계를 거처 화면을 그림

이러한 과정을 주요 렌더링 경로(Critical Rendering Path) 또는 픽셀 파이프라인(Pixel Pipeline)이라고 함.

DOM + CSSOM

  • 파서 차단 리소스 : JS
  • 렌더 차단 리소스 : CSS

먼저 브라우저는 HTML과 CSS등 화면을 그리는데 필요한 리소스를 다운로드 함(HTML 파싱하면서)

다운로드한 HTML은 브라우저가 이해할 수 있는 형태로 변화하는 파싱(parsing)단계를 거침

이 단계를 통해 요소 간 관계가 트리 구조로 표현되어 있은 DOM(Document Object Model)을 만든다.

의문점 : 왜 DomContentLoaded 전에 FCP?

https://stackoverflow.com/questions/34289535/why-first-paint-is-happening-before-domcontentloaded

 

Why First Paint is happening before DOMContentLoaded

I'm diving into performance tools that are shipped with Google Chrome as I'm trying to get my head around performance improvements techniques. I'm playing around with Timeline tab, and I found that...

stackoverflow.com

CSS도 비슷한 단계를 거처 브라우저가 이해할 수 있는 CSSOM(CSS Object Model)이라는 트리 구조가 됨

CSSOM은 각 요소가 어떤 스타일을 포함하고 있는지에 대한 정보를 포함함

렌더 트리

DOM과 CSSOM을 이용해 브라우저는 렌더링을 위한 render tree를 만듬

화면에 표시되는 각 요소의 레이아웃과 스타일 계산에 사용

  • display:none은 포함하지 않음
  • opacity:0과 visibility: hidden은 포함
    • 사용자 눈에는 보이지 않지만 요소 자체가 없어진 건 아니기 때문

레이아웃

말 그대로 화면의 레이아웃을 잡는다.

렌더 트리가 완성되면 레이아웃 단계로 넘어감

  • 화면 구성 요소의 위치나 크기를 계산하고 해당 위치에 요소를 배치함
    • 위치
    • 크기
    • 배치(z차원)

페인트

레이아웃 단계에서 페이지에서의 위치와 크기를 잡아두었으니 색을 입힘

페인트 단계에서는 화면에 배치된 요소에 색을 채워넣는 작업을 함

  • 배경색 변경
  • 글자 색 변경
  • 테두리 색 변경

브라우저는 효율적인 페인트 과정을 위해 컴포넌트를 여러 개의 레이어(layer)로 나눠서 작업하기도 함.

컴포지트

마지막 컴포지트 단계는 각 레이어를 합성하는 단계

브라우저는 화면을 그릴 때 여러 개의 레이어로 화면을 쪼갬 (높이 정보 존재)

마지막에 레이어를 하나로 합성(겹쳐서 위에서 봤을 때 2d로 만든다)

 

지금까지의 모든 과정은 아무 사이트에서나 확인 가능

Perfermance 패널에서 아무곳이나 검사해보고 메인 쓰레드의 작업을 확인해보자

Parse HTML, Layout, Paint 과정을 확인 가능

차트를 보다 보면 회색 세로 점선을 볼 수 있음

이 것이 바로 화면 갱신 주기임

브라우저는 최대 1초에 화면을 60번 그림

화면을 그리는 시점이 바로 그림 2-18에서 점선으로 된 시점임

만약 화면이 전부 그려진 후, 일부 요소의 스타일을 변경, 추가, 제거한다면

주요 렌더링 경로에서 거친 과정을 다시 한번 실행하며 새로운 화면을 그림

이것을 리플로우(Reflow)또는 리페인트(Repaint)라고 함

(하지만 브라우저는 레이아웃, 페인트를 건너뛸 기회를 얻을 수 있음. 이것이 최적화!)

리플로우와 리페인트

만약 JS로 어떤 요소의 너비와 높이를 변경하면?

  • CSSOM이 변경되어 새로운 렌더트리를 만든다 (렌더 트리)
  • 요소의 차원이 변경되었으니 레이아웃 단계에서 요소의 크기와 위치를 다시 고려한다 (레이아웃)
  • 변경된 구성에 맞게 다시 색을 칠한다 (페인트)
  • 분할된 레이어를 다시 하나로 합성한다 (컴포지트)

이 같이 레이아웃부터 다시 작업하는 것을 리플로우라 한다.

리플로우와 리페인트를 발생시키는 속성

  • 리플로우
    • 대부분의 차원을 변경시키는 속성들
      • position
      • display
      • width
      • float
      • height
      • font-family
      • top
      • left
      • font-size
      • font-weight
      • line-height
      • min-height
      • margin
      • padding
      • border 등
  • 리페인트
    • 차원은 그대로고 색, 배경 같은 경우
      • background
      • background-image
      • background-position
      • border-radius
      • border-style
      • box-shadow
      • color
      • line-style
      • outline 등

만약 JS로 어떤 요소의 너비와 높이 대신 글자 색이나 배경 색 등을 변경한다면?

브라우저는 레이아웃을 건너 뛸 수 있다는 것을 알고 있다.

 

이 같이 레이아웃을 건너뛰고 페인트만 다시 하는 것을 리페인트라 한다.

하지만 리플로우도 리소스를 꽤 먹기 때문에 피할 수 있으면 피하는 것이 좋다.

사실 기회가 별로 없는 것으로 알고 있지만 몇가지 사용 가능한 사례가 존재한다.

  • transform
  • opacity

위와 같은 속성을 사용하면 해당 요소를 별도로 분리하고 작업을 GPU에 위임하여 레이아웃 단계와 페인트 단계를 건너뛸 수 있다.

이와 같이 GPU의 도움으로 렌더링 과정을 최적화하는 것을 하드위어 가속(GPU 가속)이라고 한다

해당 부분에 해대 좀 더 자세한 내용 살펴보기

 

성능 최적화

애플리케이션 성능 최적화는 앱과 웹에서 모두 중요하다. 최근 웹 애플리케이션은 Ajax 통신, 복잡한 UI 등 많은 기능을 담으면서 크고 무거워졌다. 무거워진 웹은 긴 로딩 시간 함께 사용자 경험

ui.toast.com

하드웨어 가속(GPU 가속)

하드웨어 가속은 CPU에서 처리해야 할 작업을 GPU에 위임하여 더욱 효율적으로 처리하는 방법을 말합니다.

GPU는 애초에 그래픽 작업을 처리하기 위해 만들어진 것이므로 화면을 그릴 때 활용하면 굉장히 빠릅니다.

  • 특정 요소에 하드웨어 가속을 사용하려면 요소를 별도의 레이어로 분리해야 합니다.
    • transform 속성과 opacity 속성이 요소를 별도의 레이어로 분리하는 역할을 합니다.
  • 분리된 레이어는 GPU에 의해 처리되어 레이아웃과 페인트 단계 없이 화면 상의 요소의 스타일을 변경할 수 있습니다.
    • width, height, color와 같은 속성보다 성능이 좋을 수 밖에 없습니다.

주의사항

  • transform:translate()는 처음부터 레이어를 분리하지 않고 변화가 일어나는 순간 레이어를 분리
    • 레이어가 많으면 메모리를 많이 먹기 때문임
    • 메모리가 걱정될 경우 사용, 하지만 성능 면에서 느릴 수 있음
  • transform:translate3d() , scale3d()와 같은 3d 속성은 미리 레이어를 분리해둠
    • will-change도 사용 가능
    • 변화에 빨리 대응하지만 메모리를 그만큼 많이 사용

막대 애니메이션으로 돌아가서

CPU를 6x slowdown으로 쓰로틀링후 막대 그래프 애니메이션 기록해보기

레이아웃, 페인트, 컴포지트가 일어나는것이 보임

하지만 리플로우 작업(Recalculate style부터 paint까지)이 회색 점을 넘어가고 있음

즉, 프레임이 누락되었음을 알 수 있음

  • 화면을 1/60초 안에 보여줘야 하는데 리플로우가 발생하여 모든 단계를 다시 밟느라 필요한 화면을 제때 그려내지 못함

리플로우와 리페인트가 일어나는 속성이 아닌

GPU를 활용하여 레이아웃 단계와 페인트 단계를 건너뛸 수 있는 transform 같은 속성을 사용하는 것입니다.

애니메이션 최적화 실습

width 속성을 transform 속성으로 대체하기

실습 내용 : 

transform-origin 속성을 빼먹어서 추가함 (center left)

애니메이션 최적화 전후 비교

캡처한 작업이 프레임 드롭을 발생시키진 않았지만, 레이아웃과 페인트 작업을 하면서 꽤나 아슬아슬하게 작업을 마무리 하였습니다.

조금만 CPU가 나빴거나 성능이 좋지 않은 CPU였다면 분명 렌더링 작업을 제때 마무리하지 못했을 것입니다.

  • 프레임 드롭이란 성능 문제로 일부 프레임이 제때 보이지 않고 생략되는 현상을 말합니다.

최적화 이후 메인 쓰레드가 확실히 여유롭고 작업도 적게 하는 것을 볼 수 있습니다.

레이아웃과 페인트가 생략되었기 때문입니다.

아래 실습을 수행할 때 Disabled cache 옵션을 체크 해제했는지(캐시를 사용하는지) 확인할것
새로고침 할 때 우클릭 하면 캐시비우기 및 강력 새로고침을 테스트할 수 있음

컴포넌트 지연 로딩

1장에서와 같이 어떤 서비스에 문제가 있는지 식별

동일하게 cra-bundle-analyer 사용


npx cra-bun dle-analyzer

실행 후 모듈 크기 분석

  • 리액트 모듈은 어쩔 수 없음
  • react-image-gallary가 많은 비율을 차지
    • 26kb정도로 큰 용량은 아님
  • react-image-gallay는 모달 내부에서 사용
    • 즉시 로딩할 필요는 없음

위 분석 결과를 토대로 모달을 지연 로딩함

  • 모달 컴포넌트가 모듈을 포함하면서 자기 자신도 즉시 필요한게 아니므로, 모달을 통째로 지연로딩

실습 내용 : 

https://github.com/i0boy/frontend-performance-optimization-2/commit/20a6bf6468591cdd96ddd9df096943ec78fb5643

 

feature : 모달 레이지 로딩 / todo : 컴포넌트 프리로딩 · i0boy/frontend-performance-optimization-2@20a6bf6

Show file tree Showing 2 changed files with 43 additions and 28 deletions.

github.com

분할 후 아래와 같이 모달을 열면 모듈을 가져오는 것을 할 수 있음
(컴포넌트 번들과 외부 모듈 번들)

컴포넌트와 react-image-gallery에서 사용하는 모듈들이 한 덩어리로 분리됨.

기대한 라이브러리 용량보다 더 많은 용량을 분리할 수 있었음

파란색 부분은 모듈 번들, 초록색 부분은 컴포넌트 번들

 

컴포넌트 사전 로딩

지연 로딩의 단점

  • 초기 로딩은 빨라짐
  • 하지만 모달이 뜨기 위해선 로딩이 반드시 필요
    • 약간의 지연이 발생할 수 있음

지연 로딩을 적용하면 인터랙션 이후 코드 다운로드 및 해석이 시작된다. 이 때문에 사용자가 모달을 보는 속도가 늦어질 수 있음

사용자가 보는 화면. 코드 다운로드 / 실행 후 화면이 보이는 모습을 볼 수 있다.

사전로딩을 적용하면 코드를 로딩하는 시점을 좀 더 앞당길 수 있다.

이 방법의 문제점은 언제 코드를 미리로딩할 지 애매하다는 것이다.

고려할 수 있는 시점

  • 버튼 위로 마우스를 올렸을 때(mouseenter)
    • 가장 사용에 근접할 때까지 리소스 로딩을 지연
  • 최초에 페이지가 로드되고 모든 컴포넌트의 마운트가 끝났을 때
    • 모듈 크기가 생각보다 크면 브라우저가 여유 있을 때 받아두기

실습(프리로딩 불가능) : https://github.com/i0boy/frontend-performance-optimization-2/commit/896581a263838276925b38aa17389bf07d8895c6

 

모듈 로딩 시 프리로드는 동작하지 않는 것을 확인. DCL 이벤트 이후에 실행될 것이라 생각했으나 아니었음.

DCL 이벤트 리스너에서 프리로딩을 수행하면 가능할 듯.

그냥 모듈을 로딩하면 DCL 이전에 태스크 큐를 비우면서 스크립트 파싱을 동기적으로 실행하는듯 하다.

브라우저가 다른 쓰레드 풀에 위임할 수 있는 작업이 있는게 아니면 동기적으로 전부 처리한 후 DCL 이벤트를 발생시키는듯

 

해당 코드 제외 커밋 : 

https://github.com/i0boy/frontend-performance-optimization-2/commit/1db793b42f7a6ef4c16b8cae21814d6076dfdf19

 

아래 표틀 보면 두 가지 사항을 확인할 수 있다.

  • 메인 함수를 수행하면서 비동기 요청 수행
  • DCL 이후 스크립트 해석
    • 초기 자바스크립트 실행 시 주의할 점은 HTML 파싱을 막거나 CSS를 건드려서 렌더링을 막는 행위
  • 여러번 같은 모듈 임포트해도 캐시에서 가져옴

0과 1 모듈은 모달 컴포넌트 / 모달 의존성 모듈

이미지 사전 로딩

느린 이미지 로딩

느린 이미지 로딩은 깨진 레이아웃을 발생시킴

따라서 사용하기 전에 미리 가져오는 전략을 동일하게 사용 가능

  • 버튼 위로 마우스를 올렸을 때(mouseenter)
    • 가장 사용에 근접할 때까지 리소스 로딩을 지연
  • 최초에 페이지가 로드되고 모든 컴포넌트의 마운트가 끝났을 때
    • 모듈 크기가 생각보다 크면 브라우저가 여유 있을 때 받아두기

실습 내용 : https://github.com/i0boy/frontend-performance-optimization-2/commit/d5d60fd4e3e9b55bc84cdedab55519247ec2b505

 

이미지 지연 로딩은 자바스크립트의 Image 객체를 이용함

아래와 같이 간단한 코드를 사용하기만 해도 지연 로딩함.

그런데 객체가 하나이면 나중에 세팅된 src 이미지만 가져옴

 

 

사전 로드한 이미지의 Size 항목에 disk cache라고 적혀있고, 다운로드 시간이 매우 짧은 것을 볼 수 있음

주의 사항 :

  • 가장 중요하다고 판단되는 컴포넌트 별 대표 이미지 정도만 로딩하기
    • 대역폭을 고려할 것.
    • 이미지가 더 중요한 리소스의 로딩을 늦출 수 있음
    • 꼭 섬네일도 프리로딩 해야 할까?

참고

https://www.ascentkorea.com/core-web-vitals/

 

코어 웹 바이탈(Core web Vitals)이란 무엇인가? [2021년 5월에 시작된 구글 SERP의 변화를 이해하자]

코어 웹 바이탈(Core web Vitals)이란 무엇인가? [2021년 5월에 시작된 구글 SERP의 변화를 이해하자] 에 관한 어센트 코리아 의 블로그 글입니다. 코어 웹 바이탈(Core web Vitals)이란 무엇인가? [2021년 5월

www.ascentkorea.com

 

반응형