본문 바로가기
Javascript

원시 타입과 참조 타입 - 1. V8 엔진의 숫자 처리 방식

by East-K 2024. 10. 17.

원시 타입과 참조 타입

자바스크립트의 데이터 타입에 대해 MDN 문서에는 아래와 같이 나와 있습니다. 자바스크립트의 데이터 타입은 원시 타입(Primitive types)참조 타입(Reference types)으로 나뉘며, 각각 고유한 특징을 가지고 있습니다. (MDN 문서 참고).

1. 원시 타입(Primitive Types)

MDN 문서에 따르면, 자바스크립트는 7가지 원시 타입을 정의합니다:

  • Boolean: 참(true)과 거짓(false)을 나타냅니다.
  • null: 값이 없음을 나타내는 키워드입니다.
  • undefined: 값이 정의되지 않은 변수가 가지는 기본 값입니다.
  • Number: 정수 및 부동소수점 숫자를 포함합니다. 예: 42, 3.14.
  • BigInt: 매우 큰 정수를 나타내며, 예를 들어 9007199254740992n과 같은 값을 처리할 수 있습니다.
  • String: 텍스트 값을 나타내는 데이터 타입입니다. 예: "Hello".
  • Symbol: 고유하고 불변인 식별자를 나타냅니다.

이 원시 값들은 불변성(immutable)을 가지고 있으며, 값 자체는 수정할 수 없습니다. 예를 들어, 변수에 할당된 원시 값은 변경할 수 없지만, 변수를 재할당하여 다른 값을 가질 수는 있습니다. MDN 문서에 따르면, 원시 값에는 메서드가 없지만, 자바스크립트는 이를 자동으로 래퍼 객체(wrapper object)로 변환하여 메서드처럼 사용할 수 있게 해줍니다. 예를 들어 "foo".includes("f")는 문자열을 자동으로 String 객체로 변환하고 그 객체의 includes() 메서드를 호출하는 방식입니다.

2. 참조 타입(Reference Types)

참조 타입의 대표적인 예는 객체(Object)입니다. MDN 문서에는 속성들의 모음(collection of properties)으로 정의되며, 객체의 속성 값은 어떤 타입이든 될 수 있다고 설명되어 있습니다. 객체는 속성으로 다양한 데이터 구조를 담을 수 있으며, 키는 문자열 또는 심볼만 가능합니다.

객체의 속성에는 두 가지 유형이 있습니다:

  1. 데이터 속성(Data property): 실제 값을 저장하는 속성입니다.
  2. 접근자 속성(Accessor property): 값을 가져오거나 설정할 때 사용되는 gettersetter 함수로 구성된 속성입니다.

참조 타입은 가변성(mutable)을 가지며, 객체의 속성 값을 자유롭게 추가, 삭제, 수정할 수 있습니다.



공식 문서를 보면 다양한 타입의 값들이 있는 것은 알지만, 원시 타입참조 타입이 어떤 차이로 인해 두 개의 큰 카테고리로 분류되는지 감이 잘 오지 않을 수 있습니다. 특히, 이 차이가 메모리에 어떻게 영향을 미치는지 공식 설명을 찾아봐도 관련 내용이 명확하지 않습니다. 따라서 V8 엔진을 기준으로 실제 값이 메모리에 어떻게 저장되는지 크롬 개발자 도구를 통해 알아보겠습니다.

V8 엔진의 숫자 처리 방식

자바스크립트에서 숫자는 Number 타입으로 처리되지만, V8 엔진의 내부 코드를 보면 숫자를 Smi(Small Integer)Heap Number로 나누어 관리하고 있습니다.

1. Smi (Small Integer) (V8/smi 코드)

Smi는 Small Integer의 약자로, 자바스크립트 엔진이 자주 사용하는 작은 정수들을 효율적으로 처리하는 방식입니다. Smi는 약 -(2^30)에서 2^30 - 1 범위의 숫자들을 관리합니다. 이 범위 내의 작은 숫자들은 한 번 메모리에 할당되면, 동일한 값이 반복해서 사용될 때 기존에 할당된 메모리를 재사용합니다. 이 방식은 불필요한 메모리 할당을 줄여, 메모리 사용량을 효율적으로 관리할 수 있습니다.

