웹 앱 이해하기
고성능 웹 애플리케이션은 우수한 사용자 경험을 제공하는 데 매우 중요합니다. 웹 애플리케이션이 점점 더 복잡해짐에 따라 성능 영향을 이해하는 것은 매력적인 환경을 만드는 데 매우 중요합니다. 지난 몇 년 동안 네트워크 성능, 로드 시간 등을 분석하는 데 도움이 되는 다양한 API가 브라우저에 등장했지만, 이러한 API는 애플리케이션 속도를 저하시키는 요인을 찾을 수 있을 만큼 충분한 유연성을 갖춘 세부적인 정보를 제공하지는 않습니다. User Timing API를 입력합니다. 이 API는 웹 애플리케이션을 계측하여 애플리케이션이 시간을 소비하는 위치를 식별하는 데 사용할 수 있는 메커니즘을 제공합니다. 이 도움말에서는 API와 사용 방법의 예를 다룹니다.
측정할 수 없는 것은 최적화할 수 없습니다.
느린 웹 애플리케이션의 속도를 높이는 첫 번째 단계는 시간이 어디에 소요되는지 파악하는 것입니다. 자바스크립트 코드 영역의 시간 영향을 측정하는 것은 핫스팟을 식별하는 이상적인 방법이며, 이는 성능을 개선하는 방법을 찾는 첫 번째 단계입니다. 다행히 User Timing API를 사용하면 자바스크립트의 여러 부분에 API 호출을 삽입한 다음 최적화에 사용할 수 있는 자세한 타이밍 데이터를 추출할 수 있습니다.
고해상도 시간 및 now()
정확한 시간 측정의 기본은 정밀도입니다. 과거에는 밀리초 측정에 기반한 타이밍이 있었는데, 이는 괜찮지만 버벅거림 없는 60FPS 사이트를 빌드하려면 각 프레임을 16ms 내에 그려야 합니다. 따라서 밀리초 단위의 정확도만 있으면 정확한 분석에 필요한 정밀도가 부족합니다. 최신 브라우저에 내장된 새로운 타이밍 유형인 고해상도 시간을 입력합니다. 고해상도 시간은 마이크로초 해상도까지 정확할 수 있는 부동 소수점 타임스탬프를 제공합니다. 이는 이전보다 천 배 더 나은 성능입니다.
웹 애플리케이션에서 현재 시간을 가져오려면 Performance 인터페이스의 확장 프로그램을 구성하는 now() 메서드를 호출합니다. 다음 코드는 이 작업을 실행하는 방법을 보여줍니다.
var myTime = window.performance.now();
PerformanceTiming이라는 또 다른 인터페이스가 있는데, 이 인터페이스는 웹 애플리케이션이 로드되는 방식과 관련된 다양한 시간을 제공합니다. now() 메서드는 PerformanceTiming의 navigationStart 시간이 발생한 시점부터 경과된 시간을 반환합니다.
DOMHighResTimeStamp 유형
이전에는 웹 애플리케이션의 시간을 측정할 때 DOMTimeStamp를 반환하는 Date.now()와 같은 것을 사용했습니다. DOMTimeStamp는 정수 밀리초를 값으로 반환합니다. 고해상도 시간에 필요한 더 높은 정확도를 제공하기 위해 DOMHighResTimeStamp라는 새로운 유형이 도입되었습니다. 이 유형은 밀리초 단위의 시간도 반환하는 부동 소수점 값입니다. 하지만 부동 소수점이므로 밀리초의 소수점 값을 나타낼 수 있으므로 1,000분의 1밀리초의 정확도를 제공할 수 있습니다.
사용자 시간 인터페이스
이제 고해상도 타임스탬프가 있으므로 사용자 시간 인터페이스를 사용하여 시간 정보를 가져오겠습니다.
사용자 시간 인터페이스는 애플리케이션의 여러 위치에서 메서드를 호출할 수 있는 함수를 제공합니다. 이 함수는 헨젤과 그레텔 스타일의 탐색경로를 제공하여 시간이 어디에 사용되는지 추적할 수 있게 해줍니다.
mark() 사용
mark() 메서드는 타이밍 분석 도구 키트의 기본 도구입니다. mark()는 타임스탬프를 저장합니다. mark()의 매우 유용한 점은 타임스탬프에 이름을 지정할 수 있으며 API가 이름과 타임스탬프를 단일 단위로 기억한다는 것입니다.
애플리케이션의 다양한 위치에서 mark()를 호출하면 웹 애플리케이션에서 해당 '표시'에 도달하는 데 걸린 시간을 파악할 수 있습니다.
사양은 mark_fully_loaded, mark_fully_visible, mark_above_the_fold 등 흥미롭고 설명이 필요 없는 마크에 대해 제안된 여러 이름을 호출합니다.
예를 들어 다음 코드를 사용하여 애플리케이션이 완전히 로드되는 시점에 표시를 설정할 수 있습니다.
window.performance.mark('mark_fully_loaded');
웹 애플리케이션 전체에 명명된 마크를 설정하면 많은 타이밍 데이터를 수집하고 여유 시간에 분석하여 애플리케이션이 무엇을 언제 수행하는지 파악할 수 있습니다.
measure()를 사용하여 측정값 계산
여러 개의 타이밍 표시를 설정한 후에는 표시 사이의 경과 시간을 확인해야 합니다. 이를 위해 measure() 메서드를 사용합니다.
measure() 메서드는 표시 사이의 경과 시간을 계산하며, 표시와 PerformanceTiming 인터페이스의 잘 알려진 이벤트 이름 사이의 시간을 측정할 수도 있습니다.
예를 들어 다음과 같은 코드를 사용하여 DOM이 완료된 시점부터 애플리케이션 상태가 완전히 로드될 때까지의 시간을 계산할 수 있습니다.
window.performance.measure('measure_load_from_dom', 'domComplete', 'mark_fully_loaded');
measure()를 호출하면 설정한 마크와 관계없이 결과가 저장되므로 나중에 검색할 수 있습니다. 애플리케이션이 실행되는 동안 시간을 저장하면 애플리케이션이 계속 반응하며, 애플리케이션이 일부 작업을 완료한 후 모든 데이터를 덤프하여 나중에 분석할 수 있습니다.
clearMarks()로 표시 삭제
설정한 마크를 한꺼번에 삭제할 수 있으면 유용할 때가 있습니다. 예를 들어 웹 애플리케이션에서 일괄 실행을 수행할 수 있으므로 실행할 때마다 새로 시작해야 합니다.
clearMarks()를 호출하여 설정한 마크를 쉽게 삭제할 수 있습니다.
따라서 아래 예시 코드는 기존 마크를 모두 삭제하므로 원하는 경우 타이밍 실행을 다시 설정할 수 있습니다.
window.performance.clearMarks();
물론 모든 표시를 지우고 싶지 않은 경우도 있습니다. 따라서 특정 마크를 삭제하려면 삭제하려는 마크의 이름을 전달하면 됩니다. 예를 들어 아래 코드는 다음과 같습니다.
window.performance.clearMarks('mark_fully_loaded');
첫 번째 예시에서 설정한 표시를 삭제하고 설정한 다른 표시는 변경하지 않고 그대로 둡니다.
또한 생성한 측정값을 삭제할 수도 있으며, 이를 위한 메서드는 clearMeasures()입니다. clearMarks()와 동일하게 작동하지만, 사용자가 측정한 모든 측정값에 적용됩니다. 예를 들어 다음 코드를 사용하면
window.performance.clearMeasures('measure_load_from_dom');
위의 measure() 예시에서 만든 측정항목이 삭제됩니다. 모든 측정항목을 삭제하려면 clearMarks()와 동일하게 작동합니다. 인수 없이 clearMeasures()를 호출하면 됩니다.
타이밍 데이터 가져오기
표시를 설정하고 간격을 측정하는 것은 좋지만, 어느 시점에서는 타이밍 데이터를 가져와 분석을 수행해야 합니다. 이 역시 매우 간단합니다. PerformanceTimeline 인터페이스를 사용하기만 하면 됩니다.
예를 들어 getEntriesByType() 메서드를 사용하면 모든 표시 시간 또는 모든 측정 시간을 목록으로 가져와서 반복하고 데이터를 처리할 수 있습니다. 좋은 점은 목록이 시간순으로 반환되므로 웹 애플리케이션에서 표시가 발생한 순서대로 표시를 볼 수 있다는 것입니다.
아래 코드는 다음을 수행합니다.
var items = window.performance.getEntriesByType('mark');
는 웹 애플리케이션에서 도달한 모든 마크의 목록을 반환하고, 다음 코드는
var items = window.performance.getEntriesByType('measure');
모든 측정값의 목록을 반환합니다.
지정한 특정 이름을 사용하여 항목 목록을 다시 가져올 수도 있습니다. 예를 들어 다음 코드는
var items = window.performance.getEntriesByName('mark_fully_loaded');
startTime 속성에 'mark_fully_loaded' 타임스탬프가 포함된 항목이 하나 있는 목록을 반환합니다.
XHR 요청 시간 측정 (예시)
이제 사용자 시간 API를 충분히 이해했으므로 이를 사용하여 웹 애플리케이션에서 모든 XMLHttpRequests가 소요되는 시간을 분석할 수 있습니다.
먼저 모든 send() 요청을 수정하여 마크를 설정하는 함수 호출을 실행하고, 동시에 성공 콜백을 다른 마크를 설정하는 함수 호출로 변경한 다음 요청에 걸린 시간을 측정합니다.
따라서 일반적으로 XMLHttpRequest는 다음과 같습니다.
var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
do_something(e.responseText);
}
myReq.send();
이 예시에서는 요청 수를 추적하고 각 요청의 측정값을 저장하는 데 사용할 전역 카운터를 추가합니다. 이를 위한 코드는 다음과 같습니다.
var reqCnt = 0;
var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
window.performance.mark('mark_end_xhr');
reqCnt++;
window.performance.measure('measure_xhr_' + reqCnt, 'mark_start_xhr', 'mark_end_xhr');
do_something(e.responseText);
}
window.performance.mark('mark_start_xhr');
myReq.send();
위 코드는 전송하는 모든 XMLHttpRequest에 대해 고유한 이름 값을 가진 측정을 생성합니다. 요청이 순차적으로 실행된다고 가정합니다. 병렬 요청을 위한 코드는 순서가 맞지 않게 반환되는 요청을 처리하기 위해 조금 더 복잡해야 합니다. 이 부분은 독자의 연습 과제로 남겨두겠습니다.
웹 애플리케이션이 여러 요청을 수행한 후에는 아래 코드를 사용하여 모두 콘솔에 덤프할 수 있습니다.
var items = window.performance.getEntriesByType('measure');
for (var i = 0; i < items.length; ++i) {
var req = items[i];
console.log('XHR ' + req.name + ' took ' + req.duration + 'ms');
}
결론
User Timing API는 웹 애플리케이션의 모든 측면에 적용할 수 있는 다양한 도구를 제공합니다. 웹 애플리케이션 전체에 API 호출을 분산하고 생성된 타이밍 데이터를 후처리하여 시간이 소요되는 위치를 명확하게 파악하면 애플리케이션의 핫스팟을 쉽게 좁힐 수 있습니다. 하지만 브라우저가 이 API를 지원하지 않는다면 어떻게 해야 할까요? 괜찮습니다. API를 매우 잘 에뮬레이션하고 webpagetest.org에서도 잘 작동하는 훌륭한 폴리필을 찾을 수 있습니다. 무엇을 망설이고 계신가요? 지금 애플리케이션에서 User Timing API를 사용해 보세요. 애플리케이션의 속도를 높이는 방법을 알아내고 사용자에게 훨씬 더 나은 환경을 제공할 수 있습니다.