IT/Study

Renderer Process

프티 2022. 5. 21. 03:07
반응형

Renderer Process(이하 렌더러 프로세스)의 내부 작동

렌더러 프로세스는 웹 퍼포먼스의 많은 측면을 다룬다.

이것은 렌더러 프로세스의 내부에서는 많은 작업들이 존재하기 때문이다!

렌더러 프로세스에는 어떤 일들이 벌어질까?

렌더러 프로세스는 웹 콘텐츠들을 다룬다

렌더러 프로세스는 탭의 내부에서 발생하는 모든 것들에 대해 책임을 가지고 있다.

렌더러 프로세스에서 Main thread(이하 메인 스레드)는 당신이 사용자에게 보내는 코드의 대부분을 다룬다.

 

만약 당신이 web worker 또는 service worker를 사용한다면, 자바스크립트의 코드 일부는 때때로 Worker thread가 다루게 된다.

Web Worker와 Service Worker 

 

Web Worker와 Service Worker

브라우저는 자바스크립트를 실행하기 위해서 싱글 스레드를 사용하므로 거대한 자바스크립트 코드를 돌리면 메인 스레드가 block 되고 사용자 경험이 악화될 것이다. 앱에서 다중 스레딩 모델을

velog.io

 

Compositor와 Raster thread 또한 페이지를 효율적이고 부드럽게 렌더링 하기 위해 렌더러 프로세스 내부에서 동작한다.

 

렌더러 프로세스 내부에서 동작하는 스레드들

 

렌더러 프로세스의 주요 작업은 HTML, CSS, JS를 사용자가 상호작용할 수 있는 웹 페이지로 바꾸어 주는 것이다.

Parsing

DOM 구성 (DOM Tree)

렌더러 프로세스가 탐색(navigation)에 대한 커밋 메세지를 받고 HTML data를 받을 때,

메인 스레드는 HTML의 텍스트 문자열을 파싱하여 Document Object Model(DOM)으로 변환한다.

 

DOM은 웹 개발자가 JS를 통해 상호작용 할 수 있는 API이며 data 구조일 뿐만 아니라 페이지에 대한 브라우저의 내부 표현이다.

 

또한 HTML 문서를 DOM으로 파싱하는 것은 HTML 표준이다.

그리고 HTML을 브라우저에게 전달할 때 절대 오류를 발생시키지 않는다.

Hi! <b>I'm <i>Chrome</b>!</i>

위 코드는 다음과 같이 다루어진다.

Hi! <b>I'm <i>Chrome</i></b><i>!</i>

이는 HTML 명세가 이러한 에러들을 우아하게 다룰 수 있도록 설계되어있기 때문이다!

하위 리소스 로딩

웹 사이트는 이미지, CSS, JS와 같은 외부 리소스를 보통 사용한다.

이러한 파일들은 네트워크 또는 캐쉬로부터 로드될 필요가 있다.

 

메인 스레드는 DOM을 빌드하기위해 파싱을 하는 동안 파일들을 찾아 하나씩 요청을 보낼 수 있다.

하지만 하나씩 요청을 보내는 것은 속도가 느리기 때문에, 이를 해결하기 위해 preload scanner를 동시에 작동시킨다.

만약 HTML 문서에 <img> 또는 <link>가 있다면, preload scanner는 HTML parser에 의해 생성된 토큰을 미리 확인하고 브라우저 프로세스의 네트워크 스레드에 요청 묶음을 보낸다.

메인 스레드는 HTML을 파싱하여 DOM Tree를 빌딩한다.

JS는 파싱을 막을 수 있다

만약 HTML parser가 script 태그를 만난다면 파싱을 멈추고 해당 JS 파일을 로드하고 파싱 하여 실행한 뒤에 파싱을 재개한다.

왜 파싱을 멈추고 이러한 작업을 먼저 수행할까?

 

왜냐하면 JS는 document.write() 처럼 HTML의 문서 형태를 바꿀 수 있기 때문이다.

 

script 태그의 속성으로는 async, defer가 있는데,

async를 사용하면 로드하는 동안에는 파싱을 멈추지 않는다. 다만 로드가 완료되면 파싱을 멈추고 JS를 실행한 뒤 파싱을 재개한다.

결국 로드가 완료되면 파싱을 중단하기 때문에 사용자 경험 최적화에는 다소 아쉬운 부분이 있다.

 

