지금까지 브라우저가 화면을 세우고 다시 그리는 각 단계를 하나씩 파고들었다. 이번 편은 그 단계들을 하나의 흐름으로 다시 묶어, 화면이 사용자에게 부드럽고 빠르게 보이려면 전체가 어떤 조건을 만족해야 하는지를 다룬다. 개별 단계를 아무리 잘 다뤄도, 그것들이 정해진 시간 예산 안에 맞물려 돌아가지 않으면 사용자는 느리고 끊긴다고 느낀다.


렌더링 성능은 하나의 숫자로 말할 수 있는 것이 아니라, 서로 다른 국면의 여러 지표로 이루어진다. 처음 화면이 뜨기까지의 빠름, 화면이 움직일 때의 부드러움, 조작에 반응하는 즉각성이 각각 다른 문제다. 이 국면들을 구분해 다루어야, 어디를 개선해야 사용자의 체감이 좋아지는지를 정확히 겨눌 수 있다.

한 프레임에 주어진 시간

화면은 정지한 그림이 아니라 아주 짧은 간격으로 계속 새로 그려지는 그림의 연속이다. 이 새로 그려지는 한 장을 프레임이라 부르고, 브라우저는 일정한 박자로 이 프레임을 갈아 끼운다. 사람의 눈에 움직임이 매끄럽게 보이려면 이 박자가 촘촘하고 일정해야 한다. 박자가 흔들리면 움직임이 뚝뚝 끊기는 것처럼 느껴진다.


여기서 중요한 사실은 한 프레임을 그리는 데 주어진 시간이 정해져 있다는 것이다. 정해진 박자로 화면을 갈아 끼우려면, 다음 프레임을 그릴 차례가 오기 전에 이번 프레임에 필요한 모든 계산이 끝나 있어야 한다. 이 주어진 시간이 곧 한 프레임의 예산이며, 이 예산을 넘기면 그 프레임은 제때 준비되지 못해 건너뛰어진다.


한 프레임의 예산 안에는 스크립트 실행, 자리 잡기 계산, 색칠, 합치기가 모두 들어가야 한다. 이 중 어느 하나가 예산을 크게 잡아먹으면 나머지가 밀려 프레임이 늦어진다. 특히 스크립트가 이 예산을 넘겨 오래 돌면, 그 프레임에서는 화면을 그릴 짬이 아예 사라진다. 그래서 부드러운 화면은 매 프레임의 작업을 예산 안에 욱여넣는 일이다.


프레임 하나가 예산을 넘겨 건너뛰어지는 일이 이따금 일어나면 사용자는 잠깐의 끊김으로 느낀다. 그러나 이런 건너뜀이 잦아지면 움직임 전체가 뚝뚝 끊기는 것으로 다가온다. 그래서 렌더링 성능의 한 축은 매 프레임이 예산을 지키게 만들어, 이 건너뜀을 없애는 데 있다. 평균적으로 빠른 것보다 매 프레임이 꾸준히 예산을 지키는 것이 체감에는 더 중요하다.

첫 화면이 뜨기까지

사용자가 가장 먼저 겪는 것은 첫 화면이 뜨기까지의 시간이다. 주소를 입력한 순간부터 의미 있는 내용이 눈에 보이기까지의 이 시간이 첫인상을 좌우한다. 이 구간은 앞서 다룬 이름 조회, 연결, 문서와 스타일의 해석, 첫 배치와 색칠이 사슬처럼 이어진 결과다. 어느 한 고리가 늦으면 첫 화면 전체가 밀린다.


첫 화면을 앞당기는 핵심 원리는 그 화면을 그리는 데 꼭 필요한 것만 먼저 갖추고 나머지는 뒤로 미루는 것이다. 첫 화면에 필요한 스타일만 문서와 함께 실어 보내고, 당장 필요 없는 스크립트는 뒤로 미루며, 화면 아래쪽의 이미지는 실제로 그 근처로 스크롤이 다가올 때 불러온다. 지금 보이는 것에 필요한 자원의 우선순위를 높이고 나머지를 낮추는 것이 이 구간 최적화의 뼈대다.


이 구간에서 흔한 병목은 화면 그리기를 붙잡는 요소들이다. 문서 앞머리의 무거운 스크립트가 문서 읽기를 멈춰 세우고, 바깥에서 줄줄이 이어져 오는 스타일이 첫 그리기를 미룬다. 이런 붙잡는 요소를 찾아 실행 시점을 늦추거나 한데 모으는 것만으로 첫 화면이 눈에 띄게 빨라진다. 무엇이 첫 화면을 붙잡는지를 아는 것이 개선의 출발점이다.


다만 첫 화면을 빨리 그리는 것과 그 화면이 실제로 쓸모 있는 것은 다르다. 겉모습만 먼저 뜨고 정작 조작이 되지 않으면 사용자는 여전히 기다린다. 그래서 첫 화면의 빠름은 눈에 보이는 시점만이 아니라, 그 화면이 실제로 반응하기 시작하는 시점까지 함께 보아야 한다. 보이는 것과 쓸 수 있는 것 사이의 간격을 좁히는 것이 좋은 첫인상의 조건이다.

