자바스크립트

[번역] 웹어셈블리는 왜 빠를까?

이 글은 모질라 Hacks의 웹어셈블리 시리즈 중 다섯 번째 글인 What makes WebAssembly fast?의 번역글이다. 전 시리즈에 걸쳐 웹어셈블리 뿐만 아니라 JIT나 어셈블리 등에 대해서도 체계적으로 잘 설명하고 있고, 카툰까지 곁들여서 이해하기도 쉽다.

총 6부로 되어 있으며, 원작자의 허가를 얻어 전체 시리즈를 모두 번역했으니 처음부터 차례대로 모두 읽어보길 권한다.

  1. 카툰으로 소개하는 웹어셈블리
  2. 저스트-인-타임(JIT) 컴파일러 집중 코스
  3. 어셈블리 집중 코스
  4. 웹어셈블리 모듈의 생성과 동작
  5. 웹어셈블리는 왜 빠를까? (현재글)
  6. 웹어셈블리의 현재 위치와 미래

 

이전 글에서 나는 웹어셈블리와 자바스크립트는 양자택일의 문제가 아니라는 점을 설명했다. 우리는 너무 많은 개발자들이 웹어셈블리만을 이용해 코드를 작성하기를 바라지 않는다.

그러므로 개발자들이 어플리케이션을 개발할 때 웹어셈블리와 자바스크립트 사이에서 하나를 선택할 필요는 없다. 다만, 자바스크립트 코드의 일부를 웹어셈블리로 교체할 수는 있을 것이다.

예를 들어, 리액트를 개발하고 있는 팀은 그들의 리콘사일러(reconciler) 코드 (일명 Virtual DOM) 를 웹어셈블리 버전으로 교체할 수 있을 것이다. 리액트를 사용하는 사람들은 아무것도 할 필요가 없다. 그들의 앱은 웹어셈블리의 장점을 취한다는 것 빼고는 이전과 정확하게 똑같을 것이다.

리액트 팀과 같은 개발자들이 이런 식의 교체를 할 만한 이유는 바로 웹어셈블리가 빠르기 때문이다. 그런데, 웹어셈블리는 왜 빠른걸까?

오늘날의 자바스크립트 성능은 어떨까?

자바스크립트와 웹어셈블리의 성능차이를 이해하려면, 먼저 자바스크립트 엔진이 하는 일을 이해해야 한다.

이 다이어그램은 오늘날 어플리케이션의 시작(start-up) 성능을 간략하게 보여준다.

자바스크립트 엔진이 각각의 작업들을 진행할 때 걸리는 시간은 해당 페이지가 사용하는 자바스크립트에 따라 다를 것이다. 이 다이어그램은 정확한 성능 수치를 나타내지 않는다. 대신, 같은 기능을 하는 자바스크립트와 웹어셈블리가 어떤 성능차이를 갖는지에 대한 고차원의 모델을 의미한다.

05-01-diagram_now01-500x129.png

각각의 막대는 특정 작업을 할 때 걸리는 시간을 나타낸다.

  • 구문해석 (Parsing) – 소스코드를 인터프리터가 실행할 수 있는 형태로 가공하는데 걸리는 시간
  • 컴파일 + 최적화 – 기본(baseline) 컴파일러와 최적화(optimizing) 컴파일러에서 걸리는 시간. 일부 최적화 컴파일러의 작업은 메인 스레드에서 진행되지 않기 때문에 여기에서 제외된다.
  • 재-최적화 (Re-optimizing) – JIT(Just in time Compiler)가 그들의 가정이 틀렸을 때 재조정하는데 걸리는 시간. 이는 다시 최적화를 하거나, 기본코드로 되돌리는 작업을 모두 포함한다.
  • 실행 – 코드를 실행하는 데 걸리는 시간
  • 가비지 컬렉션 – 불필요한 메모리를 비우는 데 걸리는 시간

한가지 중요한 사실 : 이 작업들은 구분된 단위를 갖거나 특정한 순서로 진행되지 않고, 서로 맞물려서 진행된다. 구문 해석이 일부 진행된 후, 일부 실행, 일부 컴파일, 다시 일부 구문 해석, 일부 실행 등의 형태로 진행된다.

이렇게 작은 단위로 분해함으로써, 다음 다이어그램과 같았던 초기 자바스크립트의 성능으로부터 큰 개선을 이룰 수 있었다.

05-02-diagram_past01-500x147.png

단순히 자바스크립트를 실행하는 인터프리터였던 초기에는 실행속도가 상당히 느렸다. JIT가 도입된 이후로 실행속도는 급격하게 증가했다.

트레이드-오프는, 코드를 감지하고 컴파일할 때 부하가 발생한다는 점이다. 만약 자바스크립트 개발자들이 지금까지 해왔던 방식으로만 코드를 작성한다면 구문해석과 컴파일 시간은 아주 작을 것이다. 하지만 자바스크립트의 성능이 개선될수록 개발자들은 자바스크립트로 더 복잡한 어플리케이션을 개발하게 될 것이다. 즉, 좀 더 개선할 여지가 있다는 의미이다.

