주의) 이글은 깁니다...
크롬에서 렌더링은 렌더러 프로세스가 담당한다.
이 글과 이전글은 GPU가 실제 클라이언트에서 어떻게 쓰이는지 원리를 알기 위해 찾아보았던 내용이다.
그 내용을 아래에 후술한다.
렌더러 프로세스에 관한 이전 글)
[운영체제] 크롬 웹 브라우저의 구조 (프로세스 & 쓰레드)
자바스크립트는 싱글 쓰레드이지만, 크롬 브라우저의 탭 하나하나는 각각 하나의 프로세스이고 멀티 쓰레드 환경이다. 이전에 썼던 이벤트 루프 관련 글에서 정확히 브라우저의 내부 구조가 어
lodado.tistory.com
www.google.com을 검색할때 크롬에서 일어나는일
면접 단골 질문이긴 한데 네트워크 말고 크롬이 하는 일만 집중해서 간단히 살펴보자.
자세한 과정은 아래 링크를 추천한다.
브라우저마다 상세 구현은 다를 수 있기 때문에
'흐름' 을 파악하는 정도면 좋을 것 같다.
https://developers.google.com/web/updates/2018/09/inside-browser-part2?hl=ko
모던 웹 브라우저 들여다보기 (파트 2) | Web | Google Developers
브라우저가 탐색 요청을 처리하는 방법을 학습하십시오.
developers.google.com
브라우저 프로세스는 주소 창, 뒤로 및 앞으로 이동 버튼을 포함한 어플리케이션의 "chrome" 부분을 제어한다.
또한 네트워크 요청 및 파일 액세스와 같은 웹 브라우저의 권한이 부여된 보이지 않는 부분을 제어한다.
브라우저 프로세스 내부를 보면
버튼이나 입력창을 그리는 UI 스레드,
인터넷에서 데이터를 수신하기 위해 통신 스택을 건드리는 네트워크 스레드,
파일 같은 것들에 접근하기 위한 스토리지 스레드
등을 가지고 있다.
Browser Process가 내부 NetWork thread에 브라우저 탐색 과정을 수행하게 시키고, 작업이 완료되면
Renderer Process에게 IPC를 통해 페이지 렌더를 요청한다.
이후 과정이 우리가 흔히 아는 브라우저의 렌더링 과정이다.
Renderer Process가 하는 일
렌더러 프로세스는 브라우저 탭 안에서 일어나는 모든 일들을 담당한다.
렌더러 프로세스의 핵심 역할은 HTML, CSS 그리고 자바스크립트를 사용자가 인터렉션할 수 있는 웹 페이지로 만드는 것이다.
프로세스는 여러개의 쓰레드로 구성되어 있다.
Main Thread : 대부분의 코드를 처리한다. (파싱, 렌더 등)
Work Threads : 웹 워커 혹 서비스 워커를 사용할 경우 워커 스레드가 자바스크립트 코드 일부분을 처리한다.
Compositor & Raster Thread : 렌더러 프로세스 내부에서 페이지를 효율적이며 매끄럽게 렌더하기 위해 실행된다.
compositor와 Raster thread의 역할은 후술한다.
브라우저의 렌더링 과정
이것 또한 표준이 없어서... 세부 구현이 브라우저마다 다를 수 있지만 흐름 자체를 이해하자.
브라우저는 파싱 → 스타일 → 레이아웃 → 페인트 → 합성 → 렌더 등의 과정을 거친다. 그 후에 JS나 CSS를 통해 DOM이나 CSS에 변화가 생길 경우 reflow 혹은 repaint 등의 과정을 수행한다.
1) 파싱
HTML 파싱
브라우저가 HTML 을 파싱하고 읽어들이는 과정이다.
렌더러 프로세스가 탐색을 위한 커밋 메세지를 받고 HTML 데이터를 받기 시작할 때, 메인 스레드는 텍스트 문자열 (HTML)을 파싱하기 시작하고 이를 Document Object Model (DOM)으로 변환한다.
DOM은 페이지에 대한 브라우저의 내부 표현일 뿐만 아니라 웹 개발자가 자바스크립트를 통해 상호 작용할 수 있는 데이터 구조 및 API이다.
예측 파싱
HTML 문서에 <img> 혹은 <link>등이 있는 경우, 사전 로드 스케너는 HTML 파서에 의해 생성된 토큰들을 살짝 보고 브라우저 프로세스에 있는 네트워크 스레드에게 요청을 보낸다.
파싱 차단 리소스
파싱 과정에서 자바스크립트가 파싱을 중단시킬수도 있다.
<script> 태그를 만나면 HTML 문서를 파싱하는것을 멈추고 자바스크립트 코드를 로딩, 파싱, 실행한다.
이를 JavaScript는 파싱 차단 리소스(parser blocking resource)라고 부른다.
이를 방지하기 위해서는
- <script>를 body 제일 아래에 놓는다 (가장 고전적인 방법)
- 비동기로 데이터를 내려받고 다운로드가 완료되면 실행되는 async 태그를 사용한다.
- 비동기로 데이터를 내려받고 파싱이 완료되면 실행되는 defer 태그를 사용한다. (가장 추천)
- 리소스 우선순위화 - <link rel="preload"> 사용
가장 추천되는 방식은 head에 script + defer attribute이다.
asnyc, defer에 대해서는 아래 글 추천
https://guiyomi.tistory.com/101
Javascript - script 로딩 시 async vs defer
HTML 파일에서 외부 js 파일을 가져오는 방법은 다음과 같다. 그럼 여기서 궁금한 점이 발생한다. script 태그의 위치는 어디에 위치해야 할까? 보통 브라우저는 HTML 문서를 파싱할 때, 위에서부터
guiyomi.tistory.com
CSS 파싱
외부/내부의 스타일시트의 CSS를 해석해 CSSOM 트리를 구성한다.
속성을 주지 않아도 각 element들은 브라우저마다 고유의 css 속성을 가지며, 이것을 인위적으로 통일하거나
다 지우기 위해서 보통 개발할때 normalize.css나 reset.css를 사용한다.
렌더 차단 리소스(rendering-blocking-resource)
HTML, CSSOM이 완성 이전까지 브라우저는 렌더링을 멈추게 된다.
즉, css를 네트워크에서 빨리 받아오는게 중요하다.
<link rel="preload" as="script" href="super-important.js">
<link rel="preload" as="style" href="critical.css">
preload, preconnect, prefetch 등을 사용해 브라우저에게 우선순위를 알려줄 수 있다.
<link href="style.css" rel="stylesheet">
<link href="style.css" rel="stylesheet" media="all">
<link href="print.css" rel="stylesheet" media="print">
<link href="portrait.css" rel="stylesheet" media="orientation:landscape">
<link href="other.css" rel="stylesheet" media="min-width: 40em">
그리고 미디어 쿼리를 이용해 자신이 사용하는 미디어 타입에 걸맞는 css만 받아오고, 다른 기기의 css는 받아오지 않는 식으로 (용량을 줄여) 최적화 할 수 있다.
2) 스타일
dom tree와 CSSOM tree가 생성되었다면 결합해서 Render Tree를 만든다.
렌더링 트리에는 페이지를 렌더링하는 데 필요한 노드만 포함된다.
그리고 어떤 렌더 객체는 DOM 노드에 대응하지만 트리의 동일한 위치에 있지 않다. float 처리된 요소 또는 position 속성 값이 absolute로 처리된 요소는 흐름에서 벗어나 트리의 다른 곳에 배치된 상태로 형상이 그려진다. 대신 자리 표시자가 원래 있어야 할 곳에 배치된다.
3) 레이아웃(재시작시 Reflow)
레이아웃은 요소들의 기하학적인 구조를 찾는 과정이다.
현재 viewport 기준으로 정확히 DOM이 어디있는지 위치, 크기를 계산한다.
만약 CSS나 돔의 layout과 관련된 요소(width, font-size 등)가 변경된다면 reflow 과정이 생길수도 있다.
추가적으로
display: none을 사용하면 렌더링 트리에서 요소가 완전히 제거된다.
visibility : hidden을 사용하면 레이아웃에서 공간을차지하지만 요소가 보이지 않는다.
그리고 레이아웃 트리를 순회하면서 속성 트리(property tree)를 만드는 작업이 페인트와 레이아웃 사이에 끼여있다.
속성 트리는 clip, transform, opacity등의 속성 정보만 가진 트리이다.
최신 Chrome에서는 이런 속성만 별도로 관리하고 각 노드에서는 속성 트리의 노드를 참조하는 방식으로 변경되고 있다.
4) 페인트(재시작시 repaint)
레이아웃 트리를 따라가 페인트 기록을 생성한다.
배경 이미지, 텍스트 색상, 그림자등 실제 레이아웃의 수치를 변형시키지 않는 스타일의 변경이 일어나면
repaint만 발생한다.
일반적으로 reflow가 발생되면 repaint도 같이 발생한다.
(레이아웃 수치를 다시 계산해서 배치를 해야하기 때문에)
5. 합성(composite)
페이지를 그리는 방법
컴포지팅은 한 페이지의 부분들을 여러 레이어로 나누고 그 것들을 각각 레스터화하며 컴포지터 스레드에서 페이지를 합성하는 기술이다. 그리고 만약 스크롤이 발생하면, 레이어들이 이미 레스터되었기 때문에, 해야 할 것은 새로운 프레임을 합성하는 것이다.
에니메이션은 레이어들을 움직이는 동일한 방식으로 이뤄지고 새로운 프레임을 합성한다.
어떻게 웹 사이트가 여러 레이어로 나뉘는 지 개발자 도구의 Layers panel에서 볼 수 있다.
참고) 레스터화와 벡터화
레스터화는 각 요소 이미지를 컴퓨터가 이해할 수 있는 비트맵으로 변환하고, 하나의 비트맵으로 합성하는 과정이다.
https://m.blog.naver.com/reductionist101/221567932033
래스터, 벡터 차이
래스터(raster)와 벡터(vector)는 컴퓨터가 이미지를 저장하는 방식이며, 둘 사이에는 차이가 있다. 첫 번...
blog.naver.com
Layer에 대해서
메인 스레드는 레이아웃 트리를 순회하여 레이어 트리를 생성한다. (이 부분은 개발자 도구 성능 탭의 Update Layer Tree 참고)
모든 요소들에 대해 레이어를 지정할 수도 있다. 하지만 수많은 레이어를 합성하는 것은 웹페이지의 작은 부분을 매 프레임마다 새로 래스터화하는거보다 느릴 수 있다.
그래서 메인 쓰레드를 사용하지 않고 레스터와 컴포지트 하는 방식을 알아보자 (쉬운 말로 GPU를 써보자)
레이어 트리가 생성되고 페인트 순서가 결정되고 나면, 메인 스레드는 컴포지터 스레드에게 정보를 커밋한다.
그러면 컴포지터 스레드가 각 레이어를 레스터라이즈(=레스터화)한다.
컴포지터 스레드는 레이어들을 여러 타일로 쪼개고 각 타일을 다수의 레스터 스레드에게 보낸다.
레스터 스레드들은 각 타일을 레스터화하고 그 것들을 GPU 메모리에 저장한다.
컴포지터 스레드는 서로 다른 레스터 스레드들에 대해 우선 순위를 정할 수 있어서 화면 안에 보이는 (혹은 가까이 있는) 것들이 먼저 레스터될 수 있다.
타일들이 레스터되면, 컴포지터 스레드는 쿼드 군집(draw quads) 라 하는 타일 정보를 모아 컴포지터 프레임을 생성한다.
드로우 쿼드 | 메모리에서 타일의 위치 및 페이지 합성을 고려하여 타일을 그릴 페이지의 위치와 같은 정보를 포함합니다. |
컴포지터 프레임 | 한 페이지의 프레임을 나타내는 쿼드 군집의 컬렉션입니다. |
컴포지터 프레임은 IPC를 통해서 브라우저 프로세스에게 넘어간다.
즉, 조각조각 내서 IPC로 넘긴다. 이해하면서 내 멘탈도 지금 조각조각 나고 있다.
이 때, 다른 컴포지터 프레임이 브라우저 UI 변화에 따라 UI 스레드에 의해 혹은 확장 기능에 대한 다른 렌더러 프로세스들에 의해 추가될 수 있다. (GPU process는 하나니까.. 근데 이런 경우가 언제 있는지는 잘 모르겠다)
이러한 컴포지터 프레임들은 GPU에게 보내져 화면에 보여진다. 만약 스크롤 이벤트가 발생하면, 컴포지터 스레드는 GPU에게 보내질 다른 컴포지터 프레임을 생성한다.
이러한 방식의 장점은 메인 스레드의 개입 없이(reflow와 repaint 없이) 컴포지팅이 GPU에 의해 수행된다는 것이다.
현재 CSS의
- opacity
- translate
- rotate
- scale
- will-change
옵션이 GPU 가속을 지원한다.
그리고 전 글에도 적었지만, 무분별한 GPU 남용은 오히려 해가 된다.
보통은 잘 바뀌지 않는 값과 에니메이션을 레이어를 분리해 지정하는게 좋다.
(The Animation Process From 1938)
이 영상에서는 배경은 그대로 두고 앞에서 움직여야 하는 전경만 별도의 셀로 만들어서 프레임을 촬영한다.
-> 크기, 내용은 그대로고 이동(에니메이션)만 하는 element를 지정하는게 좋다
-> 작을수록 좋다
함께 읽어보면 좋을 글)
Threaded GPU Rasterization
Threaded GPU Rasterization crbug.com/454500 State of GPU Rasterization (M-42)
docs.google.com
https://web.dev/why-speed-matters/
속도가 왜 중요합니까?
사용자 경험에서 속도는 중요한 역할을 합니다. 모바일 속도로 인한 지연은 실망스러울 뿐만 아니라 비즈니스 결과에도 부정적인 영향을 미칠 수 있습니다.
web.dev
빠른 로드 시간
사이트 성능을 높이기 위한 기술.
web.dev
reference
https://d2.naver.com/helloworld/5237120
https://d2.naver.com/helloworld/59361
Vanilla Javascript로 가상돔(VirtualDOM) 만들기 | 개발자 황준일
Vanilla Javascript로 가상돔(VirtualDOM) 만들기 본 포스트는 React와 Vue에서 사용되고 있는 가상돔(VirtualDOM) 직접 만들어보는 내용이다. 그리고 이 포스트를 읽기 전에 Vanilla Javascript로 웹 컴포넌트 만들
junilhwang.github.io
https://developers.google.com/web/updates/2018/09/inside-browser-part3?hl=ko
모던 웹 브라우저 들여다보기 (파트 3) | Web | Google Developers
브라우저 렌더링 엔진의 내부 작동 방식
developers.google.com
https://velog.io/@itssweetrain/Reflow-%EC%99%80-Repaint
성능 최적화와 관련해서 : reflow 와 repaint
이렇게 렌더 트리 생성이 끝나면 배치가 시작된다. 레이아웃 단계는 트리를 생성하면서 계산된 노드와 스타일을 기기의 뷰포트에서 어떤 위치에서 어떻게 보이도록 하는지 결정하는 단계입니
velog.io
https://d2.naver.com/helloworld/2061385
프론트엔드 개발자를 위한 크롬 렌더링 성능 인자 이해하기
3년 전에 개인 블로그에 적었던 글이라 이미 보신 분들도 있으리라 생각됩니다만, 미디엄에서 이전의 기술관련 글들을 같이 관리할 겸 같이 포스팅하기로 하였습니다. :)
medium.com
'Front-end > 브라우저' 카테고리의 다른 글
Cross-Origin Read Blocking(CORB) (3) | 2022.02.04 |
---|---|
크롬 브라우저의 이벤트 핸들링 처리 (0) | 2022.02.01 |
[운영체제] 크롬 웹 브라우저의 구조 (프로세스 & 쓰레드) (0) | 2022.01.30 |