[개념정리] 자바스크립트 엔진 v8이 사용하는 Hidden Class, Inline Cacahing

2021. 1. 18. 14:14프로그래밍-Web/Javascript

engineering.linecorp.com/ko/blog/v8-hidden-class/

 

V8의 히든 클래스 이야기 - LINE ENGINEERING

자바스크립트가 되어 그 기분을 헤아릴 수 있다면 안녕하세요? LINE Fukuoka의 프론트엔드 엔지니어 Yonehara입니다. 저는 프론트엔드 개발자로서 아직 웹 브라우저나 자바스크립트의 기분을 헤아려

engineering.linecorp.com

v8.dev/blog/fast-properties

 

Fast properties in V8 · V8

In this blog post we would like to explain how V8 handles JavaScript properties internally. From a JavaScript point of view there are only a few distinctions necessary for properties. JavaScript objects mostly behave like dictionaries, with string keys and

v8.dev

참고 문헌들

 

 

크롬에서 자바스크립트를 동작시킬때에는 C++로 작성된 V8 엔진을 사용한다

(노드 JS도 동일하게 V8을 사용한다)

 

그림 출처: https://corock.tistory.com/467

V8 엔진의 작동원리는 다음과 같다

 

소스코드를 파싱해서 AST(추상구문트리)로 만들면 인터프리터바이트 코드를 생성한다

이 코드를 대상으로 컴파일러가 데이터 캐싱 및 프로파일링을 고려하여, 최적화된 기계어를 생성한다

 

 

 

사실 V8은 각 property마다 Hidden Class를 생성한다. 

 

자바스크립트는 타입이 동적으로 결정된다. 이를 Dynamic Typing 언어라고 한다.

이는 Static Typing 언어에 비해 객체의 property에 접근하는 속도가 느리다. 

 

컴퓨터 메모리는 세그먼트오프셋으로 관리되는데,

세그먼트는 현재 사용할 메모리 영역이고 그것을 더 잘게 쪼갠것이 오프셋이다.  

코드를 동작시키려면 메모리를 사용해야 하므로, 항상 사용할 특정 오프셋을 정해둬야 한다

 

Static Typing언어는 type을 설정하는 순간 얼마만큼의 메모리가 할당되어야 하는지 정해지기 때문에 오프셋이 결정된다.(길이가 정해지지 않은 배열이나 객체는 예외)

그러나 Dynamic Typing 언어는 이를 미리 결정할 수 없다

따라서 특정 객체의 property에 접근할때마다, dynamic lookup을 통해 property를 스캔하고 찾아내야 한다.

이는 리소스 소모가 많이 드는 일이다.

(사실 이러한 부분은 Prototype언어와 Class언어의 차이기도 하다. Class언어라면 Class에 속한 객체들이 모두 같은 필드 구조를 가져 어떤 변수에 접근하고, 메모리는 어디인지 알 수 있다, 그러나 Prototype은 그것이 불가능하다)

 

이 탐색을 피하기 위해 V8이 쓰는 방식이 Hidden Class이다

객체를 만들때 Hidden Class라는 객체를 만들어 오프셋 정보를 저장하는것이다

따라서 다음과 같은 특징을 가지고 있다

 

1) 반드시 하나의 객체마다 부여되며,

2) 각 property에 대해 오프셋 정보를 가지고 있고, 

3) 객체에 property가 추가,수정,삭제되면, 새로운 Hidden Class가 만들어지며, 이는 기존 Hidden Class정보에 추가로 업데이트된 정보를 가지고 있게 된다  

4) 3의 과정에서 원래의 Hidden Class는 참조해야되는 Hidden Class 정보가 추가된다

 

예시를 들어보면

 

이렇게 객체를 생성하는 순간 obj에 대한 Hidden Class A1가 생겨난다

이 시점에서 A1는 아무런 정보도 가지고 있지 않다.

 

이렇게 property를 배정하는 순간, Hidden Class A2가 생겨나고 obj는 A2를 가리키게 된다

이때 A1에는 'X를 추가하여 참조하는 Hidden Class가 A1에서 A2로 변경되었다' 라는 정보가 추가되는데, 이를 Transition이라고 한다 

이러한 과정이 연쇄적으로 일어나면 하나의 객체에 대한 Hidden Class Cycle이 만들어진다

인스턴스가 새로 생성될때마다  Hidden Class Cycle를 타고 올라가서 동일한 오프셋을 찾아내 사용한다

 

예컨데 이렇게 하나의 객체에서 두개의 인스턴스를 생성한다고 해보자

 

메모리 사용상황에서 나타나는 Map정보가 연결된 오프셋이고, 동일한 Hidden Class를 참조함을 알 수 있다.

 

이렇게 student1에 age라는 객체를 추가해보자

이렇게 트랜지션이 발생하며 이전 Hidden Class를 참조함을 알 수 있다.

 

V8의 최적화 포인트는 최소한의 Hidden Class를 만드는 것이다.

메모리 소모는 줄일지언정, property를 변경할때마다 Hidden Class가 생성되니 말이다

이를 방지하기 위해 Inline Caching을 사용한다

이 방식의 가정은 사실상 객체 필드 구조가 거의 변경되지 않으니, 오프셋값을 미리 캐싱해주겠다는것이다.

 

이 방식은 Loop에서 적용된다. 

컴파일러 최적화 분야에서는 한 번 수행된 코드는 한 번만 수행될 가능성이 높지만, 두 번 수행된 코드는 이후에 더 수행될 확률이 높다는 가정이 자주 수반된다

따라서 예시 코드에서는 arr[0]의 오프셋값을 첫 루프때 캐싱해두었다가 1에서 9까지는 캐싱한 오프셋을 사용하는 것이다.

(바꿔말하면 한번만 수행되는 코드는 Inline Caching이 필요없다)