즉, Smi는 작은 숫자를 빠르게 처리하기 위한 방식으로, 이런 숫자들은 값이 자주 사용되기 때문에 메모리를 효율적으로 재사용하면서 CPU 성능을 최적화합니다.

  • 메모리 관리: 작은 정수는 힙 메모리에 별도로 할당되지 않고, 엔진이 효율적으로 관리하는 특수한 메모리 영역에서 처리됩니다.
  • 메모리 재사용: 동일한 값을 재사용함으로써 메모리 사용량을 줄이고, 불필요한 메모리 할당을 방지합니다.

2. Heap Number (V8/heap-number 코드)

Smi 범위를 벗어나는 큰 숫자나 부동소수점 숫자는 Heap Number로 처리됩니다. 이 숫자들은 V8의 힙 메모리에 할당되며, 자주 사용되는 값이 아니기 때문에 새로운 메모리 공간을 할당받습니다. 이 때문에 Heap Number는 값이 같더라도 각 인스턴스가 서로 다른 메모리 주소를 가지게 됩니다.

  • 메모리 할당: 큰 숫자는 힙에 별도로 저장되며, 각 인스턴스는 고유의 메모리 공간을 차지합니다.
  • 성능 최적화: Heap Number는 메모리 할당을 최소화하기보다는 숫자를 빠르게 처리하는 데 중점을 두어, 성능을 향상시킵니다. 메모리에 동일한 값이 이미 존재하는지 확인하는 과정에서 발생할 수 있는 검색 비용을 피하기 위해, 각 큰 숫자는 새로운 메모리 공간을 할당받습니다. 이는 메모리 효율성은 떨어질 수 있지만, 숫자 처리 속도를 빠르게 유지하는 데 기여합니다.

왜 같은 Number 타입이지만 다르게 처리되는가?

숫자가 SmiHeap Number로 나뉘어 저장되는 이유는 성능 최적화 때문입니다. 작은 정수는 자주 사용되며, 이들을 빠르게 참조하고 메모리 할당 오버헤드를 줄이는 것이 중요합니다. 반면, 큰 숫자는 상대적으로 덜 자주 사용되기 때문에, 각 숫자에 대해 새로운 메모리 공간을 할당함으로써 성능 저하를 방지합니다.


예시 코드

다음 코드는 SmiHeap Number의 차이를 보여줍니다.

class NumberMemorySnapshot {
  constructor() {
    this.number1 = 1;       // Smi
    this.number2 = 2 ** 30; // HeapNumber
  }
}

// 인스턴스를 생성합니다.
const numberMemorySnapshot1 = new NumberMemorySnapshot();
const numberMemorySnapshot2 = new NumberMemorySnapshot();

위 코드를 크롬 개발자 도구의 메모리 탭에서 힙 스냅샷을 찍어보면, number1smi number로 처리되어 동일한 값이 여러 인스턴스에서 같은 메모리 주소(@55403)를 공유합니다. 반면 number2Heap Number로 처리되어, 각 인스턴스가 다른 메모리 주소(@54455, @54459)를 가지게 됩니다. (아래 사진 참고)

결론

자바스크립트에서 원시 타입참조 타입으로 나누는 기준은 값의 불변성저장 방식(값 자체로 저장되는지, 참조로 저장되는지)에 따라 구분됩니다. 원시 타입 내에서도 V8 엔진은 성능 최적화를 위해 숫자 타입을 SmiHeap Number로 나누어 처리합니다. 이는 원시 타입인 Number가 자주 사용되는 작은 정수는 메모리를 효율적으로 재사용하여 메모리 사용량을 줄이는 동시에, 큰 숫자는 빠르게 처리하기 위해 별도의 메모리 공간을 할당받는 방식입니다.

 

관련 공식 문서 정보를 찾기가 힘들어 크롬 개발자 도구를 통한 실제 메모리 영역 테스트와 V8 코드를 기반으로 작성하였습니다. 잘못된 정보가 있거나 궁금한 점이 있으면 댓글 부탁드립니다.


원시 타입과 참조 타입 시리즈

  1. V8 엔진의 숫자 처리 방식 (본 게시글)
  2. V8 엔진의 문자열 처리 메커니즘
  3. V8 엔진의 Oddball 타입
  4. V8 엔진의 참조 타입