관리 메뉴

Dev Blog

2장 올림픽 통계 서비스 최적화 본문

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

2장 올림픽 통계 서비스 최적화

Nomad Kim 2023. 3. 20. 23:25

Repository

학습할 최적화 기법

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

애니메이션 최적화

문제의 애니메이션 찾기

 

 

설문 항목을 클릭하면 해당 응답에 대해 필터링되고 막대 그래프의 배경 색과 막대의 가로 길이가 변합니다.

그런데 이 애니메이션을 계속 보다 보면 약간 이상하게 느껴집니다. 막대가 늘어날 때 부드럽게 늘어나지 않고 어딘가 살짝 끊기는 듯한 느낌이 있습니다. 이런 끊김 현상을 쟁크(jank) 라고 합니다.

 

느끼지 못한다면, 개발자 도구를 열고 Performance 패널의 CPU 설정을 6x slowdown으로 설정하면 애니메이션이 끊기는 현상을 더 잘 확인할 수 있습니다.

애니메이션의 원리

일반적으로 사용하는 디스플레이의 주사율은 60Hz입니다. 즉, 1초에 60장의 정지된 화면을 빠르게 보여 준다는 의미입니다. 따라서 브라우저도 이에 맞춰 최대 60FPS(Frames Per Second)로 1초에 60장의 화면을 새로 그립니다.

 

 

그렇다면 올림픽 통계 서비스의 막대 그래프 애니메이션에서 쟁크 현상이 발생한 이유도 브라우저가 정상적으로 60FPS로 화면을 그리지 못했기 때문이라고 유추해 볼 수 있습니다.

CPU가 다른 일을 하느라 바빠서 초당 60장의 화면을 그리지 못하고, 40장의 화면, 아니면 더 적게 20장의 화면을 그려 애니메이션이 끊기는 느낌을 준 것입니다.

 

Performance 패널에서 아무 사이트나 검사한 후, 메인 스레드의 작업을 살펴보면 그림 2-17과 같이 작업 이름이 Parse HTML, Layout, Paint라고 되어 있는 것을 볼 수 있습니다. 이 작업이 브라우저 렌더링 과정입니다.

 

 

차트를 보다 보면 회색 세로 점선을 볼 수 있습니다. 바로 브라우저가 화면을 갱신하는 주기입니다. 

앞서 브라우저는 1초에 화면을 최대 60번 그린다고 했는데요. 화면을 그리는 시점이 바로 그림 2-18에서 점선으로 된 시점입니다.

 

리플로우와 리페인트

화면 내 어떤 요소의 너비와 높이가 변경되었다고 가정합시다.

1. 먼저 요소의 스타일이 변했으니까 CSSOM을 새로 만들어야 합니다. 

2. 변경된 CSSOM을 이용하여 새로운 렌더 트리를 만듭니다.

3. 요소의 가로와 세로를 변경했으니, 레이아웃 단계에서 당연히 요소의 크기와 위치를 다시 고려해야 합니다.

4. 그다음 변경된 화면 구성에 알맞게 색을 칠하고(페인트)

5. 분할된 레이어를 하나로 합성(컴포지트)하겠죠. 이것을 리플로우라고 합니다.

 

이처럼 리플로우는 주요 렌더링 경로의 모든 단계를 모두 재실행합니다. 그렇기 때문에 브라우저 리소스를 많이 사용합니다.

 

 

빨간 네모 안을 보면 리플로우 작업이 브라우저가 화면을 갱신하는 시점인 회색 선을 넘어가고 있습니다. 즉, 화면을 1/60초 안에 그려서 보여 줘야 하는데 리플로우가 발생하여 모든 단계를 다시 밟느라 필요한 화면을 제때 그려내지 못한 겁니다.

 

 

다행히 리플로우와 리페인트를 피하는 방법이 있습니다. 바로 transform, opacity와 같은 속성을 사용하는 방법입니다. 이런 속성을 사용하면 해당 요소를 별도의 레이어로 분리하고 작업을 GPU에 위임하여 처리함으로써 레이아웃 단계와 페인트 단계를 건너뛸 수 있습니다.

이것을 하드웨어 가속이라고 합니다.

 

하드웨어 가속

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

분리된 레이어는 GPU에 의해 처리되어 레이아웃 단계와 페인트 단계 없이 화면상의 요소의 스타일을 변경할 수 있습니다.

따라서 리플로우와 리페인트를 일으키는 width, height, color 등의 속성이 아닌 transform 또는 opacity 속성을 이용한 애니메이션 성능이 더 좋을 수밖에 없습니다.

물론 레이어가 너무 많아지면 그만큼 메모리를 많이 사용하기 때문에 주의해야 합니다.

 

애니메이션 최적화

 

 

