FrontEnd Roadmap 03 - 인터넷 파트3: 브라우저 Deep Dive(Render To View)

SANGHYUN KIM·2024년 12월 8일
1

frontend-roadmap

목록 보기
3/9

이번 글의 구조는 MDN의 “웹페이지를 표시한다는 것: 브라우저는 어떻게 동작하는가”와 chrome for developers의 “최신 웹브라우저 들여다보기”시리즈를 기반으로 작성되었습니다.

PRE: CPU와 GPU부터 시작해서 Process와 Thread까지

CPU

사진1: CPU

사진1: CPU

  • Central Processing Unit의 약자
  • “컴퓨터의 뇌”로 표현 가능
  • 다양한 일(대부분의 연산처리)을 하나씩 처리

GPU

사진2: GPU

사진2: GPU

  • Graphics Processing Unit의 약자
  • 간단한 일을 동시에 다중처리 가능

Process와 Thread

사진3: Process와 Thread

사진3: Process와 Thread

  • Process는 애플리케이션의 실행 프로그램
  • Thread는 process 내부의 있는 것으로 프로세스 일부를 실행

Process와 Thread가 일하는 방법:

사진4: OS가 메모리에 process를 할당

사진4: OS가 메모리에 process를 할당

  • 애플리케이션을 시작하면 메모리 일부분 구역에 process 생성
    • 특정 일을 위해서 process 내부에 thread 생성 가능
    • 애플리케이션 종료 시 할당된 구역을 제거

사진5: 다른 process와 IPC를 통해서 통신

사진5: 다른 process와 IPC를 통해서 통신

  • Process가 OS에게 다른 process가 필요하다고하면 메모리의 다른 부분에 process를 생성
    • 두 process간의 소통이 필요하다면 IPC(Inter Process Communication)을 통해서 통신
    • 다수의 애플리케이션은 멀티 프로세스를 통해서 한 쪽이 반응이 없다면 다른 한쪽을 사용하여 사용자 경험을 증진하는 방향으로 대부분 작성

Q. 멀티쓰레드면 다 좋은 거 아닌가요?

이 질문에 대한 궁금증을 풀기 위해 Inpa Dev의 “스레드를 많이 쓸수록 항상 성능이 좋아질까?”를 기반하여 작성했습니다. 해당 글에는 어떤 상황에 무엇이 부합한 지를 설명하고 있으니 궁금하면 직접 읽어보면 좋습니다.

(TLDR) 항상 좋지는 않다. 멀티 쓰레드는 공유하는 자원에 접근을 해야 하는데 그 작업 간의 병목이 일어나 싱글 쓰레드보다 성능이 떨어질 수 있다.

멀티 쓰레드는 공유 자원에 접근해야 하는데 데이터의 일관성과 정확성을 위해서 동기화 기법을 사용한다.
동기화 기법에는 뮤텍스(Mutex)나 세마포어(Semaphore)같은 기법이 있는데 이들은 한 번에 하나의 쓰레드만 공유 자원에 접근 가능하게 제한하여 일관성을 유지한다. 여기서 병목이 생기게되면 속도는 저하된다.

또한, CPU간의 데이터 일관성을 유지하기 위해 CPU간의 통신을 주기적으로 해줘야 하는데 이 글을 읽다보면 IPC 통신의 값이 비싼 것을 알 수 있다. 다수의 CPU간에 동기화는 결국 통신 비용의 증가를 발생한다

사진6: 메모리간의 데이터 일관성

사진6: 메모리간의 데이터 일관성

CPU 내부의 프로세스 또는 쓰레드는 서로 동기화를 하기 위해 중간에 저장하고 다른 곳으로 전환될 때 컨택스트 스위칭 오버헤드(context switching overhead) 비용이 발생한다. 중간에 잠깐 텀이 있기에 시간과 자원을 소모한다.

사진7: 컨텍스트 스위칭 오버헤드의 예시

사진7: 컨텍스트 스위칭 오버헤드의 예시

위 정보를 전부 총합해보면 컴퓨터의 멀티 태스킹은 우리가 생각하는 방향으로 진행되지는 않는다.

사진8: 사람이 생각하는 멀티태스킹과 컴퓨터가 하는 멀티태스킹

사진8: 사람이 생각하는 멀티태스킹과 컴퓨터가 하는 멀티태스킹

