티스토리 뷰
최근 웹 프론트엔드에대해 더 깊게 학습하다보니 JavaScript 성능 최적화에도 관심을 가지게 되었습니다. 그 중 흥미롭게 공부한 내용을 정리하여 포스팅으로 작성하고자 합니다. 이번 포스팅은 JavaScript가 정적 타입 언어의 성능을 따라잡기 위해 필수적인 개념인 Hidden Class 에 대해 알아보도록 하겠습니다.
Hidden Class
자바와 같은 정적 타입 언어의 경우 객체의 속성이 고정되어 있기 때문에 객체를 생성할때부터 메모리를 얼마나 할당해야 하는지 정확하게 파악할 수 있습니다. 따라서 객체의 속성을 저장하는 메모리를 연속되기 할당한 뒤 offset기반으로 빠르게 접근하여 사용할 수 있습니다.
하지만 JavaScript는 동적 타입 언어이기 때문에 객체를 생성할때에 메모리를 얼마나 할당해야 하는지 모릅니다. 따라서 속성이 추가될 때마다 랜덤한 주소에 메모리를 할당하고 그 속성을 다루기 위해 위해 딕셔너리랜덤한 메모리에 접근해야 합니다.
또한 수 많은 객체가 모두 동일한 속성들로 이루어져 있다고 하더라도 그 속성들이 언제 바뀔지 모르기 때문에 위의 그림과 같은 Name-Value 쌍을 항상 유지해야 합니다. 이러한 방식은 JavaScript가 다른언어에 비해 느려질 수 밖에 없는 한계를 가지게 만듭니다. 하지만 V8엔진에서는 이를 최적화 시키기 위해 런타임에 내부적으로 숨겨진 클래스를 만들어 사용합니다.
function Point(x, y) { // constructor
this.x = x; // create C1 class
this.y = y; // create C2 class
}
var p1 = new Point(1, 2); // create C0 class
위의 Point 생성자 예제에서 V8엔진은 3개의 클래스를 만들게 됩니다.
- new Point가 호출되었을 때 C0 클래스 생성
- this.x = x; 를 통해 x라는 속성이 추가되었을 때 C1 클래스 생성
- this.y = y; 를 통해 y라는 속성이 추가되었을 때 C2 클래스 생성
객체를 만들고 속성을 추가할 때 마다 새로운 클래스가 만들어지며, 다음에 같은 생성자를 사용하여 다시 객체를 만든다면 이미 만들어 두었던 히든 클래스를 사용하게 됩니다. 또한 히든클래스는 순서에 의존적이기 때문에 만약 위의 예제와 같이 x, y 순서로 할당하는 것이 아닌 y, x 순서로 속성을 할당한다면 또 다른 클래스가 만들어지게 됩니다.
function Point(x, y) { // constructor
this.x = x; // create C1 class
this.y = y; // create C2 class
}
var p1 = new Point(1, 2); // create C0 class
var p2 = new Point(1, 2); // use C2 class
var p3 = new Point(1, 2); // use C2 class
p3.z = 123; // create C3 class
이후 새로운 속성이 추가된다면 새로운 히든클래스를 만들거나 이미 히든클래스를 만든게 있다면 기존의 히든클래스를 사용하게 됩니다. 이렇게 만들어진 히든클래스에는 각 속성의 정적인 Offset이 저장되며 속성의 위치 정보를 해석할 필요가 없어지기 때문에 정적 타입 언어와 같은 성능을 낼 수 있습니다.
마치며
Hidden Class를 통한 JavaScript의 성능을 최적화 시키기 위해서 동일한 객체를 정의할 때 항상 동일한 순서로 멤버를 초기화하고, 동적인 속성 추가를 최소화 시키면 됩니다. 쉽게 말하면 정적 타입과 같이 JavaScript를 사용한다면 성능은 개선된다고 생각합니다.
원래 이번 포스팅에서 Inline Caching 이라는 기법도 다루고자 했으나 검색되는 자료마다 서로 다르게 정리가 되어있어 정확하게 정리하기 위해 다음 포스팅으로 미루도록 하겠습니다.
참고 자료