반면에 deffer를 사용하면 로드하는 동안에 파싱을 멈추지 않고, 파싱이 끝나면 해당 JS파일을 실행시킴으로써 async 보다 더 나은 사용자 경험 최적화를 할 수 있다.

Style 계산 (CSS Tree)

DOM으로만은 충분하지 않다. CSS로 페이지의 요소들을 스타일링할 수 있기 때문이다.

메인 스레드는 CSS를 파싱하여 각 DOM 노드에 대한 계산된 스타일을 정의할 수 있다.

 

이것은 CSS 셀렉터들을 기반으로 하는 각 요소에 적용되는 스타일이 어떤 종류의 것인지에 대한 정보라고 할 수 있다.

이러한 정보들은 DevTools의 Element 섹션 내의 computed 섹션에서 확인할 수 있다.

각 노드들에 대한 계산된 스타일은 메인 스레드에 의해 CSS를 파싱함으로써 적용할 수 있다.

만약 개발자가 CSS 파일을 제공하지 않더라도, 브라우저는 default style sheet을 제공하기 때문에 기본적인 계산된 스타일이 적용된다.

Layout (reflow)

렌더러 프로세스가 DOM의 구조와 각 노드들에 대한 스타일(DOM Tree + CSS Tree = CSSOM Tree)을 알게 되었다.

하지만 각 노드들의 위치가 정확히 어디에 위치하는 지를 알기에는 부족하다!

 

Layout은 요소들에 대한 기하학적 정보를 찾는 과정이다.

메인 스레드는 CSSOM Tree를 살펴보고 box size와 x, y 좌표에 대한 정보를 가지는 layout Tree를 생성한다.

 

layout Tree는 DOM Tree와 유사한 구조를 가지지만, 오로지 페이지에서 보이는 정보만 포함되어 있다.

(display: hidden 같은 경우 layout Tree에서 제외됨)

메인 스레드는 CSSOM Tree를 통해 layout Tree를 생성한다.

Paint

CSSOM Tree, layout Tree로도 페이지를 렌더하기에는 아직 부족하다!

각 노드들의 사이즈, 형태, 위치를 알고 있지만 이들이 어떤 순서로 그려질 지에 대해 명확하지 않기 때문이다.

 

예를 들어, 어떠한 요소가 z-index 값을 가지고 있다면, HTML에 쓰인 순서대로 그려졌을 때 정확하지 않은 렌더링이 될 것이다.

위 예시

메인 스레드는 layout Tree를 확인하고, paint record를 생성한다.

paint record는 그려지는 과정을 담은 노트와 같다.

 

즉, paint record를 통해 실제 화면에 그려질 순서를 알 수 있다.

렌더링 파이프라인을 업데이트 하는 것은 비용이 비싸다!

렌더링 파이프라인에서 파악해야할 가장 중요한 것은,

각 단계에서 이전 작업의 결과를 사용하여 새로운 데이터를 생성한다는 이다.

 

예를 들어, layout Tree에서 변화가 발생한다면, paint 순서는 변화에 영향을 받는 부분들에 대한 재생성이 필요하다.

렌더링 파이프라인

애니메이션 요소가 있다고 생각해보자.

브라우저는 애니메이션을 모든 프레임 사이에서 실행해야 한다.

대부분의 디스플레이는 60fps를 가지고 있다. 따라서 인간의 눈으로 보기에 부드럽게 보일 것이다.

 

하지만, 이 모든 과정은 메인 스레드에서 실행되고 있다.

메인 스레드는 자바스크립트 또한 실행할 수 있는데, 이 경우에 애니메이션은 block 될 수 있다는 것을 의미한다.

즉, 사용자는 멈추어있는 애니메이션을 보게되는 안 좋은 사용자 경험을 하게 될 것이다!

 

하지만 우리는 JS를 작은 chunk로 나누어 매 프레임에서 실행시킴으로써 위의 문제를 해결할 수 있다.

Compositing

우리는 CSSOM Tree, layout Tree, paint order를 통해 페이지를 렌더링 할 모든 준비를 마쳤다.

그렇다면 이제 어떻게 페이지를 그릴까?

 

이러한 정보들을 스크린 위에 픽셀로 바꾸는 과정을 Rasterizing(픽셀화?)이라고 한다.