움직임의 부드러움

첫 화면이 뜬 뒤에는 화면이 움직이고 바뀔 때의 부드러움이 문제가 된다. 스크롤, 애니메이션, 펼침과 접힘 같은 움직임은 매 프레임 이어지므로, 앞서 말한 프레임 예산을 매 순간 지켜야 한다. 이 구간의 성능은 곧 매 프레임을 예산 안에 끝내는 능력이다.


움직임을 부드럽게 유지하는 첫째 원리는, 매 프레임 이어지는 변화를 되도록 값싼 갱신으로 표현하는 것이다. 앞 편들에서 다룬 대로, 위치를 옮기는 움직임은 자리를 다시 잡는 대신 층을 옮기는 방식으로, 나타나고 사라지는 변화는 투명도를 조절하는 방식으로 표현하면 리플로우와 리페인트를 건너뛴다. 잦은 움직임일수록 이 원칙을 지키는 것이 결정적이다.


둘째 원리는 매 프레임 안에서 무거운 계산을 돌리지 않는 것이다. 스크롤에 반응해 복잡한 계산을 하거나, 애니메이션의 각 프레임마다 큰 데이터를 다시 다루면, 그 계산이 프레임 예산을 잡아먹어 움직임이 끊긴다. 매 프레임 반복되는 자리에서는 계산을 최소한으로 줄이고, 무거운 준비는 움직임이 시작되기 전에 미리 끝내 두어야 한다.


셋째 원리는 화면 변화를 브라우저의 그리기 박자에 맞추는 것이다. 브라우저가 다음 프레임을 그릴 시점에 맞춰 변화를 적용하면, 그리기와 어긋나 헛도는 계산이 사라지고 움직임이 프레임의 리듬과 정확히 포개진다. 시간을 직접 재어 변화를 적용하는 방식보다, 그리기 박자에 올라타는 방식이 매 순간의 움직임에는 더 잘 맞는다.

조작에 대한 반응

성능의 또 다른 축은 사용자의 조작에 얼마나 빨리 반응하느냐다. 버튼을 누르거나 화면을 건드렸을 때 그 반응이 즉각 돌아오지 않으면, 사용자는 화면이 멈췄거나 자기 조작이 먹히지 않았다고 느낀다. 이 즉각성은 앞서 다룬 한 줄기 흐름의 성질과 깊이 얽혀 있다.


조작에 대한 반응도 스크립트가 처리하는 하나의 일감이며, 이 일감은 화면 그리기와 같은 흐름을 나눠 쓴다. 그래서 그 흐름이 다른 무거운 작업에 붙들려 있으면, 조작에 대한 반응은 그 작업이 끝날 때까지 줄을 서서 기다린다. 사용자가 눌렀는데 한 박자 늦게 반응하는 답답함의 상당수가 이 기다림에서 온다.


그래서 즉각성을 지키려면 흐름을 오래 붙드는 긴 작업을 없애야 한다. 무거운 일은 잘게 쪼개 사이사이 흐름을 비켜 주고, 정말 무거운 계산은 화면과 떨어진 별도의 일꾼에게 넘긴다. 이렇게 흐름을 늘 비워 두어야, 조작이 들어왔을 때 그에 대한 반응이 지체 없이 처리된다. 반응의 즉각성은 흐름을 얼마나 한가하게 유지하느냐에 달려 있다.


조작에 반응하는 자리에서 당장 필요한 최소한만 처리하고 나머지는 미루는 것도 즉각성의 요령이다. 버튼을 눌렀을 때 눈에 보이는 반응만 곧바로 내보내고, 그에 딸린 무거운 뒤처리는 다음 순환으로 넘기면, 사용자는 자기 조작이 즉시 먹혔다고 느낀다. 반응이 실제로 완료되는 시점보다, 반응이 시작되는 것을 사용자가 인지하는 시점이 체감을 좌우한다.

환경에 따라 달라지는 성능

성능을 다룰 때 흔히 빠지는 함정은 자신의 환경을 기준으로 판단하는 것이다. 개발에 쓰는 기기는 대개 성능이 좋고 통신망도 빠르다. 그 환경에서 매끄럽게 도는 화면이, 성능이 낮은 기기와 불안정한 통신망을 쓰는 실제 사용자에게는 버벅이고 느릴 수 있다. 내 환경에서 문제가 없다는 것이 사용자에게도 문제가 없다는 뜻은 아니다.


기기의 성능 차이는 특히 스크립트 실행과 화면 그리기에서 크게 벌어진다. 앞서 다룬 한 프레임의 예산은 기기가 느릴수록 지키기 어려워진다. 좋은 기기에서는 예산 안에 넉넉히 끝나던 작업이, 느린 기기에서는 예산을 넘겨 프레임을 건너뛰게 만든다. 그래서 성능은 가장 좋은 기기가 아니라 사용자층의 흔한 기기, 나아가 낮은 축의 기기를 기준으로 살펴야 한다.