따라서, 만들고자 하는 서비스의 성격에 따라서 선택을 해야하기에 “멀티 쓰레드가 항상 성능이 좋다고 보장할 수 없다”.

Browser Architecture

사진9: Chrome 내부의 process 분류

사진9: Chrome 내부의 process 분류

브라우저를 제작하는데 process와 thread에 대한 기준이 없다. 참고하는 글이 Chrome 개발 블로그이기에 Chrome기준으로 보면 위 사진과 같이 다수의 process를 사용하며 탭 내부의 화면을 그리고 각 탭마다 별도의 process를 할당한다.

사진10: Chrome 브라우저화면에서 process의 역할

사진10: Chrome 브라우저화면에서 process의 역할

Process설명
Browser Processchrome 애플리케이션의 일부분(주소창, 북마크, 뒤로가기 앞으로가기 버튼)을 컨트롤. 또한, 보이지 않는 일부분(네트워크 요청, 파일)도 컨트롤
Renderer Process웹사이트가 표현되는 탭 내부의 모든 것을 컨트롤
Plugin Process웹사이트 플러그인을 관리
GPU ProcessGPU관련 일을 전부 담당

멀티 쓰레드(프로세스)가 항상 성능이 좋은 것은 아니지만 Chrome은 다음 이점을 가지기 위해서 멀티 프로세스를 채택함:

  • 각 탭마다 별도의 프로세스가 할당되기에 하나가 문제가 있더라도 나머지 탭은 사용 가능
  • 각 탭마다 영역을 분리했기에 데이터 접근은 해당 탭 내부에서만 접근 가능. 즉, 다른 탭에서 데이터 접근이 불가하므로 보안성 장점
    • 프로세스가 분리되어서 실행되기에 Same Origin Policy도 준수

A. 내비게이션

A-1. 네비게이션 단계

  1. 주소창 입력 및 데이터 판단:

    1. 유저가 주소창에 입력하면 입력값이 URL인지 검색인지 판별
  2. 내비게이션 시작

    1. 입력 값이 URL로 판별이 되면 로딩 스피너가 돌아가고 DNS조회 후 TLS를 통하여 HTTPS/HTTP 판별여부를 진행(FrontEnd Roadmap - 인터넷 파트2: 브라우저와 DNS: DNS(Domain Name System)란?)

      사진11: UI thread와 Network thread간의 통신으로 HTTP 통신 준비

      사진11: UI thread와 Network thread간의 통신으로 HTTP 통신 준비

  3. 응답 읽기

    사진12: TLS 이후 받게 되는 첫 응답값의 헤더(header)

    사진12: TLS 이후 받게 되는 첫 응답값의 헤더(header)

    1. 연결 성공 시, 데이터를 받기 시작하며 Browser Process안의 Netrwork thread가 data를 읽기 시작하며 필요에 따라서 MIME Type Sniffing(Axios문제 해결 당시 MIME Sniffing을 알게된 기록)

    2. 만약 HTML이면서 언전하다고 판단되면 renderer process에 넘기고 다운로드가 필요한 데이터라면 download manager로 전달

      사진13: HTML이 안전한 콘텐츠인지 확인

      사진13: HTML이 안전한 콘텐츠인지 확인

    3. 이 과정에서 SafeBrowsing 검사 진행을 하며 domain과 응답값이 블랙리스트에 있거나 Cross Origin Read Blocking(CORB)를 통해 같은 출처가 아니라면 warning page로 이동

  4. Render Process 찾기

    1. Network 스레드가 경로와 데이터가 안전하다고 판단된다면 UI 스레드에 안전한다고 전달하고 UI 스레드는 renderer process를 찾아서 데이터 전달
    2. 이 과정을 최적하하기 위해서 2번 단계인 “네비게이션 시작”에서 미리 병렬처리하여 이 과정이 바로 실행될 수 있게 준비
  5. 네비게이션 시작

    1. 데이터와 renderer process가 준비되었다면 browser process가 renderer process에 IPC 통신을 하여 신호를 전달하고 data stream(응답값들) 또한 전달

    2. renderer process가 전달받았다는 신호가 나오면 네비게이션은 종료되고 documnet 로딩 단계 시작

      사진14: Browser Process가 Renderer Process에게 신호 전달

      사진14: Browser Process가 Renderer Process에게 신호 전달

    3. 또한 이때, 주소창에 보안 표시와 UI가 반영

  6. 초기 렌더링 완료 후

    1. renderer process가 완료했다고 IPC 신호를 browser process에 전달하면 UI thread는 주소창에 loading spinner를 멈춤

      사진 15: 초기 페이지 완료 시 Renderer Process가 Browser Process에게 신호 전달

      사진 15: 초기 페이지 완료 시 Renderer Process가 Browser Process에게 신호 전달

