자바스크립트 엔진의 최적화 기법 - JITC, Adaptive Compilation
Just-in-Time Compilation (JITC)
자바스크립트 엔진은 인터프리터 방식으로 동작한다고 알고 있지만, 초기 버전의 자바스크립트 엔진은 수행되는 모든 자바스크립트 코드를 바로(just-in-time) native code로 컴파일하는 방식이었다.
JITC 방식은 javascript source를 bytecode 형식으로 파싱하여 중간 언어(IR)로 변환하고, native code로 컴파일하여 수행한다.
만약 인터프리터 방식이라면 native code로 컴파일하지 않고 bytecode 형식을 하나씩 읽어가며 동작을 수행한다.
JavaScript에서는 컴파일러보다 인터프리터가 더 낫다?
JavaScript는 변수의 타입이 달라질 수 있고, class 대신 object로 상속되는 프로토타입 기반 방식의 동적인 언어이기 때문에 Java같은 정적 언어에 비해 컴파일러 방식의 효율성이 떨어진다.
Java는 연산이 많은 반면에, JavaScript는 주로 웹페이지의 레이아웃을 수정하거나, 사용자 입력에 반응하는 방식이 많다.
두 가지의 가장 큰 차이점은 자주 반복해서 수행되는 구간(hotspot)이 얼마나 많은가 인데, JavaScript는 상대적으로 hotspot이 매우 적다.
Hotspot이 적어지게 되면 native code를 수행하는 시간에 비해 그 native code를 만드는 시간, 즉 compile overhead가 상대적으로 커지게 되는 문제가 있다.
결과적으로 compilation overhead + native 가 인터프리터의 수행시간 보다 짧을 것이라는 JITC의 가정이 깨지게 된다.
Adaptive JIT Compilation (Adaptive JITC)
그래서 최근 JavaScript 엔진들은 대부분 adaptive compilation 방식을 택하고 있다.
adaptive compilation란 모든 코드를 일괄적으로 컴파일하는 것이 아니라, 반복 수행되는 정도에 따라 유동적으로(adaptive) 컴파일하는 방식이다.
기본적으로 모든 코드를 처음에는 interpreter로 수행하고, 자주 반복되는 부분(hotspot)이 발견되면 그 부분에 대해서만 컴파일러를 적용하여 native code로 컴파일하는 것이다.
runtime profiler는 함수의 수행 빈도를 기록하며 변수들의 타입이나 값을 profile 해두었다가 optimizing JIT을 적용할 때 이 정보들을 이용하여 예전 JITC에서 생성했던 예외 처리 루틴들을 대폭 생략한 효율적인 코드를 생성한다.
이러한 방식은 실제로는 동적인 변화가 자주 일어나지 않을 것이라고 가정한다.
따라서 동적인 변화가 발생했을 때 페널티는 크더라도, 변화하지 않았을 때 큰 성능 이득을 볼 수 있다.
정리
- Hotspot이 별로 없는 고전적인 JavaScript 프로그램들에는 인터프리터가 컴파일러보다 효율이 좋다.
- 최근 많이 사용되는 compute-intensive한 JavaScript 프로그램들에는 컴파일러가 좋다.
- 두 가지 성향의 코드에 대한 성능을 모두 만족하기 위해 최근 엔진들은 adaptive JITC를 채용한다.
- Adaptive JITC는 type profiling을 수행하므로, 변수의 type이 변하지 않는다면 높은 성능을 얻을 수 있다.
참고사이트
자바스크립트 엔진의 최적화 기법 (1) - JITC, Adaptive Compilation
JITC, Adaptive-JITC
A crash course in just-in-time (JIT) compilers