웹어셈블리는 어떻게 다를까?

아래 다이어그램은 웹어셈블리가 전형적인 웹 어플리케이션과 어떻게 다른지를 보여준다.

05-03-diagram_future01-500x214.png

각각의 단계를 어떻게 다루는지는 브라우저마다 조금씩 다르다. 여기서는 SpiderMonky를 모델로 설명하겠다.

가져오기 (Fetching)

다이어그램에는 나오지 않았지만, 단순히 서버에서 파일을 가져오는 일에도 시간이 소모된다.

웹어셈블리는 자바스크립트보다 더 간결하기 때문에 데이터를 가져오는 속도가 더 빠르다. 압축 알고리즘이 자바스크립트 번들 파일의 사이즈를 상당히 줄일 수 있을지는 몰라도, 압축된 바이너리 형태의 웹어셈블리가 여전히 더 작을 것이다.

즉, 서버에서 클라이언트로 전송하는 시간이 더 적게 걸리게 된다. 느린 네트워크 환경에서는 더 의미가 있을 것이다.

구문해석 (Parsing)

자바스크립트 소스가 브라우저에 전달되게 되면, 추상 구문 트리(AST : Abstract Syntax Tree)로 변환된다.

브라우저는 종종 이 작업을 지연해서 실행하는데, 초기 실행에 필요한 부분만 먼저 해석하고 아직 호출되지 않은 함수들은 스텁(stub)만 만들어 둔다. 이때 AST는 중간 표현 형식(바이트코드라고 불리는)으로 변환되는데, 이는 각 자바스크립트 엔진에 따라 다르다. 반면, 웹어셈블리는 그 자체로 이미 중간 표현 형식이기 때문에 이 변환 작업을 거칠 필요가 없다. 웹어셈블리는 단지 해독된(decoded) 이후에 에러가 없는지만 검증되면(validated) 된다.

05-04-diagram_compare02-500x169.png

컴파일 + 최적화

JIT에 대한 글에서 설명했듯이, 자바스크립트는 코드를 실행하는 도중에 컴파일된다. 런타임에 어떤 타입이 사용되는지에 따라 동일한 코드가 다양한 버전으로 컴파일되기도 한다.

브라우저들은 웹어셈블리를 각자 다른 방식으로 컴파일한다. 일부 브라우저들은 실행하기 전에 기본 컴파일 단계를 거치고, 다른 브라우저들은 JIT를 사용한다.

어떤 방식이든, 웹어셈블리는 기계 코드와 훨씬 가까운 상태에서 시작한다. 예를 들면 프로그램의 일부로써 타입이 포함되어 있는데, 이는 다음의 몇가지 이유로 더 빠르다.

  1. 최적화된 코드를 컴파일하기 전에, 어떤 타입이 사용되었는지 확인하기 위해 코드를 실행해볼 필요가 없다.
  2. 같은 코드에서 대해서 여러가지 타입들이 사용되고 있을 때, 여러가지 버전으로 컴파일할 필요가 없다.
  3. LLVM 에서 이미 많은 최적화가 진행된 상태이기 때문에, 컴파일이나 최적화에 필요한 작업이 적다.

05-05-diagram_compare03-500x175.png

재-최적화 (Reoptimizing)

JIT는 가끔 최적화된 버전을 버리고, 다시 최적화해야한다.

이는 JIT가 코드 실행에 기초하여 만들어낸 가정이 잘못된 것으로 판명될 때 발생한다. 예를 들어 순환문 내부에서 사용되는 변수에 이전 주기와는 다른 값이 할당되거나, 프로토타입 체인내부에 다른 함수가 추가되면, 역최적화(deoptimization)가 발생한다.

역최적화에는 두가지 비용이 따른다. 첫째로, 최적화된 코드를 내버리고 기본 코드로 되돌릴 때 시간이 소모된다. 둘째로, 해당 함수가 여전히 많은 빈도로 호출되고 있다면 JIT는 해당 함수를 다시 최적화 컴파일러에게 보내기로 결정할 수도 있는데, 여기에서 컴파일을 두 번 하는 것에 따른 비용이 발생한다.

웹어셈블리에서는 타입과 같은 것들이 명시적이기 때문에, JIT가 런타임에 데이터를 수집해서 타입을 추론할 필요가 없다. 즉, 재-최적화 단계가 필요없다는 의미이다.

05-06-diagram_compare04-500x201.png

실행

자바스크립트가 빠르게 실행될 수 있도록 작성하는 것은 가능하다. 이를 위해서는 JIT가 어떻게 최적화하는지에 대해서 알고 있어야 한다. 예를 들어 JIT에 관한 예전 글에서 설명했던 것 처럼, 컴파일러가 타입을 특수화(type specialize : 타입이 항상 동일하다고 가정하고 코드를 최적화하는 과정)를 하도록 만드려면 어떻게 코드를 작성해야하는지를 알아야 한다.