A-2. 네비게이션 정리

  • 모든 단계는 Browser Process내부에서 진행
  • Browser Process내부에는 UI thread와 Network thread가 존재
  • 네비게이션 단계(thread 및 process 표시)
    • (UI thread)가 주소창 입력 확인
    • (UI thread → Network thread) 주소창에 있는 url을 DNS 확인 및 TLS 진행
    • (Browser Process → Renderer Process) 안전한 접근 확인 후 받은 응답값을 Renderer Process로 전달
    • (Renderer Process → Browser Process) 초기 페이지 로드 완료 시 다시 Browser Process에 통신하여 완료 신호를 보내고 로딩 스피너 멈춤

B. 웹사이트 렌더링

B-1. 렌더링 단계

탭 내부에 일어나는 단계는 전부 renderer process가 처리하며 주요 역할은 HTML, CSS, JavaScript를 해석하고 page로 만드는 것이다

  1. Parsing
    1. Render process가 데이터를 받기 시작하여 HTML을 먼저 Document Object Model(DOM)로 전환

      1. DOM을 만드는 이 단계에서 잘못 또는 오타로 보이는 tag들은 자동 수정
    2. DOM으로 전환 중 preload scanner 또한 작동을 하는데 와 태그를 만나면 미리 source를 불러오는 역할을 해줌

      사진16: DOM tree 변환 과정

      사진16: DOM tree 변환 과정

  2. Style (CSS 파일 읽기)
    1. CSS 파일의 읽고 정보를 획득
    2. CSS 파일이 없어도 각 브라우저 마다 tag의 기존 style이 있기에 적용
  3. Layout
    1. 해석한 DOM과 CSS 정보 기반으로 renderer process의 Main thread는 layout tree를 작성

      사진17: Layout Tree 변환 과정

      사진17: Layout Tree 변환 과정

      1. layout에는 각 노드의 xy 좌표, 박스 사이즈를 가지고 있으며 화면에 표시가 되는 요소(display: none과 같은 것은 없음. 가상 선택자는 포함)만 표현
  4. Paint
    1. layout tree는 보여지는 정보만 가지고 있을 뿐 화면을 그리는 순서를 가지고 있지 않음

    2. 따라서 이 단계에서는 Main thread가 layout tree를 보고 paint할 순서를 기록

      사진18: Paint Records 작성

      사진18: Paint Records 작성

  5. Compositing
    1. rasterize: layout tree에 있는 정보를 이제 screen에 pixel화 하는 것

    2. compositing:

      1. 정의: Compositor thread에서 페이지를 layer로 분리하고 각 개별로 rasterize하여 페이지를 조합하는 방식
    3. layer로 나누기 위해서 Main thread가 layout tree를 보고 layer tree를 생성

      사신 19: Layout Tree기반으로 Layer Tree 작성

      사신 19: Layout Tree기반으로 Layer Tree 작성

    4. layer tree와 paint 정보가 정해졌으면 Main thread는 Compositor thread에 해당 정보를 전달 하고 Compositor thread에서 layer를 잘게 조게고 Raster thread가 이어받아서 잘게 조게진 조각을 rasterize 후 GPU에 저장

      사진20: 화면을 tile로 분할하여 저장

      사진20: 화면을 tile로 분할하여 저장

    5. tile들이 rasterized되었다면 Compositor thread에서 tilte에서 drawer quads를 추합하고 compositor frame을 생성

      1. Draw quads: Contains information such as the tile's location in memory and where in the page to draw the tile taking in consideration of the page compositing.
      2. Compositor frame: A collection of draw quads that represents a frame of a page.
    6. compositor frame이 완성되면 IPC를 통해서 Browser Process로 넘겨주고 화면에 표시

      사진21: Tile들을 조합하여 화면에 표시

      사진21: Tile들을 조합하여 화면에 표시

