Google JavaScript엔진 V8 버전 6세대 전환완료

Chrome, Node.js에서 사용되는 자바스크립트 엔진 V8이 8년간의 역사속에서 큰 변화가 왔다. 지난 8월 3일에 출시된 버전 6.1에서 몇년동안 진행해 온 자바스크립트 컴파일러가 세대 교체가 마쳤다고 한다. Google I/O 2017에서 “V8, Advanced JavaScript & the next performance frontier“에서 소개된 내용을 정리해본다.

V8의 목표는 “Speed up real-world performance for modern JavaScript, and enable developers to build a faster future web” (현실세계의 요구에 맞춘 모던한 자바스크립트의 성능을 향상시킬 수 있고 앞으로의 변화하고 있는 웹에 빠르개 대응하여 개발자가 지원할 수 있도록 한다)는 것이다.

V8은 JIT(Just-In-Time compile)에서 실행시킬 수 있는 JavaScript 엔진이다. 브라우저가 자바스크립트를 받으면 브라우저는 그 코드를 변환하고 실행하며 코드를 번역하고 컴퓨터가 이해할 수 있는 네이티브 코드로 컴파일하고 실행한다. 그런데 이는 몇가지 단점이 존재한다.

  1. 컴파일하여 생성된 네이티브 코드는 실행되는 피크속도가 빠른 반면, 코드양이 많아질수록 처음 실행될 때까지 지연이 발생한다. 한편 번역기를 사용한 경우 실행될 때까지 지연은 적어지지만, 실행시 속도는 느려진다.
  2. JavaScript 엔진 성능을 좋게 하려면 메모리 사용량이 많아진다. 반대로 메모리 절약을 하려면 성능이 약해진다.

예로, 아래와 같은 function을 호출하는 코드에 대해 생각해 보자.

이 코드는 function foo() 한번만 호출되지 않는다. 이경우는 실행될 때까지의 속도(Fast Startup)을 중시하고 런타임 성능(Peak Pref)는 중요하지 않다.

그러나 아래와 같은 경우,

만번 function foo()을 호출한다. 이경우 Fast Startup이 늦게라도 Peak Pref를 중요하다. foo()는 네이티브 코드로 컴파일되어 있지 않으면 안된다. 그리고 이를 PC인 경우 메모리도 넉넉하기 때문에 많은 양의 메모리 소비를 희생하고 네이티브 코드를 더 몇번에 걸쳐 최적화시킨다. 그러나 이는 어디까지나 PC의 경우, 1만회 foo()를 호출하는 코드로 모바일의 경우, 동일한 접근이 되지 않는다. Android기기는 모바일이기 때문에 PC처럼 메모리를 처리할 수 없다.

네이티브 코드로 컴팔일되지만 메모리 소비는 억제하도록 최적화 제한된다. 다른 패턴에 대해서도 살펴보자.

이는 Node에서 실행되는 코드이다. 일단 시작되면 계속 돌기 때문에, 당연히 Fast Startup을 희생하고 Peak Perf를 개선하려고 하지만,

IoT기기에서 실행하게 하면 동일한 상황이라고 할  수 없다. 서버 시스템과는 다르게 메모리 소비는 억제하지 않으면 안된다.  Peak Perf를 높이게 할 수 있지만 그것은 메모리 소비가 적게 하는 방식으로 진행된다. 같은 function을 호출하더라도 그 최적화 방법은 다양하며, 컨텍스트 및 기기 상태에 크게 의존하는 것이다. 그리고 그것은 Fast Perf인지 Peak Perf에 최적화되어 가는지 메모리가 넉넉한지 작은지에 대해서도 해결하지 않으면 안된다.

8년 전부터 모든 것을 바꾸려고 하는 V8

V8엔진은 앞에서 설명한 모든 상황에서 최적화를 할 수 있도록 하고 새로운 패턴의 JavaScript(asm.js, WebAssembly등)에 대응할 수 있도록 3년동안 실행파이프라인을 만들어 바꾸고 있었다. 과거 변천사를 살펴보면 2008년 출시 당시 단게에서는 간단한 코드생성기가 있고, 이를 통해 약간의 최적화를 어셈블리 코드가 생성되는 형태였다. 단, 최적화는 계산이 필요하기 때문에 코드를 실행할 수 있는 상태로 만들기 위해 많은 시간이 소요된다. 대량의 자바스크립크 코드를 다룰 때 Startup Time 단축을 위해 최적화작업을 생략할 수 있어야 한다.

2010년 컴파일러 Crankshaft가 추가된다. 계산비용이 높은 최적화 처리를 분리하고 Crankshaft에 그 책임을 옮겼다. 코드 생성뿐이라면, Full-codegen은 최소비용으로 컴퓨터코드를 내놓아 빠르기 때문에 Startup Time이 최적화된다. 그리고 CrankShaft를 사용하여 컴파일하면 게산비용을 줄인 Peak Perf에 가장 알맞은 기계코드로 생서오딘다. 란타임에 CrankShaft기 내놓은 코드를  Full-codegen을 통해 코드를 대체하고 최적화를 도모한다.

2015년, Crankshaft는 한계가 오게 된다. 자바스크립트 사양 자체가 크게 변화하는 시기였지만 Crankshaft는 설계, 자바스크립 기능을 지원하지 못하고, 새로운 패턴의 JavaScript(asm.js, Crankshaft 설계 자바스크립트 기능을 지원하지 못하고 새 패턴의 JavaScript(asm.js, WebAssembly등)도 충분히 대응할 수 없다. 당시 프론트엔드 개발자라면 V8엔진이 ECMAScript표준에 대한 대응 지연에 자극을 느꼈다. V8 개발팀은 이를 개선하고자 새 컴파일러 TurboFan을 추가한다.

2016년, Ignition이라는 구조가 추가되었다. Ignition소스코드를 갑자기 기계코드로 하는 것이 아니라 바이크코드릀 생성하는 컴파일러와 그를 실행하는 인터프리터가 추가되었다. 그리고 TurboFan은 바이트코드를 취급하는 형태로 만들어지고 개조되었다. 그리고 최신 버전인 5.9에서는 이 두가지 방식은 이미 사용되고 있지 않았다. 내부 코드는 존재하고 있지만 바이크코드 생성 및 해석을 위한 인터프리터 Ignition과 PeakPerf를 개선하기 위한 처를 하는 컴파일로 TurboFan에서만 실행되는 상황이다.

그리고 6.0에서는 V8에서 완전히 제거된다.

Ignition Interpreter란?

중간코드를 생성하고 실행하는 인터프리터로 메모리 소비가 적고 실행되는 Fast Startup에 적합한 특징이 있다.

  • 모바일등의 메모리가 작은 환경에서 실행되기 적합하다.
  • Startup Time최적화해야할 코드 실행에 적합하다.
  • TurboFan과 합쳐져 실행속도를 최적화할 수 있다.

TurboFan은?

주로 최적화를 하는데 목적을 하는 컴파일러이다.

사용방법으로는 확정적인 형태이지만, 어떤 새로운 자바스크립트 기능을 대응하고 있어 활용할 수 있다.

WebAssrembly 백엔드이기도 하다.

최근에는 try/catch/finally나 ES2015+ 최적화가 가능하게 되었다.

 

V에 대한 메모리관리를 위한 가비지컬렉터 Orinoco등 다양한 기술 요소가 발전되고 있는 가운데,  TurboFan에 완전히 이행상태로서 거의 끝난 상태이지만, 8년전과는 전혀 다른 web-JavaScript의 변화에 추종되는 변화를 보았다.

Site Footer