통신망의 차이도 못지않게 크다. 빠른 통신망에서는 첫 화면을 이루는 자원이 순식간에 도착하지만, 느리고 불안정한 통신망에서는 그 자원들이 뚝뚝 끊기며 도착한다. 앞서 다룬 조회와 연결의 오고 감이 느린 통신망에서는 몇 배로 늘어나, 첫 화면이 한참 뒤에야 뜬다. 그래서 자원의 수를 줄이고 크기를 줄이는 노력이 느린 통신망일수록 큰 차이를 만든다.


기기의 전력 사정도 성능의 한 축이다. 배터리로 도는 기기에서는 무거운 계산이 전력을 빠르게 소모하고 열을 내며, 기기가 스스로 성능을 낮춰 발열을 줄이기도 한다. 그러면 같은 화면도 시간이 지날수록 느려진다. 그래서 배경에서 쉼 없이 도는 계산이나 잦은 갱신은, 눈에 보이지 않아도 사용자의 기기를 지치게 하는 부담이 된다.


이 편차를 다루는 원칙은 가장 불리한 환경을 염두에 두고 설계하는 것이다. 좋은 환경에서만 잘 도는 화면은 사용자의 절반을 놓치는 것과 같다. 낮은 축의 기기와 느린 통신망에서도 최소한의 경험이 유지되도록 바탕을 튼튼히 하고, 여유가 있는 환경에서는 그 위에 더 나은 경험을 얹는 방식이 안전하다. 좋은 것을 먼저 만들고 낮은 환경을 나중에 챙기면 대개 그 챙김은 미뤄진다.


결국 성능을 판단하는 기준은 나의 환경이 아니라 사용자의 환경이다. 사용자층이 주로 어떤 기기와 통신망을 쓰는지를 알고, 그에 가까운 조건에서 화면을 확인해야 진짜 성능이 보인다. 좋은 환경에서만 재고 만족하는 것은, 정작 어려움을 겪는 사용자를 보지 못한 채 문제없다고 여기는 것이다.


다행히 이 편차를 개발 단계에서 흉내 내는 방법이 있다. 브라우저의 개발자 도구는 통신망을 일부러 느리게 하거나 기기의 계산 속도를 일부러 낮춰, 좋은 환경에서도 불리한 조건을 재현해 준다. 이 기능으로 느린 통신망과 느린 기기에서 화면이 어떻게 동작하는지를 미리 확인하면, 실제 사용자가 겪을 어려움을 개발 단계에서 앞당겨 마주할 수 있다.


이렇게 불리한 조건을 재현해 보면, 좋은 환경에서는 드러나지 않던 병목이 선명해진다. 빠른 통신망에서는 문제없이 도착하던 자원의 순서가 느린 통신망에서는 첫 화면을 한참 붙잡고, 좋은 기기에서는 예산 안에 끝나던 스크립트가 느린 기기에서는 프레임을 건너뛰게 만든다. 이 재현으로 드러난 병목을 먼저 고치면, 가장 불리한 사용자의 경험부터 끌어올릴 수 있다.

측정 없이는 개선도 없다

렌더링 성능을 다룰 때 가장 경계해야 할 것은 짐작에 기대는 것이다. 어디가 느린지, 무엇이 병목인지는 실제로 재어 보기 전에는 알 수 없고, 짐작만으로 손을 대면 엉뚱한 곳을 고치기 십상이다. 그래서 개선은 언제나 측정에서 시작해야 한다.


측정은 두 종류로 나뉜다. 하나는 개발 환경에서 화면을 그리는 과정을 직접 들여다보는 것이다. 브라우저의 개발자 도구는 각 프레임에서 스크립트, 자리 잡기, 색칠, 합치기가 각각 얼마를 썼는지를 시간 축에 펼쳐 보여 준다. 어느 프레임이 예산을 넘겼는지, 그 프레임에서 무엇이 시간을 잡아먹었는지를 이 화면에서 짚을 수 있다.


다른 하나는 실제 사용자가 겪는 성능을 현장에서 재는 것이다. 개발 환경은 대개 좋은 기기와 빠른 통신망을 갖추고 있어, 실제 사용자가 느린 기기와 불안정한 통신망에서 겪는 어려움을 그대로 보여 주지 못한다. 그래서 실제 사용자의 첫 화면 시간, 움직임의 끊김, 반응의 지연을 현장에서 모아 보아야 진짜 문제가 드러난다.


측정으로 병목을 찾았다면, 개선의 순서는 사용자 체감에 가장 큰 영향을 주는 것부터다. 아무도 겪지 않는 미세한 지연을 다듬는 것보다, 많은 사용자가 겪는 첫 화면 지연이나 잦은 프레임 끊김을 먼저 잡는 것이 이롭다. 정리하면, 렌더링 성능은 첫 화면의 빠름, 움직임의 부드러움, 반응의 즉각성이라는 여러 국면으로 이루어지며, 각 국면을 측정으로 확인하고 체감이 큰 것부터 고쳐 나가는 것이 원칙이다. 다음 편에서는 이 성능을 떠받치는 또 하나의 축인, 한 번 받아온 자원을 다시 받지 않게 하는 캐시 계층을 다룬다.