제멋대로 JS 객체 프로퍼티를 읽어내는 법(feat.V8 Hidden Class)

쏘쏘임·2022년 8월 7일
1
post-thumbnail
post-custom-banner

이번 포스팅의 목적은 다음과 같습니다.
1. 타입이 마구 변하는 JS 객체의 프로퍼티를 어떻게 빨리 읽어내는가에 대한 호기심 해결
2. 이왕이면 불필요한 동적 타이핑 남발을 자제하며 최적화할 수 있는 관점 생각해보기

🚩 사전 정보

객체 메모리 저장 : 메모리 오프셋

0⬛⬛ 1⬜⬜⬜2⬛⬛⬛

기본적으로 객체의 프로퍼티에 접근하면 객체의 기준 메모리 주소와 상대적인 값인 오프셋을 통해 값을 읽어냅니다. 각각의 오프셋은 들어올 데이터 타입을 통해 결정합니다. (number: 8byte, string: 문자열 참조값...)

  • 정적 타이핑 언어(C++, Java ...)
    • 컴파일 이전에 객체 레이아웃이 고정되기 때문에 이때 고정된 오프셋으로 프로퍼티에 바로 접근 가능
  • Javascript는 동적 타이핑 언어다
    • 동적 타이핑 언어로 구현되어 프로퍼티의 데이터 타입이나 순서, 갯수가 언제 어떻게 바뀔지 모릅니다. 즉, 선언시 오프셋 값은 참조할 수 없게 됩니다.
    • 프로퍼티 값을 읽을 때마다 프로퍼티를 찾아내야 하는 동적 탐색(dynamic lookup)이 필요합니다.
    • js 인터프리터는 메모리에 객체를 저장할 때 해쉬 테이블 형식을 사용하여 접근합니다. 이는 오프셋 값으로 바로 접근하는 것보다 상대적으로 오랜 시간이 걸립니다.

→ 동적 탐색을 회피하고자 V8엔진은 히든 클래스를 사용해 객체의 구조(오프셋 값)을 저장합니다.

🚩 히든 클래스(shape)

히든 클래스는 V8엔진에서 사용하는 데이터로 객체의 구조(프로퍼티의 오프셋)와 히든클래스 전환에 대한 정보를 담고 있습니다. 자바스크립트의 '클래스' 개념과의 혼란을 막기 위해 shape라고도 불립니다.

function Student(name, age, grade) {
    this.name = name;   //2
    this.age = age;     //3
	this.grade = grade; //4
}
var Jim = new Student("jim", 19, 'A'); //1
var Mary = new Student("Mary", 25, 'c'); //5
var Karin = new Student("Karin", 25, ['B','C']); //6

위의 예제에서 Student 생성자 함수에 대한 인스턴스 3개를 new 연산자를 이용해 생성했습니다. Jim, Mary에서는 grade 프로퍼티 값으로 문자열을 넣어주었지만 세번째 인스턴스 Karin에서 배열['B','C']로 넣었습니다.

아래는 히든 클래스가 생성되는 과정입니다.

히든클래스 생성 과정

해당 생성자 함수를 호출해서 인자를 하나씩 넣어줄 때마다 히든 클래스가 생성되고, 생성된 히든클래스를 연결하고 있습니다. name, age, grade 값이 스트링, 숫자, 스트링인 Student메서드는 바로 히든클래스의 객체 구조에 접근할 수 있습니다.

C4에서는 grade를 위한 매개변수의 인자에 배열을 넣어주자 새로운 히든 클래스를 사용하는 것을 볼 수 있습니다.

히든 클래스 변경 개발자도구에서 확인하기

  1. 위의 코드를 콘솔 창에 입력
  2. Memory 에서 스냅샷 찍기
  3. Memory에서 Student 검색
  4. Map에 지정된 식별값이 히든 클래스의 ID

개발자도구 확인1
개발자도구 확인2

  1. Student 객체 3개가 모두 같은 히든 클래스를 가지고 있습니다.
  2. grade 프로퍼티에 연결된 히든클래스 ID : 스트링 값으로 동일하게 넣어준 Jim 과 Mary의 ID는 같지만, 배열타입을 넣어준 Karin은 ID가 달라진 것을 볼 수 있습니다.

개발자도구 확인3

  1. 이번엔 Karin과 같이 grade 프로퍼티에 배열을 넣어준 hey 객체를 살펴봅시다. 이번엔 Karin의 히든클래스 맵을 재사용한 것을 볼 수 있습니다.

히든 클래스의 특징

객체 프로퍼티의 수, 순서, 데이터타입이 변할 때 마다 새로운 히든 클래스를 생성한다.

  • 객체는 반드시 하나의 히든 클래스를 참조합니다.
  • 히든 클래스는 각 프로퍼티에 대한 메모리 오프셋과 이전 히든클래스로부터의 전환 정보를 가지고 있습니다.
  • 기존 프로퍼티에 대한 추가, 삭제, 데이터타입의 변경 등 동적 타이핑이 일어날 때 신규 히든 클래스가 생성됩니다. 이 때 신규 히든 클래스는 기존 프로퍼티에 대한 정보를 가지고 오며 변경된 프로퍼티의 오프셋을 생성한다.

🚩 결론

도입부에서 언급했듯 이번 포스팅은 히든 클래스가 JS 객체를 읽어낼 때 어떻게 최적화를 하는지에 대한 개인적인 호기심을 해결하기 위한 것입니다.

V8 엔진의 깊은 원리까지는 알아볼 수 없었지만 궁금했던 부분을 조금이나마 해결할 수 있는 주제였습니다.

다음은 히든 클래스의 개념을 이해하고 할 수 있는 최적화 관점에서의 행동 지령입니다 :)

👉 객체를 다룰 때 프로퍼티 순서를 동일하게 처리 하자 (불필요한 히든 클래스 생성 방지)

👉 객체를 생성할 때 프로퍼티 초기화도 같이하자

👉 불필요하게 객체의 프로퍼티 데이터타입 및 순서, 갯수를 가능한 변경하지 말자

👉 (내 생각) 타입스크립트를 사용할 수 있다면 왠만하면 사용하자

참고 링크

Franziska Hinkelmann: JavaScript engines - how do they even? | JSConf EU

V8의 히든 클래스 이야기 - By Masami Yonehara | 2018.06.14 

V8의 히든 클래스 이야기 - LINE ENGINEERINGengineering.linecorp.com

profile
무럭무럭 자라는 주니어 프론트엔드 개발자입니다.
post-custom-banner

1개의 댓글

comment-user-thumbnail
2023년 8월 21일

좋은 글 감사합니다. 특히나 생성자를 만들어서 메모리 탭 스냅샷을 찍고 하나하나 캡쳐해주신게 이해에 도움이 많이 됐습니다!

답글 달기