최신 브라우저 렌더링 살펴보기를 통해 본 적용 기술 deep dive

김규빈·2023년 3월 9일
1
post-thumbnail

유저 트래픽이 증가함에 따라 프론트엔드 개발자가 개선할 수 있는 방법으론
브라우저 렌더링 과정을 파악하고 그중 단축시킬 수 있는 요소 파악이나 과정을 개선시켜 좀 더 나은 유저 경험을 제공할 수 있다.

그 여러 최적화 기술 중 도입한 기술들이 어느 부분에서 성능 향상이 가능했는지 자세하게 살펴보자!

1. RequestAnimationFrame

RequestAnimationFrame 브라우저에게 해당 애니메이션을 알리고 repaint 되기 전 해당하는 애니메이션을 실행시켜 애니메이션 실행을 보장할 수 있다. 요소에 애니메이션을 적용하면 브라우저는 모든 프레임 사이에서 이러한 작업을 해야 하는데, 대부분의 디스플레이 장치는 화면을 초당 60번 새로 고친다(60fps). 요소의 움직임이 모든 프레임에 반영되어야 사람이 볼 때 부드럽게 느껴진다. setInerval이나 setTimeout을 이용해 콜백을 통하여 애니메이션을 실행시키는 법은 화면 주사율이나 지연 실행 때문에 애니메이션에서 프레임이 누락되게 되면서 웹 페이지가 '버벅대는(janky)' 것처럼 보일 수 있다.

2. Intersection Observer API

Intersection Observer API 는 기존에 스크롤 이벤트 동작 감지 시 사용되던 getboundingClientRect를 개선하기 위해 나온 api이다. getBoundingClientRect 동작의 경우 렌더링 과정 중 reflow를 발생시키게 된다. reflow의 동작의 내부를 보면 브라우저는 레이아웃을 만들게 되는데 레이아웃은 요소의 기하학적 속성(geometry)를 찾는 과정이다. 메인 스레드는 DOM과 계산된 스타일을 훑어가며 레이아웃 트리를 만든다. 레이아웃 트리는 x, y 좌표, 박스 영역(bounding box)의 크기와 같은 정보를 가지고 있다. 레이아웃 트리는 DOM 트리와 비슷한 구조일 수 있지만 웹 페이지에 보이는 요소에 관련된 정보만 가지고 있다. 웹 페이지의 레이아웃을 결정하는 것은 어려운 작업이다. 가장 단순하게 위에서 아래로 펼쳐지는 블록 영역 하나만 있는 웹 페이지의 레이아웃을 결정할 때에도 폰트의 크기가 얼마이고 줄 바꿈을 어디서 해야 하는지 고려해야 한다. 단락의 크기와 모양이 바뀔 수 있고, 다음 단락의 위치에 영향이 있기 때문이다. 이러한 연산을 계속 거치게 되기 때문에 reflow를 발생시키는 요소는 성능 저하를 유발하게 된다. Intersection Observer API관찰하는 타겟 요소와 브라우저의 교차점만 추적하게 된다. 비동기 작업으로 동작하고 reflow 연산을 발생시키지 않기 때문에 성능 상 이점을 가져갈 수 있다.

3. will-change

브라우저 파싱 과정을 거쳐 만들어진 DOM과 CSSOM을 바탕으로 브라우저는 컴포지션이라는 작업을 통해 레스터화 하기 시작한다. 컴포지션은 웹페이지 각 부분을 레이어로 분리하고 컴포지터 스레드라는 별도의 스레이드에서 합성을 거친다. 어떤 요소가 레이어를 구성해야 되는지 확인하기 위해 메인 스레드는 레이아웃 트리를 순회하며 레이어 트리를 만들게 된다. 이때 will-change 속성을 통해 브라우저는 요소를 위한 별도의 레이어를 만들어야 된다고 알려줄 수 있다. 이를 통해 브라우저의 연산량을 줄여 줄 수 있는 것. 하지만 레이어가 많으면 오히려 성능이 나빠지기 때문에 레어어 수를 적절히 만들어야 한다. 그렇기에 MDN에 나와있는 will-change 속성에 대한 내용이 과도한 will-change 속성의 사용은 오히려 성능을 떨어트릴 수 있으니 계산하여 사용하라고 나와 있다. will-change 속성을 적용 후 렌더링 성능은 직접 측정해 보는 것을 추천.

4. gpu렌더링

