브라우저 동작원리 PART 3

민토의 블로그·2023년 4월 9일
1
post-thumbnail

이 글은 'Inside look at modern web browser' 를 번역한 Naver D2 글을 일고 정리한 글이다.

이 글에서는 렌더러 프로세스가 어떤 과정을 거쳐서 페이지를 렌더링 하는지 과정과 어떤 성능 이슈가 있고 이를 해결하기 위해서는 어떻게 해야하는지에 대해서 다룰꺼다.

렌더러 프로세스는 하나의 탭에서 하는 모든 작업을 의미한다.

렌더러 프로세스는 메인 스레드에서 렌더링이 수행되고 추가적인 컴포지터 스레드, 레스터 스레드등을 활용해 부드럽게 렌더링 하는게 가능하다.

DOM 구축

페이지를 이동하는 네비게이션 실행 메세지를 렌더러 프로세스가 받고 HTML 데이터를 수신하기 시작하면 렌더러 프로세스의 메인 스레드는 문자열 HTML을 파싱해서 DOM 변환하기 시작한다.

참고로 HTML은 절대 오류를 반환하지 안는 언어이다.

웹 사이트는 일반적으로 CSS, JS나 이미지등 외부 리소스를 추가적으로 받아와서 렌더링을 한다.
렌더러 프로세스의 메인 스레드가 메번 필요한 리소스를 요청하면 속도는 굉장히 느릴꺼다. 그래서 프리로드 스캐너가 동시에 실행되어 HTML 문서에 태그나 등의 태그가 나오면 브라우저 프로세스의 네트워크 스레드에 요청을 보내는 방식으로 처리를 한다.

DOM 트리를 만드는 과정에서 Script 태그가 나오게 되면 HTML DOM 파싱을 일시적으로 중단하게 된다.
왜냐하면 JS에 의해서 DOM이 수정될 수 있기 때문에 기본적으로 JS를 다 받고와서 다시 DOM을 그린다.

이때 추가적으로 script 태그에 async나 defer 속성을 주어서 병렬적으로 HTML의 파싱을 멈추지 않는 방법이 있다.

스타일 계산

DOM 트리로는 웹 페이지의 모양이나 스타일을 알 수 없기 때문에 메인 스레드가 CSS를 파싱하고 각 DOM 노드에 해당하는 계산된 스타일을 확정한다.
참고로 아무 스타일도 주지 않는데 적용되는 h1 등의 태그들은 모두 브라우저별로 기본 스타일 시트가 있기 때문에 그렇게 적용되는거다.

레이아웃

그 다음 과정은 메인 스레드가 DOM과 계산된 스타일을 훑어가면서 레이아웃 트리를 만든다.
DOM 트리와 비슷하지만 실제로 화면에 보이는 요소만 레이아웃 트리에 포함시켜서 만들게 된다.
이 레이아웃 과정이 가장 어려운 작업이다. 폰트크기가 어느정도이고 폰트크기에 따라서 어디서 줄바꿈이 일어나고 등의 내용이 필요하기 때문에 쉽지 않은 작업이다.

페인트

DOM트리와 스타일, 레이아웃만을 가지고는 페이지를 렌더링 할 수는 없다.

요소의 크기와 모양 위치를 알더라도 각 요소들을 어떤 순서로 그려야하는지에 대한 기준과 판단이 있어야 한다.

그래서 메인 스레드에서 페인트 과정을 기록하기 위해서 레이아웃 트리를 돌면서 페인트 레코드를 기록한다.

레이어 나누기

그리고 어떤 요소가 어떤 레이어에 있어야 하는지 확인하기 위해서 메인 스레드는 레이아웃 트리를 순회하면서 레이어 트리를 만든다.

합성

이젠 우린 문서의 구조와 요소의 스타일 요소의 기하학적 속성 페인트 순서등을 알게된 상태에서는 화면의 픽셀로 변환하는 작업 즉 레스터화를 진행한다. (레스터 스레드에서 수행된다)

이 단계에서는 GPU가 많이 사용된다.

합성은 웹 페이지의 각 부분을 레이어로 분리해서 별도로 레스터화를 시키고 이를 컴포지터 스레드에서 별도로 웹 페이지로 합성하는 기술을 의미한다.

레이어 트리가 생성되고 페인트 순서가 결정되면 이제 메인 스레드는 해당 정보를 컴포지터 스레드에 넘긴다.

이후 모든 작업이 끝나면 합성된 프레임 IPC 통신을 통해 브라우저 프로세스에 전송한다.
여기서 스크롤 이벤트가 발생하면 컴포지터 스레드는 GPU로 보낼 다른 합성 프레임을 만든다.

스타일, 레이아웃, 페인트 성능

렌더링 파이프라인을 보면 각 각 단계가 그 이후 단계에 영향을 미친다는걸 알 수 있다.
예를들어 레이아웃 단계에서 수정이 발생하면 페인트 단계를 거쳐서 다시 렌더링 되는 페인트 레코드를 재생성 해야한다.

이런 작업을 사용자에게 어색하지 않게 보여주려면 1초에 60fps 즉 60번정도의 화면이 새로 그려져야 한다.
즉 style => layout => paint 과정이 1초에 60번 반복해야한다.

하지만 만일 몇번의 프레임에 위와 같은 과정에 의해서 페이지가 렌더링 되지 않는다면 사용자는 화면의 어색함을 느끼게 될 수가 있다.

만일 DOM 요소의 이동등의 이벤트가 발생한다면 위와 같은 작업을 반복하면서 화면을 새로 렌더링 하므로 사용자에게 버벅거리는 현상이 보일 수도 있다.

그리고 이런 모든 작업은 메인 스레드에서 수행이 된다.

이 때 메인 스레드에서 JS가 실행되면 렌더링이 막힐 수 있다.

성능 문제 해결

css 애니메이션 속성 별로 reflow와 repaint 의 차이가 난다.
즉 reflow는 layout 단계부터 paint 단계가 수행되고 repaint는 paint 단계만 다시 수행되는걸 의미한다.

여기서 reflow 단계가 repaint 단계보다 성능에 더 좋지 않기 때문에 이벤트 별로 속성을 보고 선택하는 식으로 성능을 개선할 수 있다.

이벤트중에는 합성 단계만 진행하는 이벤트가 가장 부드럽다고 볼 수 있다.

합성은 메인 스레드에서 하지 않고 컴포지터 스레드에서 진행하기 때문에 메인 스레드에도 영향을 주지 않기 때문이다.

두 번째로는 requestAnimationFrame 메서드를 사용해서 프레임마다 실행되도록 스케쥴 관리를 하는 방법이다.

참고

profile
블로그 이전했습니다. https://frontend-minto.tistory.com/

0개의 댓글