2020. 11. 4. 20:30ㆍ웹 프론트엔드 깊게 이해하기/성능 최적화
이 글은 아래 글과 영상을 바탕으로 작성되었습니다.
주요 렌더링 경로 | Web | Google Developers
https://www.youtube.com/watch?v=G1IWq2blu8c
Course Introduction - The Critical Rendering Path - Browser Rendering Optimization
들어가기
네이티브 앱만큼의 성능을 내는 웹앱을 만드는 방법으로 자바스크립트 프레임워크를 활용한 SPA 가 대두되었지만, 제가 경험한 바에 의하면 꼭 SPA 를 쓴다고 해서 기존의 웹앱보다 성능이 더 빠른 웹앱을 만들었다는 느낌이 들지는 않았습니다. 애니메이션은 여전히 툭툭 끊기고, 요소들은 깜빡거리고, 갑작스레 렉이 걸리고, 로딩 속도는 여전하거나 오히려 더 느려진 것 같은 느낌이 들고는 했죠.
비록 기존의 자바스크립트를 통한 방법 보다는 리렌더링 성능이 빨라진 것은 사실이지만, 그것과는 별개의 기본 렌더링 성능을 보장하지 못했기 때문에 이런 문제가 발생하는 것이라고 저는 결론을 내렸습니다.
따라서 이 시리즈에서는 브라우저의 UI 를 렌더링과 관련된 모든 과정을 살펴보고 그 프로세스를 염두에 둘 때, 우리는 어떤 식의 코드를 짜야 하는지를 알아보도록 하겠습니다. 그럼 가장 먼저, 브라우저가 웹페이지를 렌더링하는 주요 과정을 살펴봅시다.
주요 렌더링 경로
자원 다운로드
브라우저는 외부에서 필요한 파일들을 불러와서 렌더링에 활용하지만, 가장 처음 다운로드하는 파일은 html 파일입니다. 브라우저는 html 태그들 중 <script> 태그, <link> 태그, <img > 태그를 발견하면 그 태그 안의 링크를 읽어들여서 그 파일을 다운로드 하는데 즉, 위에 보이는 html을 제외한 다른 파일들은 html 을 브라우저가 위에서 아래로 쭉 읽으면서 파싱하는 과정에서 받아온 파일들인 것입니다.
CSSOM 트리 구성
브라우저는 html 을 읽어가던 중 style 태그를 만나면, 혹은 css 파일을 가져오는 link 태그를 만나면 그 내용을 바탕으로 CSSOM 트리를 구성합니다. css 내용 중 선택자가 노드가 되고, 그 선택자에 지정된 css 속성이 그 노드의 속성이 됩니다. 만일 하위 선택자, 자식 선택자를 지정한 부분을 만나게 되면, 브라우저는 CSSOM 트리에 해당 내용을 반영하여 부모-자식 관계를 구현합니다.
DOM 트리 구성
브라우저는 계속해서 태그들을 읽어들이면서 DOM 트리를 구성해나갑니다. 브라우저는 우리가 작성한 HTML 파일 바탕으로, 흔히 알려져 있는 트리 자료구조의 형식으로 객체들을 만들어 부모-자식 관계로 연결시키고 메모리에 저장하는 것입니다.
스타일링
파싱을 통해 DOM 트리와 CSSOM 트리 모두 생성이 끝나면 브라우저는 이 둘을 서로 매칭시키는 작업에 돌입합니다. 각각의 태그마다 어떤 스타일링이 적용되어야 하는지가 여기서 결정되는 것입니다. 이런 매칭 과정은 또 하나의 트리를 만들어내는데 이를 '렌더(Render) 트리'라고 부릅니다. 이때 하나의 DOM 트리 노드(태그)의 스타일링은 그 노드에 적용할 수 있는 일반적인 규칙(그 노드보다 상위에 있는 노드의 스타일)를 먼저 적용한 후, 그 노드를 위한 더 구체적인 규칙(레벨을 낮춰가면서 발견하는 그 노드를 위한 스타일)을 적용하는 '하향식'으로 적용됩니다. 이를 이해하면 보면 CSS 스타일링의 우선순위를 더 잘 이해할 수 있고, 왜 CSSOM이 트리구조를 가져야 하는지를 알 수 있습니다.
참고로 알아둘 수 있는 것은 렌더 트리는 오직 렌더링에 필요한 노드들만을 가지게 된다는 것입니다. DOM 과 CSSOM 을 매칭시키는 과정에서 브라우저는 렌더링에 필요없는 태그와 스타일은 자동적으로 제외시킵니다. 참고로 css 속성 중 display: none 을 만나면 브라우저는 그것이 적용되는 노드 또한 렌더링 트리에서 제외시킵니다. 자세한 매칭 과정은 아래와 같습니다.
- DOM 트리 중 루트 노드부터 시작해서 방문을 시작합니다.
- 방문 할때마다 CSSOM 트리를 순회하면서 앞에서 말한 '하향식'으로 스타일 속성을 부여하거나 덮어씌우면서 렌더 트리의 노드를 하나하나 붙여나갑니다.
- 만일 <script> 태그와 같은 렌더링에 필요없는 태그나, display: none 으로 지정된 태그가 있다면 그 노드는 렌더 트리에 추가시키지 않습니다.
- html 상에는 pseudo 엘리먼트들이 없지만, CSS 상에서 이들이 명시되어 있다면 렌더트리에는 이들이 노드로써 붙여집니다(다만 해당 pseudo 엘리먼트의 content 속성이 명시되어 있어야 합니다!).
위의 과정을 생각해볼 때, 렌더 트리 생성 작업은 DOM 이나 CSSOM 트리가 클수록 오래 걸릴 수밖에 없으며 결과적으로 렌더 트리 또한 비례하여 커질 것임을 알 수 있습니다.
레이아웃
렌더 트리가 생성되었다면 브라우저는 이것을 바탕으로 본격적인 연산에 돌입합니다. 렌더 트리에는 각각의 노드(태그)들의 순서와 관계, 너비, 높이, 각종 스타일링에 필요한 수치들이 담겨있기에 브라우저는 이 내용들을 가지고 화면을 어떻게 그려낼지에 대한 '설계도'를 작성할 수 있습니다. 이때 브라우저는 렌더트리에 붙여진 스타일링 속성 중 px 이 아닌 다른 단위나 %, vh 와 같은 상대적 단위를 실제 px 로 계산해서 설계도에 반영하게 됩니다. 전체 렌더링 프로세스 중, 이 과정이 가장 무거운(연산이 많은) 작업입니다.
*브라우저 화면을 늘리거나 줄여서 어느 요소의 크기가 상대적으로 줄거나 늘어난다면 그때마다 레이아웃 과정이 반복되는 것입니다.
페인트
이제 생성된 설계도를 가지고 브라우저는 직접 화면상에 그림을 그리기 시작합니다(더 엄밀히 말하자면 화면상의 모든 픽셀 하나하나에 색을 입힙니다). 이 과정은 레이아웃 과정에서 구한 벡터를 픽셀상에 나타내는 과정이며 이를 래스터화라고 부르기도 합니다. 정말 중요한 점은 이 '그리기' 과정이 마치 포토샵처럼 레이어 단위로 수행된다는 것입니다. 레이어는 기본적으로는 하나지만 transform 속성이나 position: absolute 속성과 같은 스타일 속성이 부여된 렌더 트리의 노드가 있다면 그 노드들은 별개의 레이어에 속하게 됩니다. 지금까지의 모든 과정은 CPU 가 처리하는 일이었습니다. 이렇게 얻어진 레이어들을 GPU 에게 전달하면, 페인트 과정은 끝나게 됩니다.
합성 & 렌더
이제 GPU 가 생성된 레이어를 하나로 합성하여 스크린에 붙이면 렌더링이 완전히 끝나게 됩니다. 레이어가 많을 수록, 합성이 오래걸리기 때문에 특정한 이유가 있지 않다면 레이어 수를 많게 하는 것은 좋은 선택이 아님을 알 수 있습니다. 사용자는 이 시점에서야 웹 페이지를 볼 수 있게 됩니다.
'웹 프론트엔드 깊게 이해하기 > 성능 최적화' 카테고리의 다른 글
스타일과 레이아웃 과정 최적화 (0) | 2020.11.17 |
---|---|
자바스크립트를 활용한 인터랙션 성능 최적화 (0) | 2020.11.17 |
웹 성능 최적화의 척도(RAIL) (0) | 2020.11.16 |
웹 프론트엔드 성능 최적화(2) - 리렌더링 과정의 이해 (0) | 2020.11.09 |
크롬 개발자 도구를 사용한 성능 측정 (0) | 2020.10.22 |