JavaScript 엔진이 객체, 배열 및 맵을 최적화하는 방법 (V8 성능 가이드)

발행: (2025년 12월 23일 오후 08:53 GMT+9)
10 min read
원문: Dev.to

Source: Dev.to

번역할 텍스트가 제공되지 않았습니다. 번역이 필요한 전체 내용을 알려주시면 한국어로 번역해 드리겠습니다.

V8이 데이터 구조를 최적화하여 눈에 보이지 않는 성능 저하를 방지하는 방법

어느 순간, 모든 JavaScript 애플리케이션은 벽에 부딪히게 됩니다. 오류가 발생하지도 않고 예외도 나타나지 않지만, 점점 느려지는 느낌이 듭니다—리스트가 렌더링되는 데 시간이 더 오래 걸리고, 폼이 둔해지며, 한때 사소해 보였던 루프가 이제는 중요한 문제가 됩니다. 코드는 여전히 정상적으로 동작하지만, 애플리케이션이 커짐에 따라 성능이 조용히 저하됩니다. 대부분의 경우 문제는 비즈니스 로직이 아니라 JavaScript 엔진(특히 Chrome과 Node.js에서 사용되는 V8)이 데이터가 구조화된 방식을 어떻게 처리하느냐에 있습니다.

이 가이드는 조기 마이크로 최적화를 다루는 것이 아닙니다. 데이터 패턴 중 일부가 매끄럽게 확장되는 반면, 다른 패턴은 엔진의 최적화를 은밀히 비활성화시키는 이유를 이해하는 데 초점을 맞춥니다.

Objects and Hidden Classes

JavaScript 객체는 유연한 키‑값 가방처럼 보이지만, V8은 이를 매우 다르게 취급합니다. 속성 접근을 빠르게 만들기 위해 V8은 shape에 따라 객체를 그룹화하고 hidden class (내부적으로는 Maps 라고 부름)를 사용합니다. hidden class는 다음을 기록합니다:

  • 객체가 가지고 있는 속성들
  • 그 속성들이 추가된 순서

많은 객체가 동일한 shape을 공유하면, V8은 속성 접근을 적극적으로 최적화할 수 있습니다.

Consistent Shape

const a = {};
a.name = "John";
a.age = 32;

const b = {};
b.age = 35;
b.name = "Michel";

두 객체는 동일한 hidden class를 갖게 되며, V8은 객체의 hidden class와 name의 정확한 메모리 오프셋을 캐시할 수 있습니다. shape이 동일하게 유지되는 한, V8은 속성 조회를 완전히 건너뛸 수 있습니다.

shape이 달라지면 인라인 캐시가 polymorphic이 되고, 이어서 megamorphic이 되어 결국 디옵티마이즈가 발생합니다.

Class‑based Instances

class User {
  constructor(name, age, role) {
    this.name = name;
    this.age = age;
    this.role = role;
  }
}

const users = [
  new User("Farhad", 32, "developer"),
  new User("Sarah", 28, "designer"),
];

모든 인스턴스가 동일한 hidden class를 공유하므로, 빠르고 예측 가능한 속성 접근이 가능해집니다—리스트, 모델, 자주 생성되는 객체에 이상적입니다.

One‑off Objects

const formData = {
  name: "Farhad",
  age: 32,
  role: "developer",
};

설정 파일, API 페이로드, 혹은 한 번만 생성되는 객체의 경우 shape 재사용이 크게 중요하지 않을 수 있습니다.

Dynamically Adding Properties (Bad)

const user = {};
for (const key of keys) {
  user[key] = getValue(key);   // each new key may create a new hidden class
}

새로운 속성이 추가될 때마다 객체의 shape이 바뀔 수 있어, V8이 새로운 hidden class를 생성하게 됩니다.

Safer Dynamic Assignment

const user = {
  name: undefined,
  age: undefined,
  role: undefined,
  email: undefined,
};

for (const key of keys) {
  if (key in user) {
    user[key] = getValue(key);
  }
}

shape을 미리 정의해 두면 hidden‑class의 안정성을 유지하면서도 유연한 할당이 가능합니다.

배열

