November 27 December 1, 2017

류선임 edited this page Dec 4, 2017 · 10 revisions

Weekly Magazine

Weekly Pick!

원문 : https://medium.com/dev-channel/the-cost-of-javascript-84009f51e99e

자바스크립트의 비용

자바스크립트에 굉장히 많이 의존하는 사이트를 구축할 때, 우리는 종종 쉽게 보기 힘든 방식으로 무언가를 전송하는데 비용을 지불하곤 한다. 이번 포스트에서는 당신이 만든 사이트를 모바일 장치에서 빠르게 로드하고 상호 작용 하려고 할 때, 약간의 규칙이 왜 도움이 되는지에 대해 설명할 것이다.

tl;dr: less code = less parse/compile + less transfer + less to decompress

네트워크

대부분의 개발자는 자바스크립트의 비용에 대해 생각할 때, 다운로드와 실행 비용의 측면에서 생각한다. 사용자의 연결 상태가 느릴수록 유선을 통해서 많은 바이트의 자바스크립트 코드를 전송하는 것이 느려질 수 있다.

이는 사용자가 실제로 사용하는 유효한 네트워크 연결 타입이 3G, 4G와 와이파이가 아닐 수 있어서 개발도상국 같은 곳에서 문제가 될 수 있다. 당신이 커피숍에서 와이파이를 사용할 때 2G 속도의 셀룰러 핫스팟에 연결될 수 있다.

다음과 같은 방식으로 자바스크립트의 네트워크 전송 비용을 줄일 수 있다.

  • 사용자가 필요로 하는 코드만 전송한다. 코드 분리(code-splitting)가 도움이 될 수 있다.
  • 축소한다. (ES5를 위한 Uglify, bable-minify 또는 ES2015를 위한 uglify-es 사용)
  • 크게 압축한다. (Brotli~q11, Zopfli 또는 gzip 사용) Brotli는 gzip보다 압축률이 좋다. 이것은 CertSimple의 자바스크립트 바이트 압축 크기를 17% 절약하고 LinkedIn의 로드 시간을 4% 절약하는데 도움이 되었다.
  • 사용하지 않는 코드를 제거한다. 개발자 도구 코드 커버리지(DevTools code coverage)로 식별한다. 코드 제거에 대해서는 트리 쉐이킹(tree-shaking), 클로저 컴파일러(Colsure Compiler)의 고급 최적화와 lodash-babel-plugin와 같은 라이브러리 트리밍 플러그인 또는 Moment.js와 같은 라이브러리를 위한 웹팩 ContextReplacementPlugin을 참조하라. 모던 브라우저에서는 미리 트랜스파일링하는 기능을 막기 위해 babel-preset-env와 browserlist를 사용한다. 고급 개발자들은 웹팩 번들 파일 분석기를 사용해 불필요한 의존성들을 제거한다.
  • 캐싱하여 네트워크 순회를 최소화한다. 스크립트에 대한 최적의 수명 시간(max-age)을 결정하고 변경되지 않은 바이트 전송을 막기 위해 유효한 토큰(ETag)를 공급한다. Service Worker 캐싱은 앱 네트워크를 탄력있게 만들고 V8 코드 캐시와 같은 기능에 열심히 접근하도록 만든다. 파일명 해싱(filename hashing)을 사용한 장기 캐싱(long-term caching)에 대해 알아보라.

(사용자에게 제공되는 자바스크립트 양을 줄이는 모범 사례)

파싱/컴파일

다운로드 되었을 때 자바스크립트의 가장 많은 비용은 자바스크립트 엔진이 코드를 파싱(parse)하고 컴파일(compile)하는데 드는 시간이다. 크롬 개발자 도구에서, 파싱과 컴파일은 성능 패널(Performance panel)의 노란색 "스크립팅(Scripting)" 시간에 해당된다.

Bottom-Up, Call Tree 탭에서 정확한 파싱 및 컴파일 시간을 볼 수 있다.


(크롬 개발자 도구 성능 패널 > Bottom-Up 탭. V8’s Runtime Call Stats를 활성하하면, 파싱 및 컴파일과 같은 소요 시간에 대한 구간을 볼 수 있다.)

그러면 이것이 왜 중요한가?

코드를 파싱하고 컴파일하는데 많은 시간을 소비하는 것은 사용자가 당신의 사이트와 빠른 상호 작용을 느리게 한다. 전송하는 자바스크립트가 많을수록, 사이트와 상호 작용하기 전에 파싱과 컴파일하는데 시간이 오래 걸린다.