B-2. 렌더링 정리

  • 모든 단계는 Renderer Process내부에서 일어난다
  • Renderer Process내부에는 Compositor thread, Raster thread, Main thread가 있다
  • 렌더링 단계(thread 및 process 표시)
    • (Main thread)HTML 파일을 DOM, CSS 파일은 CSSOM으로 전환
    • (Main thread) DOM과 CSSOM을 기반으로 Layout Tree 생성
      • Layout Tree에는 화면에 표시될 정보만 표출(display: none X)
    • (Main thread) Layout Tree를 보고 화면에 그려지는 단계를 작성하는 Paint Records 생성
    • (Main thread) Composition을 위해서 Layout Tree를 기반으로 각 Layer Tree를 생성
    • (Main thread → Compositor thread) 받은 Layer Tree 내부의 layer를 tile로 잘게 분리하고 우선순위 설정
    • (Compositor thread → Raster thread) tile들을 rasterize하고 GPU에 전달
    • (Compositor thread) rasterized 된 tile을 모으고 compositor frame 생성
    • (Render Process → Browser Process) IPC통신을 통해 compositor frame을 Browser Process로 넘겨주고 화면 표시

C. 렌더링 최적화

B-1의 Style, Layout, Paint 작업은 전부 일렬로 진행. 즉, 전 단계가 완료되어야 다음 단계가 실행된다.
또한 해당 작업들은 전부 Main thread에 서 진행이 된다.
따라서, 실행되는 부담이 크고 Main thread에서 작업이 최소화되어야 한다.

C-1. JavaScript 용량 파편화

JavaScript가 있으면 Main thread의 점유율을 가져가고 중간에 일을 멈추는 것을 알 수 있다. 따라서 화면을 조정하는 작업을 할 때(예를 들어서 animation 처리를 하는 경우) requestAnimationFrame()을 통해서 JavaScript를 파편화 시켜서 화면을 최적화 할 수 있다.

사진22&23: Page Jank 및 requestAnimationFrame() 사용 시의 현상

사진22&23: Page Jank 및 requestAnimationFrame() 사용 시의 현상

사진22&23: Page Jank 및 requestAnimationFrame() 사용 시의 현상

C-2. Reflow 와 Repaint

먼저 Reflow와 Repaint의 정의는:

  • Reflow: 레이아웃부터 다시 그리는 행위(Layout → Paint → Compositing)
  • Repaint: 페인트부터 다시 그리는 행위(Paint → Compositing)

각 재실행 작업들은 결국 Render Process가 다시 계산하는 작업을 실행하게 되며 Main thread의 점유를 하게 된다.
Main thread 점유 시 다른 일을 못 할 수 있기에 점유율을 최대한 줄이는 것이 좋다.

어떤 것들이 위 작업들을 유발할까?

  • Reflow: 윈도우 리사이징, JS를 통한 DOM 동적 변화 등등
  • Repaint: 스타일 변화(기하학적인 변화를 제외한 것들)

그러면 어떻게 최적화를 할 수 있을까?

  • width, height, padding, margin과 같이 레이아웃에 직접적으로 영향을 주는 작업 최소
  • transform과 opacity 속성 사용
    • reflow와 repaint가 일어나니 않는 속성이며 (will-change 속성 부여하여)GPU 가속을 사용 가능
  • display:none을 통하여 일괄적으로 숨김 후 일괄 수정 후 다시 화면 표시
    • 여러번의 reflow 또는 repaint보다 display 속성이 변경될 때 생기는 1번의 reflow 및 repaint 처리가 더 좋을 수 있음

이 글(CSS Triggers List – What Kind of Changes You Can Make)에서 CSS의 어떤 속성이 layout, paint, 또는 composite에 영향을 주는 지 확인이 가능하다.
업무 중 동적으로 변경되는 화면이 있고 시간 또한 있다면 참고하면서 만들기 좋을 것 같다.

참고자료

Inside look at modern web browser (part 1)  |  Blog  |  Chrome for Developers

웹페이지를 표시한다는 것: 브라우저는 어떻게 동작하는가 - 웹 성능 | MDN

🤔 스레드를 많이 쓸수록 항상 성능이 좋아질까?

브라우저는 어떻게 웹사이트를 화면에 표시할까요?

매끄럽게 동작하는 브라우저 만들기 - 렌더링 최적화 Reflow, Repaint

lists.w3.org

프론트엔드 개발자 면접 단골 질문 6 | 리플로우와 리페인트

CSS Triggers List - What Kind of Changes You Can Make

profile
꾸준히 공부하자

0개의 댓글