배달의 민족으로 유명한 우아한 형제들에서 후원을 하여 우아한 형제들 키친에서 세미나가 진행되었다. 행사 장소가 석촌 호수 주변이었는데, 바로 앞에 롯데월드가 있어서 한 10여년 만에 멀리서나마 롯데월드를 볼 수 있었다.
장소 찾기가 힘들어서 우연찮게 우아한 형제들 사무실까지 가보게 되었는데 여기저기 센스있는 문구들이 눈에 띄었고, 근무환경이 매우 좋아보였다. 우아한 형제들 키친은 옆 건물 지하에 위치해 있었고, 직원들이 식사를 하는 식당이었다. 까페 같은 느낌으로 잘 꾸며져 있었고, 직원들의 복지에 신경을 많이 쓰는 지 다트 기계와 커피 머신이 놓여져있었다.
이번 세미나의 주제는 웹 프론트엔드 개발을 하면서 성능 최적화와 관련된 내용들이었다. 많은 사람들이 참석하여 좌석이 거의 가득 찼었다. 에어컨이 엄청 빵빵하게 틀어져있었는데 하필 에어컨 바로 아래에 앉아서 1시간 동안 떨면서 강연을 들었다. 아직도 두통이…
CPU와 GPU
응용프로그램의 실행 성능은 주요 기능을 수행하는 시간과 그래픽 출력 시간으로 결정된다. 이 작업들을 CPU 혼자서 담당하게 되면 작업량에 따라 자원을 많이 사용하게 되고, 그만큼 다른 작업들은 block 될 수가 있다. 더군다나 그래픽 출력에는 많은 자원이 필요하다. 주방을 예로 들면 지배인, 서빙, 주방장의 역할을 혼자서 담당하게 되면 그만큼 주문이 밀리게 될 것이고, 손님은 계속 기다려야 하는 상황이 된다. 역할을 분담하게 되면 지배인은 주문을 받아 주방에 알리기만 하면 되고, 주방에서는 전달받은 메뉴에 대해 요리에만 전념하면 되므로 작업을 효율적으로 진행할 수가 있다. 이와 마찬가지로 CPU에서 화면에 어떻게 그릴 것인지를 판단한 후에 GPU에 이 작업을 위임하면 화면에 그리는 작업은 GPU가 전담하여 그만큼 CPU가 받는 부담을 덜어줄 수가 있다.
CPU와 GPU는 전혀 다른 메모리 공간을 사용하는데, CPU는 Main Memory를, GPU는 Video Memory를 사용한다. CPU가 Main Memory를 사용하여 어떻게 그릴 것인지를 판단하면 이 정보를 GPU에게 전달해야 하는데 이 때 전달해주는 역할을 BUS가 담당한다. BUS를 통해 CPU로 부터 명령을 받은 GPU는 Video Memory를 사용하여 그리는 역할을 하게 된다.
CPU가 알려주지 않으면 GPU는 그릴 정보에 대해 알 방법이 없으므로, 그려야 하는 정보가 수정되면 반드시 CPU는 BUS를 통해 GPU에게 알려야하는데, 이 BUS의 전송 속도가 CPU와 GPU의 연산속도보다 현저하게 느리기 때문에 병목구간이 될 수 있다. (CPU와 GPU간의 데이터 전송 시간은 “데이터의 크기 / BUS 속도”로 결정) 그러므로 그려야하는 정보가 자주 변경이 된다면 그 때마다 CPU가 GPU에게 데이터를 전달하면서 성능 저하가 발생하기 때문에 변경할 부분을 최소화 하는 것이 중요하다.
또한, Main Memory와 Video Memory는 모두 한계가 있기 때문에 메모리를 모두 사용하게 되면 기존에 메모리에 할당되어 있던 것들을 해제하고, 추가 작업을 위한 공간을 할당해야 한다. 그러므로 메모리 한계로 인한 성능 저하가 발생할 수가 있다.
GPU에서 일어나는 일들
간단하게 3D 그래픽스에 대한 개념을 살펴보면, 우선 Vertex와 Polygon이라는 것이 있다. Vertex는 공간 좌표를 의미하고, 이 좌표들을 이어서 하나의 도형을 만들게 되는데, 이 때 만들어진 도형이 Polygon이다. 다음으로 Texture Mapping 작업이 수행되는데, Texture는 그려질 이미지를 의미하고 이 이미지를 Polygon에 씌우는 작업을 Texture Mapping이라고 한다. 이러한 작업들을 GPU가 수행하게 되는데 Texture Mapping을 수행하며 해당 도형을 회전, 확대, 축소, 기울임 등의 변형(Transform)을 빠르게 적용 할 수 있다. 이 과정을 요약해보면 Vertex가 모여서 Polygon에 Texture를 입히고, 디스플레이에 이미지로 출력하게 된다. (이 과정에서 당연히 Video Memory를 사용한다.)
GPU가 잘하는 것과 못하는 것
하드웨어 가속 렌더링의 성능 최적화를 위한 첫걸음은 GPU가 잘하는 것과 못하는 것을 이해하는 것이다.
잘하는 것
위에서 설명했다시피 GPU는 수신된 데이터로 무언가를 그리는데 적합하다. 텍스쳐를 가지고 이미지를 빠르게 출력하는 것이 가능하고, 한번 그렸던 텍스쳐에 대해서는 다시 요청하지 않고 재활용하여 성능 저하를 방지한다. Transform 작업(회전, 확대, 축소, 기울임 등)을 동시에 처리하는 것에도 최적화 되어 있다.
못하는 것
비디오 메모리로의 전송속도가 느리다는 단점이 있다. 이는 위에서 설명했다시피 CPU에서 GPU로 데이터를 전달할 때 사용되는 BUS의 전송속도가 느리기 때문에 이 구간에서 병목이 발생하는 것이다. 데이터 전달 시에 GPU에게 내리는 명령과 버텍스 데이터는 큰 영향을 주지 않지만 압도적으로 텍스쳐 이미지에 대한 데이터 크기가 크기 때문에 이미지 변경이 자주 일어나면 그만큼 성능 저하가 발생하게 된다. 일반적인 이미지 뿐만아니라 텍스트를 포함하여 화면에 그려지는 모든 것들이 그려야하는 요소들이다.
또한 CPU 처리 시간도 큰 이슈가 된다. GPU의 데이터는 CPU에서 생성된 후에 전송이 되기 때문에 GPU의 자원이 아무리 여유롭다고 해도 CPU가 바쁘면 그만큼 로딩 속도가 느려지게 된다. 이 내용과는 무관한 내용으로, 구글에서 개발한 webp라는 이미지 포맷이 있다. 이 포맷은 jpg나 png와 같은 이미지 포맷보다 성능이 더 좋다고 하지만 이를 지원하지 않는 다면 CPU가 해당 포맷을 파싱하여 비트맵 형태로 GPU에게 전달해주어야 하므로 CPU의 부담이 커지게 되고, 그만큼 성능 저하가 발생하게 된다.
웹 페이지의 렌더링 (크롬)
웹 페이지는 파싱을 통해서 HTML 태그들은 DOM tree의 형태로 메모리에 적재되고, css의 내용들도 Styles struct로 메모리에 적재된다. 이 후 DOM tree를 Render tree로 생성한 후에 각 노드들을 개별적인 이미지로 생성한다. 트리 구조 및 스타일에 따라 이미지를 배치 및 합성하여 실제 디스플레이에 출력하는 과정을 거치게 된다.
여기서 Render tree의 각 노드들을 이미지로 생성하는 과정에서 사용되는 이미지 단위의 요소들을 레이어라고 한다. 웹 페이지에서 이미지를 출력하는 과정을 보면 모든 이미지가 준비된 후에 출력되는 것이 아니라 부분부분 로딩이 되는 것을 볼 수가 있는데 여기서 부분부분 출력되는 요소들이 레이어이다. Render tree를 통해 생성된 이미지 레이어들이 텍스쳐로서 GPU에 업로드 되고, 레이어들을 배치/합성 하여 최종적인 웹 페이지가 표현된다. 주의할 점은 레이어의 이미지 생성은 CPU가 담당한 다는 것이다. 이렇게 생성된 레이어들을 배치하고 합성하는 과정을 Composite 이라고 한다.
Reflow & Repaint 문제
레이아웃의 변경이 트리를 따라 전파된다. 많은 경우에 레이어 이미지 갱신이 필요하게 되는데 레이어 이미지가 변경되면 GPU의 Video Memory의 텍스쳐 갱신이 필요하다. 그러므로 변경되는 정보를 CPU에서 계산하여 BUS를 통해 GPU에 알리고 해당 정보를 갱신하여 다시 그려주는 작업을 거쳐야 한다는 것이다.
정리
- DOM으로부터 노드들을 개별적으로 혹은 그룹지어 레이어 단위들로 분리
- 레이아웃을 계산하고 각 레이어들이 그려야할 영역의 크기, 위치 등을 계산
- 그려야할 영역 계산을 위해 CPU에 오버헤드 발생
- 레이어들 각각은 렌더링을 위해 비트맵으로 출력
- 레이어 이미지를 생성하기 위해 CPU에 오버헤드 발생
- 생성된 비트맵을 GPU에 텍스쳐로 업로드
- GPU의 비디오 메모리로 전송하는 오버헤드 발생
- 계산된 레이아웃으로 레이어의 텍스쳐 이미지들을 최종 스크린 이미지로 배치, 합성
크롬에서 DOM 노드가 레이어로 분리되는 조건들
- 3D 혹은 Perspective를 표현하는 CSS transform 속성을 가진 경우
- 하드웨어 가속 디코딩을 사용하는 <video> 엘리먼트
- 3D 컨텍스트 혹은 하드웨어 가속 2D 컨텍스트를 가지는 <canvas> 엘리먼트
- 투명도(opacity) 속성, transform 애니메이션의 사용
- 가속 가능한 CSS 필터를 가진 경우
- Compositing Layer를 하위 노드로 가진 경우
- 낮은 z-index를 가진 형체 노드가 Compositing Layer를 가진 경우
레이어로 분리되면 변경된 레이어만 다시 그리면 되므로 성능을 개선할 수가 있다. 하지만 레이어가 너무 잘게 쪼개지는 경우에는 그만큼 메모리를 많이 사용하게 되므로(레이어 분리는 필연적으로 텍스쳐 이미지 분리를 의미) 메모리 해제/할당에 대한 성능 저하가 발생할 수 있으므로 주의해야한다.
Worker
- 백그라운드에서 동작
- 모든 Worker는 자신만의 스코프를 가진다.
Web Worker
자바스크립트는 UI 스레드에서 동작하기 때문에 자바스크립트에서 루프를 돌게 되면 UI 동작도 block 된다. Web Worker는 UI 스레드와 비즈니스 로직을 위한 스레드를 분리하기 위한 것이 목적이다.
Shared Worker
다수의 브라우징 컨텍스트에서 접근할 수 있는 Worker
Service Worker
이벤트 드리븐 모델을 사용하고 있으며, 지속적인 백그라운드 처리를 한다. 여기서 지속적이라는 의미는 브라우저가 열려있지 않더라도 서버로부터 데이터를 전달 받을 수 있다는 것을 의미한다. (예를 들면 크롬에서 페이스북의 알림 기능)
원격 푸시 알림이나 백그라운드 동기화의 최초 진입점으로 사용하기 적합한데, 보안에 취약하기 때문에 Service Worker를 사용하려면 반드시 HTTPS를 사용해야 한다.
Service Worker를 사용하게 되면 캐시를 많이 활용하게 되는데 캐시를 사용하면 서버에서 업데이트 된 내용을 빠르게 반영할 수 없다는 단점이 있으므로 프레임 정보와 같은 변경될 소지가 적은 데이터들은 캐시를 활용하고(cache first), 컨텐츠와 같은 자주 변경될 소지가 있는 것들은 서버로부터 데이터를 받도록 하는 것이(network first) 좋다.
이 외에 유용했던 정보
- 크롬 개발자 도구
- Timeline
- Rendering
- Paint Falshin에 체크해보면 GPU가 다시그리는 영역 확인 가능
- Layer Borders를 체크하면 레이어 영역 확인 가능
- Profiler
- 프로파일링 시 Profiler와 Timeline 시점을 지정해주면 프로파일 가능
- 자바스크립트 함수로 제공됨
- css의 will-change 속성
- GPU에게 레이어로 잘라줬으면 좋겠다는 요청. 레이어로 구분될 수도 있고 안될 수도 있음
- link 태그의 rel 속성 (힌트 제공)
- dns-prefetch
- preconnect
- preload
- prefetch
- script의 defer, async 속성
- iframe의 data-src 속성
- iframe을 렌더링 하는 순간 자신의 페이지 로딩은 block되는데 이 속성을 이용하면 자신의 페이지를 우선적으로 로딩하고 이 후에 iframe을 로딩한다.
- HTTP2에서는 https만 사용가능하다.
참고
'Work > Conference' 카테고리의 다른 글
HashiCorp 밋업 - 당근마켓에서 Packer와 Vagrant 사용기 (0) | 2018.08.15 |
---|---|
Golang 밋업 - Bazel 소개와 카카오 게임즈 사례 (0) | 2018.08.14 |
SK Tech Planet 2015 참관 후기 (0) | 2015.10.08 |
Deview 2015를 다녀와서 (0) | 2015.09.15 |
GopherCon Korea 2015 컨퍼런스를 다녀와서.. (0) | 2015.08.16 |
댓글