자바스크립트는 브라우저가 동일한 크기의 이미지와 웹 폰트를 처리하는 것보다 비용이 더 비싸다. - Tom Dale

자바스크립트와 비교했을 때 동일한 크기의 이미지를 처리할 때 더 많은 비용이 소비되지만, 모바일 하드웨어에서 자바스크립트는 페이지의 상호 작용에 부정적인 영향을 줄 가능성이 크다.

(이미지는 일반적으로 디코딩되고 래스터화 되는 동안 메인 스레드를 멈추게 하거나 사용자와의 상호 작용을 방해하지 않는다. 그러나 자바스크립트는 파싱과 컴파일의 실행 비용 때문에 상호 작용이 지연될 수 있다.)

우리가 파싱과 컴파일이 느리다고 말할 때, 컨텍스트(context)가 중요하다. 여기서는 평균적인 휴대폰에 대해서 말한다. 보통의 사용자들은 느린 CPU 및 GPU, L2/L3 캐시가 없고 제한적인 메모리의 휴대폰을 가질 수 있다.

네트워크 성능과 장치 성능은 항상 일치하지 않는다. 놀라운 광섬유 연결을 사용하는 사용자는 장치에 전송되는 자바스크립트를 파싱하고 평가할 수 있는 좋은 CPU를 가지지 못할 수도 있다. 이것을 반대로 하면.. 끔찍한 네트워크 연결 상태에 불타듯이 굉장히 빠른 CPU가 있다. - Kristofer Baxter, LinkedIn

자바스크립트 시작 성능 글에서, 저급 및 고급 하드웨어에서 단순 압축 해제된 자바스크립트 1MB까지의 파싱 비용에 대해 언급했었다. 시중에서 판매되는 가장 빠른 휴대폰과 평균적인 휴대폰 사이에는 파싱 및 컴파일하는데에 2~5배 정도의 시간 차이가 난다.


(다른 클래스의 데스크톱 및 모바일 장치 사이에서 자바스크립트 번들 1MB의 파싱 시간)

CNN.com과 같은 실제 사이트는 어떨까?

iPhone 8의 경우 CNN의 자바스크립트를 파싱 및 컴파일하는데에 4초밖에 걸리지 않았다. 평균적인 휴대폰(Moto G4)으로 13초가 걸렸다. 이는 사용자가 얼마나 빨리 사이트와 상호 작용할 수 있는지에 큰 영향을 줄 수 있다.

(Apple A11 Bionic 칩과 평균 안드로이드 하드웨어인 Snapdragon 617의 성능에 따른 파싱 시간 비교)

이것은 Moto G4와 같이 평균적인 하드웨어에서 테스트의 중요성을 강조한다. 당신의 사용자가 가지는 장치와 네트워크에 최적화하는 것이 중요하다.

구글 애널리틱스(Analytics)를 사용하면 당신의 사이트에 접속하는 실제 사용자의 모바일 장치 클래스를 파악할 수 있다. 이것은 실제 CPU/GPU 제약 조건을 이해할 수 있도록 도와준다.

우리는 실제로 굉장히 많은 자바스크립트를 전송하고 있는가? 아마도, 어쩌면 :)

HTTP 아카이브(상위 500K 사이트)를 사용하여 모바일에서 자바스크립트의 상태를 분석했을 때, 50%의 사이트가 상호 작용하는데 14초 이상 걸리는 것을 볼 수 있다. 이러한 사이트들은 자바스크립트를 파싱하고 컴파일하는데에 4초를 소비한다.

페이지에서 중요하지 않은 자바스크립트를 제거하면 전송 시간, CPU를 많이 쓰는 파싱 및 컴파일링과 잠재적인 메모리 오버헤드를 줄일 수 있다. 이것은 또한 당신의 페이지가 더 빠르게 상호 작용할 수 있도록 도와준다.

실행 시간

파싱과 컴파일만으로 비용이 발생하는 것은 아니다. 자바스크립트 실행(파싱 및 컴파일된 코드 실행)은 메인 스레드에서 발생하는 동작 중 하나이다. 실행 시간이 길면 사용자가 얼마나 빨리 당신의 사이트와 상호 작용할 수 있는지도 알 수 있다.

스크립트 실행이 50ms 이상 걸리면, 상호 작용하는데 걸리는 시간은 자바스크립트를 다운로드, 컴파일, 실행하는 전체 시간만큼 지연된다. -Alex Russell

