출처 : https://developer.chrome.com/blog/inside-browser-part3/
자세한 내용은 원문을 참고해주세요.
지난 "모던 웹 브라우저 톺아보기2"에서 어떻게 브라우저가 서버로 부터 데이터를 들고오는 지 등.. 브라우저 랜더링에 관해서 살펴보았습니다.
이번에는 렌더러 프로세스에 대해서 알아보도록 하겠습니다.
렌더러 프로세스는 웹 성능의 많은 면에 관여를 합니다. 많은 부분들이 일어나기 때문에 일반적인 부분들만 다루도록 하겠습니다.
렌더러 프로세스는 브라우저 탭 안에서 일어나는 모든 일들을 담당합니다. 렌더러 프로세스 안의 메인 스레드가 개발자가 구현한 대부분의 코드를 처리합니다. (웹 워커 혹은 서비스 워커를 사용하면 워커 스레드가 코드 일부분을 처리합니다.) 컴포지터 스레드와 레지스터 스레드 또한 페이지를 효율적이며 매끄럽게 렌더하기 위해 실행됩니다.
각 스레드의 역할을 살펴보면
렌더러 프로세스는 위 역할을 가지는 스레드들을 통해 HTML, CSS 그리고 자바스크립트를 사용하여 사용자가 인터렉션 가능한 웹페이지를 만듭니다.
URL을 입력받은 후 브라우저 내부의 여러 동작과 네트워크를 거쳐서 랜더러 프로세스가 동작하게 되고 그 과정에서 HTML, CSS, Javascript가 다운로드 되고 렌더러 프로세스의 메인 쓰레드로 전달되게 됩니다.
전체적은 과정은 아래 그림과 같습니다.
vsync and input data (frame start)를 시작으로 여러 단계의 파이프라인을 거쳐 프레임이 종료 될때 commit을 통해 gpu process 에 전달되는 과정이 나타나있습니다.
vsync 및 input event handlers, requestAnimationFrame 등에 대한 설명은 재쳐두고 일단 MainThread에서 진행되는 렌더링 과정부터 살펴보도록 하겠습니다.
파싱은 서버로부터 전송받은 문서의 문자열을 브라우저가 이해할 수 있는 구조로 변환하는 과정을 파싱이라고 하는데요. 파싱 결과는 문서 구조를 나타내는 노드 트리인데, 파싱 트리 또는 문법 트리라고 합니다. 파싱의 결과로 만들어진 DOM을 통해 추후 노드들을 쉽게 관리 할 수 있게 됩니다.
렌더러 프로세스가 탐색을 위한 커밋 메세지를 받고 html 데이터를 받기 시작할 때, 메인쓰레드는 html을 파싱하여 DOM으로 변환하게 됩니다. ( 변환, 토큰화, 렉싱 등..의 과정이 있음 https://html.spec.whatwg.org/)
잠깐!
html 파서는 script 태그를 발견하면 파싱을 잠시 멈추고 JS를 로드, 파싱, 실행해야 합니다. JS가 전체 돔 구조를 바꿀 수 있기 때문에 html 문서를 다 파싱하고 JS를 로드 할 수 있도록 script 태그는 맨 아래에 작성하는 것이 좋습니다.
CSS로 페이지의 요소를 스타일링을 해야하는데요.
main스레드는 CSS를 파싱하고 각각 DOM node에 대한 계산된 스타일을 결정짓게 됩니다. CSS셀렉터를 베이스로 각각의 요소에 어떤 스타일이 적용되는지에 대한 정보가 존재하며 이런 정보는 개발자도구의 computed섹션에서 확인할 수 있습니다.
CSS를 전혀 활용하지 않더라도 브라우저가 기본 스타일 시트를 가지고 있기 때문에 각 돔 노드는 스타일을 가지게 됩니다. (CSSOM 구축)
추가 내용 ) CSSOM이 트리 구조를 가지는 이유는 페이지에 있는 객체의 최종 스타일을 계산할 때 브라우저는 해당 노드에 적용 가능한 가장 일반적인 규칙으로 시작한 후 더욱 구체적인 규칙을 적용하는 방식이기 때문에, 즉 '하향식'으로 규칙을 적용하는 방식으로 계산된 스타일을 재귀적으로 세분화하게 됩니다.
CSSOM 트리와 DOM 트리를 결합하여, 표시해야 할 순서로 내용을 그려낼 수 있도록 하기 위해 렌더 트리(레이아웃 트리)를 형성합니다. 이 과정을 웹킷에서는 Attachment라고 합니다.
렌더 트리를 생성하러면 브라우저는 대략 3가지 작업을 수행합니다.
1. DOM 트리의 루트에서 시작하여 화면에 표시되는 노드 각각을 탐색합니다.
랜더트리가 만들어진 후에 기기의 viewport를 기준으로 노드들의 정확한 위치와 크기를 계산하게 됩니다. (Layout 과정 - Reflow )
추가내용 )
Layout 이벤트는 타임라인에서 렌더 트리 생성, 위치 및 크기 계산을 캡쳐한 시간을 나타내줍니다.
요소들을 어떤 순서로 그릴지 판단해야 합니다. z-index는 임의의 요소들에 설정될 수 있는데, 이 경우 html에 작성된 순서로 그리면 잘못된 렌더링이 발생합니다.
페인트 단계에서 메인 스레드는 레이아웃 트리를 따라가 페인트 기록을 생성합니다.
rendering파이프라인에서 이해해야할 가장 중요한 것은 각 단계에서 이전작업의 결과물이 새 데이터를 만들어내는 데 사용된다는 것입니다.
예를 들어, 만약 layout트리에서 무언가 변경이 생기면,문서에서 영향받는 부분들에 한해 Paint순서가 다시 만들어져야 합니다.
만일 요소들을 애니메이션으로 표현할 경우, 이러한 동작을 매 프레임마다 실행해야 합니다. 대부분의 디스플레이는 60fps로 스크린을 새로고침합니다.
렌더링 연산은 메인 스레드에서 실행되기 때문에, 이 과정이 JS를 실행한는 것을 방해할 수 있습니다.
이 경우, requestAnimationFrame()을 이용하여 매 프레임마다 실행하는 것을 미리 설정할 수 있습니다. 맨 위의 그림과 아래 그림을 참고해주세요.
페이지에 대한 정보를 스크린의 픽셀로 바꾸는 것을 레스터라이징이라고 합니다. 이를 처리하는 단순한 방법은 화면에 보이는 영역을 레스터하는 것입니다. 사용자가 스크롤을 내리면, 레스터된 프레임을 움직이고 더 레스터링하여 부족한 부분을 메꿉니다.
하지만 모던 브라우저는 컴포지팅 이라는 방식으로 동작하게 됩니다.
컴포지팅은 한 페이지의 부분들을 여러 레이어로 나누고 그 것들을 각각 레스터하며 컴포지터 스레드에서 페이지를 합성하는 기술입니다.
만약 스크롤이 발생하면, 레이어들이 이미 레스터되었기 때문에 새로운 프레임을 합성하면 됩니다. 어떻게 웹 사이트가 여러 레이어로 나뉘는지는 개발자 도구의 Layers panel에서 볼 수 있습니다.
어떤 요소들이 어떤 레이어에 있어야 하는지 알기 위해, 메인 스레드는 레이아웃 트리를 순회하여 레이어 트리를 생성합니다. (개발자 도구 성능 탭의 Update Layer Tree) 만약 별도의 레이어에 있어야만 하는 페이지의 어떤 부분(ex. 슬라이드되어 들어오는 사이드 메뉴)이 아직 처리되지 않은 경우에는 CSS 속성 will-change를 이용하여 브라우저에게 미리 알려줄 수 있습니다.
지나친 수의 레이어에 대해 합성하는 것은 매우 느리므로 이는 렌더링 성능을 따질 때 아주 중요합니다.
레이어트리(렌더트리)가 생성되고 페인트 순서가 결정되고 나면, 메인 스레드는 컴포지터 스레드에게 정보를 커밋합니다. 그 후 컴포지터 스레드가 각 레이어를 레스터라이즈합니다.
컴포지터 스레드는 레이어들을 여러 타일로 쪼개고 각 타일을 다수의 레스터 스레드에게 보냅니다. 레스터 스레드들은 각 타일을 레스터라이즈하고 GPU 메모리에 저장합니다.
컴포지터 스레드는 서로 다른 레스터 스레들에 대해 우선순위를 지정할 수 있습니다. 그리하여 화면 안에 보이는 것들이 먼저 레스터될 수 있습니다. (우선순위를 미리 지정함)
또한 한 레이어는 다른 해상도에 따라 다수의 타일을 가질 수 있는데 이는 줌인아웃 동작을 처리하기 위함입니다.
타일들이 레스터되면, 컴포지터 스레드는 쿼드 군집이라는 타일 정보들을 모아 컴포지터 프레임을 생성하게 됩니다.
컴포지터 프레임은 IPC를 통해 브라우저 프로세스로 넘어가게 됩니다. 이 때, UI 변화에 따라 다른 컴포지터 프레임이 UI스레드 또는 다른 렌더러 프로세스들에 의해 추가될 수 있습니다.
컴포지터 프레임들은 GPU에 보내져 화면을 그리게 됩니다. 만약 스크롤 이벤트가 발생하면 컴포지터 스레드는 GPU에 보낼 다른 컴포지터 프레임을 생성합니다.
컴포지팅의 장점은 메인 스레드의 개입 없이 수행된다는 것입니다. 컴포지터 스레드는 스타일 계산이나 JS 실행을 기다릴 필요가 없습니다. 이것이 컴포지팅만 하는 애니메이션이 부드러운 성능을 위한 가장 좋은 방법으로 여겨지는 이유입니다. 만약 레이아웃 또는 페인트가 다시 계산된다면 메인 스레드가 관여해야 합니다.
( 그래서 애니메이션으로 동작을 일으키면 더 부드럽게 동작하는 것이였군..)
즐겁게 읽었습니다. 유용한 정보 감사합니다.