JavaScript의 미래: JIT를 넘어 AOT 컴파일의 시대로 (feat. Porffor)

Web Engines Hackfest 2025의 Oliver Medhurst 발표 영상을 보고 팀 내 공유용으로 정리한 글입니다. 원본 발표는 글 맨 아래에 붙여 뒀습니다.

우리는 매일 V8과 같은 놀라운 엔진 위에서 코드를 작성하면서도, 그 내부 동작에 대해 깊이 생각해 볼 기회는 많지 않습니다. Node.js가 빠르다는 것은 당연하게 여기지만, 그 ‘빠름’이 어떤 트레이드오프를 가지고 있는지, 그리고 더 나은 방식은 없는지에 대한 고민이죠.

최근 우연히 본 ‘Web Engines Hackfest 2025’의 “Compiling JavaScript ahead-of-time” 이라는 발표 영상은 저에게 신선한 충격을 주었습니다. Oliver Medhurst가 소개한 ‘Porffor’라는 프로젝트를 통해, 우리가 당연하게 여겼던 JIT(Just-in-Time) 컴파일 방식의 한계와 AOT(Ahead-of-Time) 컴파일이 가져올 JavaScript의 미래를 엿볼 수 있었습니다.

JavaScript 엔진 방식, 어떻게 발전해왔나?

영상의 내용을 바탕으로 JavaScript 엔진의 발전 과정을 간단히 짚어보겠습니다.

  1. 인터프리터(Interpretation) 방식
    • 초기 JavaScript 엔진(QuickJS 등)이 사용하던 방식으로, 코드를 한 줄씩 해석하고 바로 실행합니다.
    • 장점: 구현이 간단합니다.
    • 단점: 코드를 실행할 때마다 매번 해석해야 하므로 속도가 매우 느립니다.
  2. JIT(Just-in-Time) 컴파일 방식
    • V8, SpiderMonkey 등 현대적인 엔진들이 사용하는 방식으로, 이름 그대로 ‘실행 시점’에 컴파일합니다.
    • 장점: 자주 사용되는 코드(‘핫 코드’)를 찾아내 기계 코드로 컴파일하여 캐싱해두기 때문에 인터프리터 방식보다 훨씬 빠릅니다.
    • 작동 방식: 처음에는 인터프리터로 코드를 실행하다가, 반복적으로 실행되는 부분을 JIT 컴파일러가 최적화된 코드로 변환하여 성능을 끌어올립니다.

JIT 덕분에 JavaScript는 ‘느린 스크립트 언어’라는 오명을 벗고 서버사이드(Node.js)까지 영역을 확장할 수 있었습니다. 하지만 이 방식이 정말 완벽한 해결책일까요?

JIT, 그 빛과 그림자: 우리가 감수해야 했던 것들

Oliver의 발표는 JIT 방식이 우리가 모르는 사이에 요구했던 비용들을 명확하게 보여주었습니다.

  • 성능의 역설: JIT는 벤치마크에서는 빠르지만, 실제 환경에서는 오히려 성능 저하를 유발할 수 있습니다. Microsoft Edge 팀의 연구에 따르면, JIT를 비활성화하자 일부 페이지 로딩 속도는 느려졌지만, 메모리 사용량은 개선되고 시작 시간은 더 빨라졌습니다. JIT 컴파일 자체가 상당한 리소스를 소모하는 오버헤드라는 의미입니다.
  • 보안의 그림자: JIT의 가장 큰 문제점 중 하나는 보안입니다. 동적으로 실행 가능한 메모리 페이지를 생성하는 JIT의 특성은 공격자에게 좋은 타겟이 됩니다. 영상의 자료에 따르면, 주요 브라우저에서 발견된 보안 버그의 절반 가량이 JIT와 관련이 있을 정도입니다.
  • 무시할 수 없는 오버헤드: JIT 엔진을 포함한 런타임은 그 자체로 거대합니다. 영상의 node --jitless 벤치마크는 JIT를 껐을 때 최대 메모리 사용량이 20.5%나 감소하는 결과를 보여주었습니다. 간단한 기능을 위해 수십, 수백 메가바이트의 런타임을 통째로 배포하는 것은 비효율적일 수밖에 없습니다.

새로운 대안, AOT 컴파일과 ‘Porffor’의 등장

C++이나 Rust처럼, 코드를 실행하기 전에 미리 컴파일하는 AOT(Ahead-of-Time) 방식은 JIT의 이런 단점들을 해결할 수 있는 강력한 대안입니다. 그리고 ‘Porffor’는 바로 이 AOT 방식을 JavaScript에 적용한 실험적인 프로젝트입니다.

Porffor는 JavaScript/TypeScript 코드를 Wasm(WebAssembly)이나 네이티브 바이너리로 직접 컴파일합니다. 영상에서 보여준 라이브 데모는 정말 충격적이었습니다.

console.log("hello hackfest!"); 코드를 컴파일한 결과:

  • Deno: 100 MB
  • Bun: 97 MB
  • Porffor (Native): 20 KB
  • Porffor (Wasm): 4 KB

단순한 “hello world”를 위해 100MB에 가까운 런타임을 통째로 들고 다닐 필요가 없어진다는 의미입니다. 성능 또한 놀라웠습니다. Deno로 실행 시 55ms가 걸렸던 코드가 Porffor 네이티브 바이너리로는 786µs(마이크로초) 만에 실행되었고, 메모리 사용량도 1.4MB 수준으로 급감했습니다.

결론

그동안 당연하게 생각해왔던 JavaScript의 실행 환경에 대해 다시 한번 생각하게 된 계기였습니다.

첫째, JavaScript의 새로운 영토 확장 가능성을 보았습니다. 수십 KB 단위의 바이너리를 만들 수 있다는 것은, 메모리와 저장 공간이 극도로 제한된 임베디드 시스템이나 IoT 기기에서도 JavaScript가 활약할 수 있다는 의미입니다. AOT 컴파일은 JavaScript를 진정한 의미의 ‘유비쿼터스 언어’로 만들어 줄 핵심 열쇠가 될지도 모릅니다.

둘째, ‘보이지 않는 비용’에 대한 자각입니다. 우리는 Node.js의 편리함 뒤에 숨겨진 거대한 런타임의 크기와 JIT의 보안 리스크를 간과해왔습니다. 특히 서버리스 환경이나 수많은 컨테이너를 운영해야 하는 MSA 환경에서 작은 바이너리 크기와 빠른 콜드 스타트 속도는 엄청난 비용 절감으로 이어질 수 있습니다.

마지막으로, JS 생태계는 여전히 역동적으로 진화하고 있다는 사실입니다. V8 엔진이 등장한 지 15년이 넘었지만, 여전히 JavaScript의 근본적인 실행 방식을 혁신하려는 시도가 계속되고 있다는 점이 개발자로서 가슴을 뛰게 합니다.

물론 Porffor는 아직 초기 단계의 프로젝트이고, eval 이나 with 같이 정적 분석이 어려운 동적 기능들을 처리하는 데에는 한계가 있을 것입니다. 하지만 이 프로젝트가 제시하는 방향성은 분명 JavaScript의 미래에 중요한 이정표가 될 것이라 생각합니다.

참고

발표: Web Engines Hackfest 2025 – Oliver Medhurst, Compiling JavaScript ahead-of-time