자바스크립트는 메인 스레드가 잠기는 것을 막기 위해 작은 청크(small chunk)들로 되어있는 것이 좋다. 실행되는 동안 수행되는 작업량을 얼마나 줄일 수 있는지 탐색해보라.

자바스크립트 전달 비용을 줄이기 위한 패턴

느린 자바스크립트에 대한 파싱 및 컴파일과 네트워크 전송 시간을 지키려고 할 때, 경로 기반 청킹(route-based chunking) 또는 PRPL과 같은 패턴이 도움이 될 수 있다.

PRPL은 적극적인 코드 분할(aggressive code-splitting)과 캐싱을 통해서 상호 작용을 최적화하는 패턴이다.

PRPL이 가지는 영향을 시각화해보자.

V8 Runtime Call Stats를 사용해 인기있는 모바일 사이트와 프로그레시브 웹 앱의 로드 시간을 분석했다. 보다시피 파싱 시간(주황색)은 많은 사이트에서 소비되는 시간 중 중요한 부분이다.

PRPL 패턴을 사용한 사이트 Wego는, 경로에 대한 적은 파싱 시간을 유지하고 굉장히 빠르게 상호 작용한다. 위의 다른 많은 사이트들은 자바스크립트 비용을 낮추기 위해 코드 분할과 성능 예산(performance budgets)을 채택했다.

다른 비용들

자바스크립트는 다른 방법으로 페이지 성능에 영향을 줄 수 있다.

  • 메모리. 페이지는 GC(garbage collection) 때문에 자주 정지되거나 끊김이 나타날 수 있다. 브라우저가 메모리를 회수할 때 자바스크립트 실행이 일시 중지되기 때문에, 빈번하게 가비지를 수집하는 브라우저는 우리가 원하는 것보다 자주 실행을 멈출 수 있다. 끊김 현상으로부터 페이지를 보호하기 위해 메모리 부족과 빈번한 GC 정지를 피해라.
  • 런타임 동안 장기 실행(long-running)되는 자바스크립트는 응답하지 않는 페이지의 원인인 메인 스레드를 차단할 수 있다. 스케쥴링을 위해 requestAnimationFrame() 또는 requestIdleCallback()를 사용하여 작업을 작은 조각들로 묶어서 민감한 이슈들을 최소화할 수 있다.

점진적인 부트스트래핑(Progressive Bootstrapping)

많은 사이트에서 상호 작용의 비용 때문에 콘텐츠 가시성을 최적화한다. 큰 자바스크립트 번들 파일을 가질 때 첫 번째 페인팅을 빠르게 하기 위해서, 개발자들은 때때로 서버측 렌더링을 사용한다. 자바스크립트가 패치되었을 때 이벤트 핸들러에 붙어 렌더링을 "업그레이드"한다.

이것은 자체 비용이 있기 때문에 주의해야 한다. 첫 번째는 당신은 일반적으로 상호 작용을 방해할 수 있는 큰 HTML 응답을 보내게 되고, 두 번째는 자바스크립트 처리가 끝날 때까지 상호 작용을 할 수 없는 상태(역자주: 아래 이미지에서 Uncanny Valley 영역에 해당됨)에 사용자를 남겨둘 수 있다.

점진적인 부트스트래핑이 더 나은 접근 방법일 수 있다. 현재 경로에 필요한 HTML, 자바스크립트, CSS로만 구성된 최소한의 기능적인 페이지를 전송한다. 다른 자원들이 도착하면 앱은 레이지 로드(lazy-load)하고 많은 기능들을 잠금 해제할 수 있다.

(Paul Lewis 의 점진적인 부트스트래핑 시각화)

결론

전송 크기는 느린 네트워크(low end network)에서 굉장히 중요하다. 파싱 시간은 CPU 바운드 장치에 중요하다. 이들을 낮게 유지하는 것이 중요하다.

우리 팀은 자바스크립트 전송을 유지하고 낮은 파싱 및 컴파일 시간을 위해 제한적인 성능 예산을 채택하여 성공을 거두었다. 모바일용 예산에 대한 안내는 Alex Russell의 "Can You Afford It?: Real-world Web Performance Budgets" 글에서 볼 수 있다.

모바일 장치를 대상으로 사이트를 구축하는 경우에는, 대표적인 하드웨어로 개발하고 자바스크립트 파싱 및 컴파일 시간을 낮게 유지하며 팀이 자바스크립트 비용을 계속 지켜볼 수 있도록 보장하는 성능 예산을 채택하는 것이 가장 좋은 방법이다.

더 알아보기

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.