배열은 매우 빠릅니다—하지만 실수로 느리게 만들기 전까지는 말이죠. 빠른 배열을 선반에 꽉 붙어 있는 동일한 상자들이 일렬로 놓인 모습이라고 생각해 보세요. 상자가 하나라도 빠지지 않고 모두 같은 타입이면 V8이 효율적으로 순회합니다. 빈 공간이나 타입이 섞이면 성능이 떨어집니다.

V8은 element kinds 를 추적하여 배열이 어떻게 저장될지 결정합니다:

Element Kind설명
SMI_ELEMENTS작은 정수 (가장 빠름)
DOUBLE_ELEMENTS부동소수점 숫자 (빠름)
ELEMENTS혼합 또는 객체 값 (느림)

한 번 배열이 더 느린 kind 로 전환되면 다시 업그레이드되지 않습니다.

희소 배열 (구멍)

const arr = new Array(3);
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;   // 초기 구멍 때문에 더 느린 표현 방식 사용

혼합 타입

const nums = [1, 2, 3];
nums.push(4.5);
nums.push("5");   // ELEMENTS 로 영구 다운그레이드

요소 삭제

delete arr[5];    // 구멍을 만들며 → 느려짐
// 권장:
arr.splice(5, 1);

숫자 연산을 위한 Typed Arrays

const data = new Float64Array(1024);

Typed array는 고정된 요소 타입을 사용하고, hidden‑class 오버헤드를 피하며, 예측 가능한 메모리 레이아웃을 제공하므로 수학 연산이 많거나 바이너리 데이터를 처리할 때 이상적입니다.

Map과 일반 객체

속성을 동적으로 추가하고 제거하는 무거운 객체 변형은 V8을 사전 모드 (해시‑테이블 저장)로 강제합니다. 이로 인해 다음과 같은 비용이 발생합니다:

  • 숨겨진 클래스 전환 (CPU 비용)
  • 인라인 캐싱이 작동하지 않음
  • 속성 접근이 해시 기반이 됨

동적 키 집합의 경우 Map이 적절한 데이터 구조입니다.

일반 객체 예시 (작고 고정된 데이터셋)

const store = {};
store[userId] = data;

작고 정적인 컬렉션에서는 잘 동작하지만 키 집합이 커지면 비효율적이 됩니다.

Map 예시 (동적 키)

const store = new Map();
store.set(userId, data);

비교 표

작업객체 (안정적)객체 (동적)Map
읽기O(1) (IC)O(1) (Hash)O(1)
쓰기저렴함비쌈저렴함
삭제비쌈비쌈저렴함
크기O(n)O(n)O(1)

언제 어떤 것을 사용할까

구조최적 용도
객체고정 구조, 읽기 중심 데이터
배열순서가 있는, 밀집된 컬렉션
Map동적 키, 빈번한 변형

실용적인 가이드라인

  • 걱정하지 마세요 코드가 핫 경로가 아니거나 데이터셋이 작고 객체가 한 번만 생성되며 가독성이 떨어질 경우 객체나 배열 최적화에 대해.
  • 경험법: 먼저 프로파일링하고, 그 다음 최적화한다.

대부분의 JavaScript 성능 문제는 “느린 코드”보다 데이터 구조가 맞지 않아서 발생합니다. 기억하세요:

  • 안정적인 형태를 가진 객체
  • 조밀하고 동질적인 배열
  • Map에 저장된 동적 데이터

V8이 데이터 패턴을 예측할 수 있으면, 무거운 작업을 대신 수행합니다. JavaScript 성능은 영리한 트릭이 아니라 예측 가능하고 의도적인 데이터 구조에 달려 있습니다.

핵심 요약: 객체는 일관되게, 배열은 조밀하게, 동적 데이터는 Map에 보관하세요. 핫 경로를 프로파일링하고, 이해하기 쉬운 코드를 작성하면 V8이 속도를 제공할 것입니다.

Back to Blog

관련 글

더 보기 »

JavaScript 코드 성능을 향상시키는 방법

고성능 JavaScript는 예측 가능성에 관한 것입니다. 인간에게는 지루하지만 JIT 컴파일러에게는 즐거운 코드를 작성하세요. 성능은 마법이 아닙니다. 그것은 나입니다.

JS에서 함수, 객체 및 배열.

함수 함수는 특정 작업을 수행하고 재사용할 수 있는 코드 블록입니다. JavaScript에서 함수를 정의하는 방법은 세 가지가 있습니다: 1. Function D...