하드웨어 가속에 대한 이해와 적용
전통적으로 브라우저는 웹 페이지 콘텐츠의 렌더링을 대부분 CPU에 의존했습니다. 하지만 최근에는 휴대용 기기에도 GPU(graphics processing unit)가 기본으로 포함되고 비디오나 3D 그래픽과 같은 콘텐츠의 사용이 늘면서 GPU를 활용해 웹 페이지의 콘텐츠를 렌더링하는 방법을 고민하게 됐습니다. GPU를 렌더링에 활용하는 하드웨어 가속을 사용하면 성능 향상이라는 이점을 얻을 수 있지만 무분별하게 사용하면 오히려 해가 될 수도 있습니다.
이 글은 하드웨어 가속의 기본적인 이론과 하드웨어 가속을 사용하는 방법을 설명하고, 하드웨어 가속을 사용할 때 고려해야 할 사항과 주의 사항을 살펴봅니다. 마지막으로 새로운 CSS 모듈 명세인
will-change
속성으로 더욱 효율적으로 하드웨어 가속을 사용하는 방법을 설명합니다.하드웨어 가속
하드웨어 가속은 GPU를 활용하기 때문에 GPU 가속이라고도 부른다. 먼저 하드웨어 가속의 기본 이론을 살펴보고 하드웨어 가속을 사용하는 방법을 설명하겠다.
하드웨어 가속이란?
하드웨어 가속을 이해하려면 브라우저가 웹 페이지를 화면에 렌더링하기까지의 과정을 이해해야 한다.
HTML 소스가 로딩되고 파싱되면 브라우저는 태그를 DOM 트리로 구성한다. DOM 트리는 화면에 표현되는 요소와 화면에 표현되지 않는 요소로 이루어져 있다. 예를 들어
<head>
나 <script>
등은 화면에 표현되지 않는 DOM 트리의 요소다.
브라우저는 화면에 표현되는 요소를 RenderObject 트리로 구성한다. RenderObject 트리의 노드는 z-index 속성이나 중첩 등을 처리하기 고안된 RenderLayer에 대응된다.
RenderLayer 가운데 실제 화면에 출력돼야 하는 노드는 다시 GraphicsLayer를 생성한다. 최상위(root) 노드나
<canvas>
, <video>
등이 GraphicsLayer를 생성하는 RenderLayer다.
하드웨어 가속은 GraphicsLayer 단위로 렌더링된 이미지를 GPU를 이용해 한 장의 이미지로 합성(composition)해서 화면에 출력하는 기술이다.
다음은 DOM 트리와 RenderLayer, GraphicsLayer의 관계를 나타낸 그림이다.
위 그림의 요소를 다시 설명하면 다음과 같다.
- DOM 트리: HTML 웹 페이지를 파싱한 트리로, HTML 문서의 각 요소를 쉽게 처리(추가, 삭제 등)하기 위하여 브라우저의 엔진이 사용하는 트리다.
- RenderObject 트리: DOM 트리로부터 만들어지는 트리로, DOM 트리의 노드 가운데 실제 화면에 표현되는 노드만으로 구성된 트리다.
- RenderLayer: 브라우저의 엔진이 하드웨어 가속 등을 처리하기 위해 사용하는 논리적인 레이어로, 각 RenderObject의 속성에 따라 RenderLayer에 할당된다.
- GraphicsLayer: 하드웨어 가속 처리를 위한 물리적인 레이어로, 레이어별 RenderObject를 GraphicsLayer 단위로 렌더링한 뒤 최종적으로 GPU를 통해 합성된다.
GPU에 이미지가 업로드되면 이미지는 3차원 그래픽 표면에 다양한 형태로 매핑될 수 있으며, 위치 이동이나 변형 등 다양한 작업을 이미지를 매핑하는 형태로 적용할 수 있다. 그래서 더 적은 비용으로 다양한 변형과 요소의 합성을 실행할 수 있게 된다.
하드웨어 가속은 웹 페이지의 일부 요소가 아닌 모든 요소를 GPU를 통해 합성하는데, 최상위 레이어(root layer) 개념이 있어서
<body> Hi </body>
와 같은 단순한 HTML을 처리할 때도 GraphicsLayer가 생성되고 GPU 텍스처(texture)로 업로드되어 합성한 다음 화면에 출력된다.텍스처는 간단하게 설명하면 메인 메모리(예: RAM)에서 비디오 메모리(예: GPU VRAM)로 이동되는 비트맵 이미지라고 생각하면 된다.
다시 정리하면 브라우저는 다음과 같은 과정을 거쳐 HTML 코드를 우리가 볼 수 있는 모습으로 브라우저에 출력한다.
- HTML 코드를 통해 DOM 트리를 구성하고, 화면에 표현되는 요소는 RenderObject 트리로 구성(필요한 경우 RenderLayer로 매핑)한다.
- RenderLayer 요소 가운데 GPU에 업로드되는 요소는 다시 GraphicsLayer로 분리되며, 레이어 각각을 독립적인 소프트웨어 비트맵으로 출력한다.
- GPU에 텍스처로 업로드한다.
- 다양한 레이어를 화면에 표시할 최종 이미지로 함께 합성한다.
합성 과정은 다음 그림과 같이 분리된 레이어가 합쳐져 하나의 완성된 모습으로 출력하는 과정이다.
Photoshop과 같은 이미지 편집 도구를 사용해 본 경험이 있다면, 여러 개로 분리된 레이어로 작업하고 하나의 이미지로 출력하는 모습을 상상하면 이해가 더 쉬울 것이다.
가속 대상
RenderLayer에 포함되는 요소 가운데 다음의 조건 중 한 가지에 부합되면 GraphicsLayer로 설정된다.
- CSS 3D Transform(translate3d, preserve-3d 등)이나 perspective 속성이 적용된 경우
<video>
또는<canvas>
요소- CSS3 애니메이션함수나 CSS 필터 함수를 사용하는 경우
- 자식 요소가 레이어로 구성된 경우
- z-index 값이 낮은 형제 요소가 레이어로 구성된 경우. 레이어로 구성된 요소의 위에 위치하면 해당 요소도 레이어로 구성된다.
가속 대상 확인 방법
예를 보면서 어떤 요소(또는 영역)가 GPU에 업로드되는지 확인해 보겠다.
다음과 같은 마크업이 있고, 렌더링된 화면은 아래와 같다.
<div class="cds_area">
<div class="pchl">...</div>
<div class="pchl">...</div>
<div class="pchl">...</div>
<div class="pchl">...</div>
<div class="pchl">...</div>
</div>
패널을 감싸고 있는 상위 노드에 CSS 3D 속성을 적용해 하드웨어 가속 대상이 되도록 설정한다.
<div class="cds_area" style="transform:translateZ(0);">
Show composited layer borders 옵션
Chrome 개발자 도구의 Show composited layer borders 옵션은 웹 페이지에 있는 레이어 영역과 레이어가 어떻게 나뉘어 GPU에 업로드되는지 확인할 수 있게 하는 옵션이다.
Chrome의 개발자 도구에서 Rendering 탭을 선택하고 Show composited layer borders를 선택한다.
화면을 확인하면 파란색의 그리드와 노란색 선이 나타난다.
- 파란색 그리드는 하나의 큰 레이어를 업로드해야 하는 경우 타일 형태로 나누어 업로드되는 각각의 영역을 의미한다.
- 노란색 선 영역은 자체 레이어(GraphicsLayer)를 갖는 요소를 의미한다.
위의 화면에서는 페이지에 한 개의 레이어가 있고 레이어는 파란색 테두리로 나뉘어진 타일의 형태로 GPU에 업로드된다는 것을 알 수 있다.
Layers 탭
Chrome 개발자 도구의 Layers 탭에서는 페이지에 구성된 레이어를 확인할 수 있다. 다음 그림에서 요소가 GPU에 업로드됐음을 확인할 수 있다.
Chrome 개발자 도구에서 Layers 탭이 안 보이면 다음과 같이 설정한다.
1. Chrome의 주소 창에chrome://flags/#enable-devtools-experiments
를 입력해 설정 페이지를 연다.
2. 설정 페이지의 개발자 도구 실험을 사용합니다.에 있는 사용을 클릭한다.
3. Chrome을 다시 시작한 다음 개발자 도구의 설정 창에서 Experiments를 클릭한다.
4. Layers panel을 선택하고 개발자 도구를 닫았다가 다시 열면 Layers 탭을 선택할 수 있다.
하드웨어 가속 사용 시 고려 사항
하드웨어 가속을 사용하면 웹 페이지의 렌더링 속도가 빨라지지만 잘못 사용하면 오히려 렌더링 속도가 느려지거나 브라우저에 문제가 일어날 수 있다. 하드웨어 가속을 사용할 때 주의할 점과 고려할 사항을 알아보겠다.
주의 사항
하드웨어 가속을 사용하면 다양한 성능 향상을 기대할 수 있지만, 그렇다고 모든 요소를 대상으로 적용하면 안 된다. 하드웨어 가속 대상을 지정할 때 다음의 사항을 기억하기 바란다.
- 무분별한 하드웨어 가속은 오히려 브라우저를 느리게 한다.
- 요소에 하드웨어 가속 속성이 부여되면 즉시 대상 영역이 GPU에 업로드되며, 이때 업로드되는 영역이 크면 화면이 깜빡이는 현상이 발생될 수 있다.
- 요소에 하드웨어 가속 속성이 부여되면 레이어로 분리되며, 레이어는 변경되는 내용이 없는 한 요소를 GPU 메모리에 다시 업로드하지 않는다.
- 하드웨어 가속 속성을 사용한 요소의 내용이 변경되면 GPU 메모리가 갱신되므로 요소의 내용을 미리 변경한 다음 하드웨어 가속 속성을 부여한다.
- 성능이 낮은 기기에서 하드웨어 가속을 사용하면 오히려 성능 저하를 가져올 수 있다.
적용 시 고려 사항
하드웨어 가속을 사용할 때는 다음과 같은 점을 고려한다.
- 하드웨어 가속을 적용하는 요소의 크기는 작을수록 좋고, 요소의 개수는 화면에서 5~6개로 구성하는 것이 좋다.
특히, 요소의 속성값에 따라 요소의 영역이 커질 수 있기 때문에 주의해서 적용해야 한다. 예를 들어text-indent
나left
같은 속성에-999em
이나-9999px
과 같이 화면 영역을 지나치게 벗어나게 값을 설정하면, 콘텐츠 영역의 크기가 늘어나고 하드웨어 가속에 의해 구성된 레이어도 커지게 돼 불필요한 메모리를 사용하게 된다. - DOM 요소의 내용이 자주 변경되지 않는 영역에 하드웨어 가속을 적용한다.
내용 변경이 아닌 이동이나 크기 변경이 자주 발생하는 영역에 하드웨어 가속을 적용하고, 이동이나 크기 변경은transform
속성을 사용한다. - 기기에 따라 선별적으로 하드웨어 가속을 적용한다.
JMC(Jindo Mobile Component)는 기기가 하드웨어 가속에 적합한 기기인지 확인할 수 있게 useCss3d() 메서드를 제공한다.
// 하드웨어 가속이 적합한 기기면 true 값을 반환
jindo.m.useCss3d();
성능 개선 사례
하드웨어 가속을 잘못 적용해 문제가 일어났던 사례를 살펴보면서 하드웨어 가속을 어떻게 적용해야 효과적인지 알아보겠다.
상위 노드와 하위 노드 모두에 하드웨어 가속을 적용
다음의 사례는 하드웨어 가속을 잘못 사용해 iPhone 6 Plus에서 브라우저가 종료된 사례다.
문제가 일어난 페이지에서는 다음과 같이 상위 노드와 하위 노드에 모두 CSS 3D 속성을 적용했다.
<div class="flick-panel _top" style="... -webkit-transform: traslate3d(100%,0px,0px);">
...
<div class="wrap program _cardArea">
<div class="cds_area _infiniteCardArea">
<div class="pchl" style="...-webkit-transform: traslate3d(0px,0px,0px);">...</div>
<div class="pchl" style="...-webkit-transform: traslate3d(0px,104px,0px);">...</div>
다음은 Chrome의 개발자 도구에서 확인한 노드 구성이다.
Chrome 개발자 도구의 Layers 탭으로 확인한 레이어 구성은 다음과 같다.
각 패널이 레이어로 구성돼 있는 것을 알 수 있다. 제일 첫 번째 요소의 경우에는 레이어의 크기가 10,202 × 104픽셀이고 예상 메모리(Memory estimate)가 4MB다. 이런 상황이라 브라우저가 종료되거나 기기가 꺼졌다 다시 시작하는 현상이 자주 일어났다.
다음은 문제가 되는 부분을 해결하기 위해 상위 노드에만 CSS 3D 속성을 적용한 마크업이다.
<div class="flick-panel _top" style="... -webkit-transform: traslate3d(100%,0px,0px);">
...
<div class="wrap program _cardArea">
<div class="cds_area _infiniteCardArea">
<div class="pchl" style="...-webkit-transform: translate(0px,0px);">...</div>
<div class="pchl" style="...-webkit-transform: translate(0px,104px);">...</div>
다음은 Chrome의 개발자 도구에서 확인한 노드 구성이다.
Chrome 개발자 도구의 Layers 탭으로 확인한 레이어 구성은 다음과 같다.
상위 노드에만 CSS 3D 속성을 적용하면 모든 노드를 레이어로 구성했을 때보다 메모리를 더 적게 사용한다는 것을 확인할 수 있다.
잘못 구성된 레이어
다음은 일부 레이어가 과도하게 큰 영역으로 구성된 사례다.
왼쪽 화면에서 보듯이 첫 번째 레이어의 크기는 10,202 × 104픽셀이고 예상 메모리가 4MB다. 반면 오른쪽 화면에서 보이는 두 번째 레이어의 크기는 414 × 104픽셀이고 예상 메모리는 168KB다.
이 둘의 차이는 왜 발생한 것일까?
첫 번째 레이어의 하위 요소에 적용된
text-indent
속성 때문이다. text-indent
속성이 다음과 같이 과도하게 큰 값으로 설정돼 있다..pchl .ic_up {
...
text-indent: -9999px;
...
}
다음은 Chrome 개발자 도구로 확인한 첫 번째 레이어의 CSS 속성이다.
하드웨어 가속 속성을 적용한 대상 요소의 하위 노드에 과도하고 크게 설정된 text-indent 속성으로 인해 전체 레이어의 크기가 지나치게 커져 브라우저가 종료되는 현상이 일어난 것으로 보인다.
이 속성을 제거하고 다시 확인하면 다음과 같이 레이어의 크기가 비정상적으로 설정되지 않는 것을 확인할 수 있다.
효율적인 하드웨어 가속
앞에서 살펴본 것과 같이 하드웨어 가속을 사용하면 렌더링 속도가 빨라지는 이점이 있지만 무분별하게 사용하거나 잘못 사용하면 문제가 일어날 수 있다.
이러한 문제를 해결하기 위해 새로운 CSS 명세인
will-change
속성을 사용하는 것을 고려할 수 있다.will-change 속성에 관한 더 자세한 내용은 다음 자료를 참고한다.
* W3C CSS Will Change Module Level 1
* Everything You Need to Know About the CSS will-change Property
* Bye Bye Layer Hacks, Hello will-change
will-change 속성
어떤 특정한 요소에 하드웨어 가속을 적용하고 싶을 때 사용한 방법이 레이어 핵(layer hack)이다. 레이어 핵은 하드웨어 가속을 위해 요소에 CSS 3D 속성을 부여해 요소를 GraphicsLayer로 만드는, 일종의 '핵(hack)'이다. 다음과 같이
transform
속성의 값으로 translate3d(0,0,0)
나 translateZ(0)
등을 지정한다.<div style="transform:translateZ(0);"> text </div>
그러나 레이어를 생성하는 비용이 크기 때문 실행 환경과 상관 없이 무조건 GPU에 텍스처를 업로드하게 하는 CSS 3D 속성 설정은 경우에 따라 심각한 성능 저하를 불러올 수 있다.
will-change 속성(초기에는
will-animate
속성이었다)은 미래에 변경이 발생할 속성에 관해 브라우저에 힌트를 주는 것이라고 생각하면 된다. 미리 힌트를 주면 브라우저가 해당 변경을 위한 작업을 사전에 최적화해 실제 변경이 발생될 때 더 빠르게 업데이트할 수 있고 더 효율적인 방법으로 GPU에 레이어를 생성할 수 있다. 이 속성은 기존의 레이어 핵을 대체하기 위한 목적도 있다.사용 방법
브라우저가
will-change
속성을 지원하는지는 다음과 같은 스크립트로 확인할 수 있다.var hintSupport = window.CSS && window.CSS.supports && CSS.supports('will-change', 'transform');
will-change
속성의 브라우저 지원 범위는 http://caniuse.com/#search=will-change를 참고한다.
빠른 시점(near future)에 변경일 일어날 속성을 지정한다.
.some { will-change : tansform, opacity }
1)
will-change
속성의 이름에서 알 수 있듯이 현재 변경이 발생하고 있는 시점이 아닌, 발생할(will) 시점에 속성을 지정해야 한다. 따라서 다음과 같이 지정하는 것은 피해야 한다./* 나쁜 예: 변경이 발생되는 시점에 속성을 지정 */
.element:hover {
will-change: transform;
transition: transform 2s;
transform: rotate(30deg) scale(1.5);
}
2) 변경이 완료된 이후에는 속성을 제거한다.
일반적으로
will-change
속성으로 지정되는 변경 작업은 많은 비용과 리소스를 사용하는 작업이 대부분이다. 최적화 이후 브라우저의 일반적인 동작은 최적화를 위해 수행된 작업을 제거하고 다시 원래 상태로 돌아가는 것이다.
그러나
will-change
속성은 이러한 기본적인 브라우저의 동작을 덮어쓰고 최적화된 작업을 일정 시간 동안 내부적으로 유지하기 때문에 불필요하게 오래 유지되지 않도록 작업이 끝난 이후에는 속성을 제거해야 한다.var el = document.getElementById('element');
el.addEventListener('mouseenter', hintBrowser);
el.addEventListener('animationEnd', removeHint);
function hintBrowser(event) {
event.target.style.willChange = 'transform, opacity';
}
function removeHint(event) {
event.target.style.willChange = '';
}
많은 자료에서
will-change
속성을 적용한 다음 꼭 제거하라고 하는데(http://www.sitepoint.com/introduction-css-will-change-property/#always-remove-will-change) 상황에 따라 변경이 완료된 이후 제거하지 않고 유지하는 것이 필요할 때도 있다.
예를 들어 사용자가 미래의 시점에 동일한 액션을 반복해서 수행할 것으로 예상되거나 페이지에서 계속 사용되는 인터랙션 등에서는 속성을 제거하고 다시 설정하는 것보다 제거하지 않고 계속 유지하는 것이 더 나은 성능을 얻을 수 있다.
마치며
이 글을 통해 하드웨어 가속은 무엇이고 어떻게 적용할 수 있는지 알아보고, 잘못 사용하면 심각한 문제가 일어날 수도 있다는 것을 살펴봤다.
프런트엔드 성능은 지속적인 관심을 두고 꾸준히 개선해 나가야 하기 때문에 한번 적용했다고 해서 끝나는 것이 아니다. 조금이라도 더 나은 성능을 얻을 수 있다면 계속 시도하고 적용하려는 노력이 필요하다. 이 글을 통해 많은 분들이 더 나은 성능을 갖는 웹을 만들 수 있기를 바란다.
댓글 없음:
댓글 쓰기