자바스크립트를 사용하여 코딩을 하면서 항상 가지고 있던 질문이 있습니다.
어떻게 코딩을 해야 효율적인 자바스크립트 코드를 작성할 수 있을까?
결론부터 말하자면, 정적타입 언어로 생각하고 코딩하면 됩니다.
자바스크립트는 타입을 가지고 이지 않는 동적언어 타입이며 컴파일 동작이 없는 인터프리터 언어 입니다.
먼저, 자바스크립트가 엔진에서 사용하는 방식인 Just-in-Time compilation 방식과 Adaptive compiliation에 대해 소개하겠습니다.
현대 대부분의 자바스크립트 엔진(Safari, Chrome, FirFox 등)은 JITC 방식을 사용합니다. JITC는 프로그램 실행 중(런타임) 그 순간에 코드를 컴파일을 하는 코드 실행 방법을 뜻 합니다.
자바스크립트 JITC는 아래 그림과 같은 방식으로 수행됩니다.
당연히 인터프리터로 수행하는 방법보다 네이티브 코드로 수행하는 것이 빠르지만 자바스크립트의 경우 그렇지 않습니다. GCC와 같은 static compiler 라면 네이티브 코드를 생성하는 도중에 컴파일러가 최적화 알고리즘을 적용하지만 자바스크립트의 경우 그렇지 못합니다. 또한 JITC는 프로그램 실행 중 컴파일이 수행 되어서 overhead가 발생합니다. (그렇기 때문에 overhead가 큰 최적화 알고리즘을 적용하지 못해, 최소한의 최적화를 적용합니다)
code quality
컴파일러에 의해 최적화 알고리즘을 적용하요 코드 성능이 좋아진 지표를 이야기 합니다.
자바스크립트 엔진은 bytecode를 실행하는 방법을 JITC, 인터프리터 2가지를 가지고 있습니다. 2가지 방식 중 더 효율적인 방법은 무엇일까요?
JITC 방법은 위에 설명한 문제점 외에도 또 다른 문제점을 가지고 있습니다.
자바스크립튼 변수 타입이 정해지지 않은 동적타입의 언어입니다. 그렇기 대문에 자바스크립트 JIT compiler는 모든 예의적인 케이스를 고려하여 코드를 생성해야 합니다.
만약 덧셈의 두 operand가 모두 int형(JavaScript는 타입이 없는 대신 엔진 내부적으로 타입을 가지고 있습니다)이라면 그냥 더해서 변수 값에 저장하면 되지만, 덧셈의 operand 둘 중 하나라도 int형이 아니거나, 혹은 더하고 보니 integer 범위를 벗어나는 등 예외 케이스가 발생하게 되면
slow case로 건너뛰게 됩니다
slow case
Slow case는 native code로 생성하기 어려운 (정확히는 native code로 표현하면 양이 많아지는) 동작들을 native code로 뽑아내는 대신 미리 엔진 내부에 C로 구현된 function(helper function)을 호출하여 동작을 수행하는 경우를 말합니다. 만약 덧셈에서 int+int, string+string, string+int 이런 케이스를 모두 native code로 뽑아낸다면 단순한 덧셈을 위해 엄청나게 많은 native code가 필요하게 되므로 helper function을 호출하게 됩니다.
또 다른 문제는 자바스크립로 구현된 프로그램들의 목적은 Java와는 많이 다릅니다. Java는 연산이 많은(compute-intensive) 프로그램들이 많은 반면에, JavaScript는 주로 web page의 layout을 건드리거나, 사용자 입력에 반응하는 방식의 프로그램이 많습니다. 두 가지의 가장 큰 차이점은 자주 반복돼서 수행되는 구간(hotspot)이 얼마나 많은가 인데, JavaScript는 상대적으로 hotspot이 매우 적습니다.
즉, 동적타입 언어로 인한 compiler overhead와 적은 hotspot 때문에 자바스크립트는 JITC 방법보다는 인터프리터 방법이 적합해보입니다.
하지만, 당연히 그렇지 않습니다. JITC가 가지고 있는 방법을 개선한 Adaptive JIT Compilation에 대해서 설명해드리겠습니다.
최근 JavaScript 엔진들은 대부분 adaptive complilation방식을 채택하였습니다. Adaptive compilation은 모든 코드를 일괄적으로 같은 수준의 최적화를 적용하는 것이 아니라, 반복 수행되는 정도에 따라 유동적으로(adaptive) 서로 다른 최적화 수준을 적용하는 방식입니다.
아래는 Chrome V8 엔진의 adaptive JITC인 crankshaft의 동작 방식을 간단히 나타낸 것입니다.
기본적으로 모든 코드는 처음에 interpreter로 수행합니다. 그러다가 자주 반복되는 부분(hotspot)이 발견되면, 그 부분에 대해서만 JITC를 적용하여 native code로 수행합니다. 최근의 엔진들은 JITC도 여러 단계로 나누어서 적용합니다. 처음에는 최소한의 최적화만 적용하는 JITC (baseline-JITC)로 컴파일 하여 수행하다가, 여기서 더 자주 반복된다 싶은 코드에는 더 많은 최적화를 적용하는 JITC(Optimizing-JITC)로 컴파일 하여 code quality가 높은 코드를 생성하게 됩니다.
지금까지의 내용을 요약하면 다음과 같습니다.
•Hotspot이 별로 없는 고전적인 JavaScript 프로그램들에는 interpreter가 JITC보다 효율이 좋다.
•최근 많이 사용되는 compute-intensive한 JavaScript 프로그램들에는 JITC가 좋다.
•두 가지 성향의 코드에 대한 성능을 모두 만족하기 위해 최근 엔진들은 adaptive JITC를 채용한다.
•Adaptive JITC는 type profiling을 수행하므로, 변수의 type이 변하지 않는다면 높은 성능을 얻을 수 있다.
C나 Java처럼 동적 타입 언어라고 생각하고 코딩을 하시면 됩니다.