V8은 Chrome과 Node.js에서 사용되는 자바스크립트 엔진입니다. 동적 타입 언어인 자바스크립트를 어떻게 빠르게 실행하는지 내부 원리를 이해하면, 성능 좋은 코드를 작성하는 데 큰 도움이 됩니다.

V8의 실행 파이프라인

PLAINTEXT
소스 코드 → 파서(AST) → Ignition(인터프리터, 바이트코드)
                        ↓ (핫 코드 감지)
                   TurboFan(JIT 컴파일러, 기계어)
  1. Ignition: 바이트코드 인터프리터로 빠르게 실행 시작
  2. TurboFan: 자주 실행되는 코드(핫 코드)를 최적화된 기계어로 컴파일
  3. Deoptimization: 최적화 가정이 깨지면 바이트코드로 폴백

Hidden Class (Maps)

V8은 동적 객체에 정적 언어처럼 구조(Shape)를 부여합니다.

JS
// V8은 객체의 "형태"를 추적함
function Point(x, y) {
  this.x = x; // Hidden Class C0 → C1 (x 추가)
  this.y = y; // Hidden Class C1 → C2 (y 추가)
}

const p1 = new Point(1, 2);
const p2 = new Point(3, 4);
// p1과 p2는 같은 Hidden Class를 공유 → 최적화 가능!

// 같은 순서로 프로퍼티를 추가하면 같은 Hidden Class
const a = {};
a.x = 1;
a.y = 2;

const b = {};
b.x = 3;
b.y = 4;
// a와 b는 같은 Hidden Class

// 다른 순서 → 다른 Hidden Class (비효율)
const c = {};
c.y = 1; // 순서가 다름!
c.x = 2;
// c는 a, b와 다른 Hidden Class

성능 팁: Hidden Class 최적화

JS
// 나쁜 예: 프로퍼티 추가 순서가 다름
function createUser(type) {
  const user = {};
  if (type === "admin") {
    user.role = "admin";
    user.name = "관리자";
  } else {
    user.name = "사용자"; // 순서가 다름!
    user.role = "user";
  }
  return user;
}

// 좋은 예: 항상 같은 순서로 프로퍼티 초기화
function createUser(type) {
  return {
    name: type === "admin" ? "관리자" : "사용자",
    role: type === "admin" ? "admin" : "user",
  };
}

// 나쁜 예: 동적으로 프로퍼티 추가/삭제
const obj = { a: 1, b: 2 };
delete obj.b; // Hidden Class 변경 → 최적화 해제

// 좋은 예: 프로퍼티를 null로 설정
obj.b = null; // Hidden Class 유지

Inline Cache (IC)

프로퍼티 접근 위치를 캐시하여 반복 접근을 빠르게 합니다.

JS
function getX(obj) {
  return obj.x; // 이 접근 패턴을 캐시
}

// 같은 Hidden Class 객체를 반복 전달 → Monomorphic IC (가장 빠름)
const points = [
  { x: 1, y: 2 },
  { x: 3, y: 4 },
  { x: 5, y: 6 },
];
points.forEach(getX); // 모두 같은 형태 → 빠름

// 다른 Hidden Class 객체를 전달 → Megamorphic IC (느림)
getX({ x: 1 });
getX({ x: 1, y: 2 });
getX({ x: 1, y: 2, z: 3 });
getX({ a: 0, x: 1 }); // 형태가 다양 → 캐시 효과 감소

IC 상태

상태설명성능
Monomorphic1가지 형태가장 빠름
Polymorphic2-4가지 형태보통
Megamorphic5가지 이상느림

JIT 최적화와 Deoptimization

JS
// TurboFan이 최적화하는 코드
function add(a, b) {
  return a + b;
}

// 항상 숫자로 호출 → 정수 덧셈으로 최적화
for (let i = 0; i < 10000; i++) {
  add(i, i + 1); // 핫 코드 → JIT 컴파일
}

// 갑자기 문자열 전달 → Deoptimization 발생!
add("hello", "world"); // 가정이 깨짐 → 바이트코드로 폴백

Deoptimization을 피하는 방법

JS
// 1. 타입을 일관되게 유지
function process(value) {
  return value * 2;
}
// 항상 숫자만 전달

// 2. try-catch를 최소 범위로
// 나쁜 예: 함수 전체를 try-catch로 감싸기
function processBad(data) {
  try {
    // 긴 로직... TurboFan이 최적화하기 어려움
  } catch (e) {}
}

// 좋은 예: 에러 발생 가능 부분만 분리
function processGood(data) {
  const parsed = safeParse(data); // try-catch는 이 안에서만
  // 나머지 로직은 최적화 가능
}

// 3. arguments 사용 피하기
// 나쁜 예
function bad() {
  return arguments[0] + arguments[1]; // 최적화 방해
}

// 좋은 예
function good(a, b) {
  return a + b;
}

메모리 관련 최적화

JS
// SMI (Small Integer) — 포인터 없이 직접 저장
const smi = 42; // 효율적

// HeapNumber — 힙에 별도 할당
const heapNum = 1.5; // 소수점 → 덜 효율적

// 배열 최적화
const packed = [1, 2, 3]; // PACKED_SMI_ELEMENTS (가장 빠름)
const withHoles = [1, , 3]; // HOLEY_SMI_ELEMENTS (구멍 있음)
const mixed = [1, "two", 3]; // PACKED_ELEMENTS (타입 혼합)

// 배열 성능 팁
// 1. 구멍 만들지 않기
// 2. 타입 혼합하지 않기
// 3. 배열 크기를 미리 알면 미리 할당

실전 성능 팁 요약

이유
프로퍼티 추가 순서 일관성Hidden Class 공유
프로퍼티 delete 피하기Hidden Class 변경 방지
함수에 같은 타입 전달Monomorphic IC 유지
배열 타입 혼합 피하기배열 요소 종류 최적화
try-catch 범위 최소화JIT 최적화 범위 확대

**기억하기 **: V8은 Hidden Class로 동적 객체에 구조를 부여하고, Inline Cache로 프로퍼티 접근을 캐시합니다. "같은 형태의 객체를 일관되게 사용"하는 것이 V8 최적화의 핵심입니다. 실무에서는 마이크로 최적화보다 알고리즘과 아키텍처가 더 중요하지만, 면접에서는 이 원리를 아는 것이 플러스입니다.

댓글 로딩 중...