"V8은 JavaScript를 실행하는 엔진이야. Node는 그 엔진에 파일, 네트워크, 프로세스 같은 기능을 붙인 런타임이고."
소스 코드가 기계어에 가까워지는 과정
V8은 JavaScript 소스를 읽어 문법 구조를 만들고, 먼저 Ignition 바이트코드로 실행해. 처음 보는 코드를 곧바로 무겁게 최적화하면 준비 비용이 더 클 수 있으므로 빠르게 실행을 시작하는 쪽을 택하는 거야.
실행 중 같은 코드 경로가 반복되면 V8은 수집한 정보를 바탕으로 더 빠른 단계로 올려. Sparkplug와 Maglev가 중간 단계를 맡고, 충분히 뜨거우면서 입력 형태가 안정된 코드는 TurboFan이 최적화한 기계어로 바꿔. 모든 함수가 반드시 각 단계를 순서대로 거치는 건 아니지만, 먼저 실행하고 관찰한 뒤 필요한 부분을 최적화한다는 흐름은 같아.
그래서 짧은 벤치마크의 첫 실행과 충분히 예열한 뒤의 실행은 결과가 다를 수 있어. 최적화가 기대한 입력 형태가 깨지면 V8은 안전한 코드로 되돌아가기도 해. 이 과정을 역최적화라고 불러.
객체 모양을 기억하는 숨은 클래스
JavaScript 객체는 실행 중에도 속성을 더하거나 지울 수 있어. 매번 속성 이름을 처음부터 찾아야 한다면 접근 비용이 커지지. V8은 속성의 이름과 추가 순서가 같은 객체들을 같은 모양으로 보고, 그 모양을 나타내는 숨은 클래스와 속성 위치 정보를 재사용해.
비슷한 객체를 늘 같은 순서로 초기화하면 V8이 이미 배운 모양을 계속 활용하기 쉬워. 반대로 뜨거운 반복문 안에서 속성을 자주 추가하거나 삭제하고, 같은 함수에 서로 전혀 다른 모양의 객체를 계속 넘기면 기존 최적화가 풀릴 수 있어. 그렇다고 모든 객체를 이 규칙에 맞춰 억지로 바꿀 필요는 없어. 실제 병목을 측정한 뒤 이 현상이 보일 때 살펴보면 돼.
V8과 Node가 맡는 일은 달라
readFile()이나 http.get()을 호출하면 Node의 네이티브 코드와 libuv, 운영체제 쪽으로 작업이 넘어가. 성능 문제를 추적할 때도 계산이 V8 안에서 느린지, I/O가 Node와 운영체제 쪽에서 기다리는지 먼저 나눠 봐야 해.같은 언어를 실행하는 다른 엔진들
V8만 JavaScript를 실행하는 건 아니야. Firefox는 SpiderMonkey를, Safari와 Bun은 JavaScriptCore를 사용해. Deno는 Node처럼 V8을 사용하지만 V8 바깥에 제공하는 API와 권한 모델은 달라.
같은 JavaScript라도 엔진의 최적화 방식과 런타임이 제공하는 API가 다르면 속도와 호환성이 달라질 수 있어. Bun의 시작 시간이 빠른 것도 JavaScriptCore 하나로 설명할 수는 없어. 엔진 특성과 런타임 설계, 도구 통합을 함께 봐야 해.