브라우저의 핵심은 CPUGPU 연산이라고 할 수 있겠다. GPU 성능이 시간이 지날수록 고도화되고 있기 때문에 view를 구성하는 과정에서 GPU 렌더링을 통해 현대의 웹뷰 성능이 크게 증가했다고 봐도 무방하다. 위에서 설명한 레이어트리를 생성하고 페인트 순서가 결정되면 메인 스레드는 해당 정보를 컴포지터 스레드에게 넘기게 된다. 컴포지터 스레드는 각 레이어를 타일 형태로 나눠 레스터 스레드로 보내게 되는데 레스터 스레드는 각 타일을 레스터화 해 gpu 메모리에 저장하게 된다. Gpu렌더링은 Cpu의 메인 쓰레드와는 별도로 작업이 진행되기 때문에 빠른 연산이 가능하고 별도 작업이라는 장점을 통해 성능이 개선 되는 것. 브라우저에게 gpu를 통해 렌더링 하라고 알려주는 방법은 아래와 같다.

  1. CSS 3D Transform(translate3d, preserve-3d 등)이나 perspective 속성이 적용된 경우
  2. video 또는 canvas 요소
  3. CSS3 애니메이션함수나 CSS 필터 함수를 사용하는 경우
  4. 자식 요소가 레이어로 구성된 경우
  5. z-index 값이 낮은 형제 요소가 레이어로 구성된 경우. 레이어로 구성된 요소의 위에 위치하면 해당 요소도 레이어로 구성된다.

5. Webp

구글에서 만든 이미지 포맷. 이름처럼 Web을 위해서 만들어진 효율적인 이미지 포맷이다. webp는 기존의 이미지 포맷 gif, png, jpeg 같은 이미지를 대체하기 위해 만들어졌다. 무손실 압축을 사용할 경우도 PNG 포맷보다 20~30% 정도 크기가 작아진다. 우리가 개발하다 보면 디바이스의 성능이 낮거나 저전력 기기(스마트폰 포함)의 사양에 따라 높은 리소스를 요청할 필요도 있다. 그럴 경우 상대적으로 작은 크기를 가진 webp 리소스를 통해 상대적으로 부담을 줄일 수 있다. 하지만 WebP를 렌더링 하는 과정에서 배터리, 램, CPU 등을 많이 점유/소모하여 전력 부분에서는 효율이 떨어질 수도 있다. 날로 발전해가는 디바이스 시장에서 2021년 기준 출시된 기기에서는 WebP 정도는 별 리소스 투입 없이 쉽게 재생이 가능하기 때문에 시간이 좀 더 흐른 뒤엔 별 무리가 없을 것으로 보인다. 하지만 webp 포맷은 모든 브라우저에서 지원을 하지 않기 때문에 지원 상태 체크는 필수적이다. 일반적으론 브라우저에게 canvas 요소를 만들고 dataUrl 에 webp 속성을 주어 webp가 에러없이 적용이 되었다면 webp를 지원하는 브라우저라고 판별 할 수 있다. 지원하는 브라우저의 경우 webp 리소스를 사용하고 아닐 경우 기존의 jpg같은 리소스를 사용하면 되기 때문에 지원하는 브라우져 환경에선 좀 더 효율적인 리소스 로드를 하자.

6. 리소스 우선 순위 로드

브라우저의 탭 내부 모든 내부 연산은 렌더러 프로세스가 작업을 담당한다. 페이지를 이동하는 내비게이션 실행 메시지를 렌더러 프로세스가 받고 HTML 데이터를 수신하기 시작하면 렌더러 프로세스의 메인 스레드는 문자열(HTML)을 파싱해서 DOM으로 변환하기 시작한다. 웹 사이트는 일반적으로 이미지, CSS, JavaScript와 같은 외부 리소스를 사용한다. 이러한 파일은 네트워크나 캐시에서 로딩해야 한다. DOM을 구축하기 위해 파싱하는 동안 이런 리소스를 만날 때마다 메인 스레드가 하나하나 요청할 수도 있을 것이다. 하지만 속도를 높이기 위해 '프리로드(Preload) 스캐너'가 동시에 실행된다. HTML 문서에 img 또는 link 와 같은 태그가 있으면 프리로드 스캐너는 HTML 파서가 생성한 토큰을 확인하고 브라우저 프로세스의 네트워크 스레드에 요청을 보낸다. 이렇게 리소스 로드 순위를 제공해 렌더러 프로세스가 작업을 진행 하는동안 네트워크 스레드를 통해 병렬적으로 동작을 진행시켜 빠른 로드가 가능하게 제공 할 수도 있다. HTML을 파싱하는 도중 스크립트 태그를 만나게 되면 기존의 dom 구축을 중지하고 스크립트를 파싱하게 되는데 이 는 JS에서 dom을 변경시킬 수 있는 코드가 있을 수 있기 때문이다(document.write) 만약 스크립트 내부에 dom을 조작하는 요소가 없다면 async, defer같은 속성을 통해 비동기적으로 로드하는 방법이 있다.

출처
https://d2.naver.com/helloworld/2922312 시리즈
번역글을 제공해준 네이버 D2 감사합니다 !

profile
FrontEnd Developer

0개의 댓글