여기서 주의할 점은 단순히 transform에 scaleX 값만 설정하면 막대 너비가 비율대로 표시되긴 하는데, 왼쪽에 치우치지 않고 가운데 정렬이 되어 버립니다. 그 이유는 기본적인 scale의 기준점이 중앙에 있기 때문에 중앙을 중심으로 정렬된 것인데요. 이것을 왼쪽 기준으로 변경하기 위해서 transform-orgin 속성을 center left로 변경해 줘야 합니다.

 

 

이처럼 레이아웃과 페인트 작업이 생략되었기 때문에 포함된 작업이 줄었고 최적화 전보다 여유로워 졌습니다.

 


컴포넌트 지연 로딩

번들 파일 분석

cra-bundle-ananlyzer를 설치하고 실행합니다.

 

 

2.chunk.js의 내용을 보면, react-dom과 styled-components뿐만 아니라 react-image-gallery라는 라이브러리가 들어 있습니다. 그리고 이 라이브러리에 커서를 올려 두면 대략적인 사이즈가 26KB(Parsed size) 정도임을 알 수 있는데(그림 2-27), 사실 이 라이브러리는 서비스 첫 화면부터 필요하지 않습니다. 이 라이브러리가 필요한 시점은 사진 갤러리가 있는 모달 창을 띄울 때입니다.

모달 코드 분리하기

const LazyImageModal = lazy(() => import("./components/ImageModal"));
//생략
<Suspense fallback={null}>
    {showModal ? (
      <LazyImageModal
        closeModal={() => {
          setShowModal(false);
        }}
      />
    ) : null}
</Suspense>

이렇게 하면 정적으로 import 되어서 번들 파일에 함께 포함되었던 Image Modal 컴포넌트와

그 안에서 사용되고 있는 react-image-gallery 라이브러리가 청크 파일에서 분리됩니다.

그리고 ImageModal이 로드되기 전에 발생하는 에러를 방지하기 위해,

Suspence 컴포넌트로 LazyImage- Modal 컴포넌트를 감싸 줘야 합니다.

 

결과

 

 

한 가지 특이한 점은 react-image-gallery만 분할될 것이라 생각했는데, react-image-gallery에서 참조하고 있는 모든 라이브러리가 함께 묶여 분할되었다는 점입니다. 덕분에 기대했던 26KB보다 많은 용량인 52KB가 분할되었습니다.

나중에 이 모달 컴포넌트 안에 더 많은 콘텐츠나 라이브러리가 들어간다면, 지금 적용한 컴포넌트 지연 로딩 기법이 꽤 의미 있는 최적화 기법이 될 것입니다.

 


컴포넌트 사전 로딩

이 기법은 사전 로딩이라는 단어에서도 알 수 있듯이 나중에 필요한 모듈을 필요해지기 전에 미리 로드하는 기법입니다

 

 

여기서 고려할 수 있는 타이밍이 두 가지 있습니다. 

하나는 사용자가 버튼 위에 마우스를 올려놨을 때(mouseenter)이고, 

다른 하나는 최초에 페이지가 로드되고 모든 컴포넌트의 마운트가 끝났을 때입니다.

 

버튼 위에 마우스를 올려놓았을 때 사전 로딩

 

 

결과적으로 버튼을 클릭하기 전에 모달 코드를 준비해 두니 딜레이 없이 모달을 띄울 수 있게 됩니다. 버튼을 클릭할 때, 마우스 커서를 버튼 위에 올리고 클릭하기까지 대략 300~600밀리초 정도 시간 차가 있습니다. 아주 찰나의 순간이긴 하지만 브라우저가 새로운 파일을 로드하기에는 충분합니다.

 

컴포넌트의 마운트 완료 후 사전 로딩

 

 

오른쪽 Waterfall에서 타임라인을 확인해 보면 초기 페이지 로드에 필요한 파일(0.chunk.js, bundle.js)을 우선 다운로드하고 페이지 로드가 완료된 후에야 모달 코드를 다운로드하고 있습니다.

 


이미지 사전 로딩

 

컴포넌트는 import 함수를 이용하여 로드했는데, 이미지는 이미지가 화면에 그려지는 시점, 즉 HTML 또는 CSS에서 이미지를 사용하는 시점에 로드됩니다. 하지만 이런 경우 외에 자바스크립트로 이미지를 직접 로드하는 방법이 한 가지 있습니다. 

바로 자바스크립트의 Image 객체를 사용하는 방법입니다.

 

 

이때 주의해야 할 점은 이 테스트를 할 때 Disable cache 옵션을 체크 해제해야 한다는 것입니다.

이미지 사전 로딩이 가능한 이유는 이미지를 로드할 때 브라우저가 해당 이미지를 캐싱해 두기 때문입니다. 

 

 

확인해 보면 사전 로드한 이미지의 Size 항목에 disk cache라고 적혀 있고, 다운로드 시간(Time)이 매우 짧은 것을 볼 수 있습니다. 

왜냐하면 페이지가 로드된 후 바로 이미지를 사전 로드해 뒀기 때문입니다.

 


-알라딘 eBook <프론트엔드 성능 최적화 가이드> (유동균 지음) 중에서

Comments