초기에 크롬에서는 뷰포트 내부의 부분을 래스터하고, 사용자가 페이지를 스크롤하면 래스터된 프레임을 움직이고 빈 공간을 채우는 방식을 사용했다고 한다.

래스터라이징 방식

그러나, 모던 브라우저에서는 compositing이라는 보다 정교한 방식을 사용한다.

What is compositing?

compositing은 페이지의 각 부분을 레이어로 나누고, 레이어를 각각 래스터화한다.

이후에 compositor 스레드에서 페이지로 합성하는 기술이다.

 

만약 사용자가 스크롤한다면, 레이어들이 이미 래스터화 되어 있으므로 새로운 프레임으로 합성하기만 하면 된다.

애니메이션 또한 같은 방식으로 레이어를 움직이고 새로운 프레임으로 합성하는 방식을 통해 얻을 수 있다.

 

레이어로 나누기

메인 스레드는 layout Tree를 통해 layer Tree를 생성한다.

레이어 트리 생성 과정

모든 요소에 레이어 트리를 줄 수 있겠지만, 지나친 레이어는 매 프레임마다 페이지의 작은 부분들을 모두 래스터화 하는 것보다 더 느린 성능을 보일 수 있다.

따라서 애플리케이션의 렌더링 퍼포먼스를 측정하는 것이 중요하다고 볼 수 있다.

메인 스레드의 래스터 및 합성

layer Tree와 paint order가 정해졌다면, 메인 스레드는 compositor 스레드에 해당 정보를 커밋한다.

compositor 스레드는 각 레이어를 래스터화 한다. 그리고 레이어는 페이지의 전체 길이와 같이 거대할 수 있다.

따라서 compositor 스레드는 이를 타일로 나누고 각 타일을 raster 스레드에 보낸다.

 

raster 스레드는 각 타일을 래스터화 하고 이를 GPU 메모리에 저장한다.

compositor는 각 레이어를 래스터화하고, 타일로 나누어 raster 스레드에 보낸 뒤 이를 래스터화하여 gpu 메모리에 저장한다.

compositor 스레드는 래스터 스레드들의 우선순위를 지정하여, 뷰포트에 가까운 항목이 먼저 래스터화 될 수 있도록 한다.

또한 레이어는 zoom-in과 같은 작업을 처리하기 위해 다양한 해상도에 대한 여러 타일을 가지고 있다.

 

타일이 래스터화 되면, compositor 스레드는 드로우 쿼즈(draw quads)라는 타일 정보를 모아서 compositor frame을 생성한다.

*draw quads: 페이지 compositing을 고려한 타일을 그리기 위한 페이지의 위치와 메모리 내에서의 타일의 위치와 같은 정보를 담고 있다.

*compositor frame: 페이지의 프레임을 나타내는 draw quads의 모음

 

compositor frame은 IPC(프로세스 간 통신; Inter Process Communication)에 의해 제출된다.

여기서 브라우저의 UI 변경이나 익스텐션에 대한 또 다른 렌더러 프로세스에 의해 다른 compositor frame이 추가될 수 있다.

 

이렇게 모인 compositor frame들은 스크린에 보이기 위해 GPU에 보내진다.

스크롤 이벤트가 발생하면, compositor 스레드는 또 다른 compositor frame을 생성하여 GPU로 보낼 것이다.

ui 변경에 따른 ui 스레드, 익스텐션에서 다른 렌더러 프로세스에 의한 또다른 compositor frame들이 추가될 수 있다. 이들은 모두 GPU에 보내져 화면에 나타나게 된다

compositing의 이점은 메인 스레드의 관여 없이 위의 과정을 실행할 수 있다는 것이다.

compositor 스레드는 style 계산이나 JS 파일 실행을 기다릴 필요가 없다.

하지만 layout이나 paint가 다시 계산된다면 메인 스레드가 관여하게 된다.

 

출처

https://developer.chrome.com/blog/inside-browser-part3/

반응형

'IT > Study' 카테고리의 다른 글

Blocking/NonBlocking과 Sync/Async  (0) 2022.05.29
Cross Browsing  (0) 2022.05.24
Redux 내부 구조  (0) 2022.01.29
몰랐던 내용 정리 (상시 업데이트)  (0) 2021.11.26
동적 타입 언어  (0) 2021.11.23