"V8 은 interpreter 가 아냐. JIT compiler 가 네 코드가 뜨거워질 때까지 interpreter 인 척하는 거야."
V8 이 실제로 하는 일
V8 은 네가 쓴 JavaScript 소스 — 글자들의 줄 — 을 받아서 CPU 가 실행할 수 있는 뭔가로 바꿔. 단순한 방법은 한 줄씩 해석하는 거야, 옛날 Python 의 CPython 처럼. 빠른 방법은 machine code 로 컴파일하는 거지. V8 은 둘 다 해, 단계별로 — 그 단계 구조가 빠른 이유야.
파이프라인 (Node 26 에 들어간 V8 12.x 기준) 은 대략 이래: parser → AST → Ignition (interpreter, bytecode 뱉음) → TurboFan (optimizing JIT, machine code 뱉음) → Sparkplug + Maglev (2021-2023 추가된 중간 tier). 스크립트 처음 돌릴 때 V8 은 최적화에 시간 안 써 — 그냥 파싱하고 Ignition 으로 돌려. 같은 code path 가 뜨거워지면 (비슷한 입력으로 여러 번 호출되면) V8 이 그걸 윗 tier 로 승격시켜. 제일 뜨거운 path 는 진짜 x86_64 / arm64 명령어가 돼.
JavaScript 벤치마크가 이상한 이유가 이거야: 첫 실행은 느리고, 열 번째는 빠르고, 작은 코드 변경이 너를 최적화 tier 에서 interpreter 로 떨어뜨릴 수 있어. "파라미터 하나 더했더니 코드가 느려졌어" 는 V8 세계에서 실제로 일어나는 대화야.
Hidden Class — JS 를 빠르게 만든 트릭
JavaScript 객체는 사전처럼 생겼지: {x: 1, y: 2}. 단순한 엔진은 그걸 hash map 으로 다루고 property 접근마다 hash lookup 비용을 내. V8 은 안 그래. V8 은 네 객체의 *모양* 을 보고 모양마다 *hidden class* 를 뒤에서 만들어. 같은 key 를 같은 순서로 가진 두 객체는 hidden class 를 공유해. Property 접근이 단일 메모리 offset 으로 바뀌어 — C struct 만큼 빨라.
함정: property 를 동적으로 추가 / 삭제하거나 다른 순서로 할당하면 V8 이 hidden class 를 invalidate 해. 그게 최적화를 죽여. 그래서 "객체 property 는 항상 생성자에서 같은 순서로 다 초기화해라" 라는 조언이 있는 거야 — V8 이 모든 instance 에 대해 같은 hidden class 를 유지하게 해주는 거지.
V8 ≠ Node
readFile 이나 http.get 부를 때마다 V8 은 Node 의 C++ 코드한테 control 을 넘기고, 그게 libuv 를 부르고, 그게 OS 를 불러. "V8 영역" 과 "Node 영역" 의 경계는 진짜야 — 성능 디버깅 할 때 이게 중요해.네가 다른 데서 보게 될 엔진들
V8 만이 유일한 JavaScript 엔진 아냐. Firefox 는 SpiderMonkey 써. Safari 는 JavaScriptCore (Nitro 라고도 함) 써. Bun 은 JavaScriptCore — 그래서 Bun 이 Node 보다 startup 빠른 이유 일부야 (엔진별 startup 특성 다름). Deno 는 V8 써 — Node 와 같은 엔진, 다른 런타임이 감싸고 있는 거지.
Node 쓰면서 SpiderMonkey / JavaScriptCore 내부까지 알 필요는 없어. 근데 존재한다는 걸 알면 "이 코드 Bun 에서 더 빨라" 또는 "Chrome 에선 되는데 Safari 에선 안 돼" 같은 일이 왜 일어나는지 설명돼 — 같은 언어, 다른 엔진 구현.