하지만, 대부분의 개발자들은 JIT 내부에 대해서 알지 못한다. 심지어 JIT 내부에 대해서 알고 있는 개발자들도, 정확한 개선 포인트를 알기는 쉽지 않다. 코드를 읽기 쉽게 만들기 위해 사용되는 코딩 패턴들(공통 작업을 추상화해서 타입에 관계없이 동작하는 함수로 만드는 작업 등)은 컴파일러가 코드를 최적화하는 것을 방해한다.

게다가 JIT가 사용하는 최적화는 브라우저마다 달라서, 특정 브라우저에 맞추어 코딩을 하면 다른 브라우저에서는 성능이 느릴수도 있다.

이러한 이유로, 웹어셈블리로 작성된 코드를 실행하는 것은 일반적으로 더 빠르다. 자바스크립트를 위해 JIT가 하는 많은 최적화 작업들(타입 특수화와 같은)은 웹어셈블리에서는 전혀 필요없다.

뿐만 아니라 웹어셈블리는 컴파일러를 목표로 디자인되었다. 즉, 인간 프로그래머가 작성하는 용도가 아닌, 컴파일러가 생성해내는 용도로 디자인 되었다는 의미이다.

인간 프로그래머가 직접 프로그래밍할 필요가 없기 때문에, 웹어셈블리는 기계에게 더 이상적인 명령(instruction) 셋을 제공할 수 있다. 코드가 어떤 종류의 작업을 하느냐에 따라 이러한 명령들은 10% 부터 800% 까지 더 빠를 수 있다.

05-07-diagram_compare05-500x171.png

가비지 컬렉션

자바스크립트에서 개발자들은 더이상 필요없는 오래된 변수들을 메모리에서 제거하는 작업에 대해 신경쓸 필요가 없다. 대신에 자바스크립트 엔진이 가비지 컬렉터를 이용해 이러한 작업을 자동으로 해 준다.

하지만 만약 예측가능한 성능을 원한다면, 이는 문제가 될 수도 있다. 가비지 컬렉터가 언제 작동할 지 제어할 수 없기 때문에, 안좋은 타이밍에 실행될 지도 모른다. 대부분의 브라우저들은 이를 잘 스케쥴링해 주고 있지만, 여전히 코드 실행을 방해하는 요인이 될 수도 있다.

최소한 현재까지는, 웹어셈블리는 가비지 컬렉션을 전혀 지원하지 않는다. 메모리는 수동으로 (C나 C++와 같이) 관리된다. 이로 인해 개발자들이 프로그래밍 하기는 더 어려울 수도 있지만, 성능에 있어서는 더 안정된 결과를 만들어낼 수 있을 것이다.

05-08-diagram_compare06-500x204.png

결론

웹어셈블리는 많은 경우에 자바스크립트보다 더 빠르다. 왜냐하면 :

  • 웹어셈블리 코드가 자바스크립트보다 더 간결하기 때문에 (압축된 경우에도 마찬가지), 코드를 가져오는 데에 더 적은 시간이 걸린다.
  • 웹어셈블리 코드를 해독(decode)하는 시간이 자바스크립트의 구문을 해석 (parse) 하는 시간보다 적게 걸린다.
  • 웹어셈블리 코드는 자바스크립트 코드보다 더 머신 코드에 가깝고, 서버단에서 미리 최적화가 되어 있기 때문에 컴파일하고 최적화하는 시간이 적게 걸린다.
  • 웹어셈블리는 타입이나 다른 정보가 미리 내장되어 있기 때문에, 자바스크립트 엔진이 실행시점에 분석할 필요가 없어서 재-최적화하는 시간이 필요없다.
  • 웹어셈블리 코드는 성능을 위해 컴파일러를 느리게 만드는 요인들에 대해 개발자들이 미리 알고 있을 필요가 없으며, 머신에 적합한 명령어 셋을 갖고 있기 때문에 실행시간도 주로 더 적게 걸린다.
  • 메모리를 직접 관리하기 때문에 가비지 컬렉션이 필요없다.

이것이 바로 같은 작업을 할 때 웹어셈블리가 자바스크립트보다 더 좋은 성능을 내는 이유이다.

몇몇 경우에는 기대한 것 만큼 웹어셈블리가 빠르지 않을 때도 있지만, 머지않아 이를 개선할 변화가 있을 것이다. 다음 글에서 이런 내용들을 다루도록 하겠다.

Lin Clark 에 대해

Lin은 모질라 개발자 관계 팀의 엔지니어이다. 그녀는 자바스크립트, 웹어셈블리, Rust, Servo 등을 끄적거리며, 코드 카툰을 그린다.

코드카툰 : http://code-cartoons.com/
트위터 : @linclark

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s