Skip to content

Split View: 컴파일러와 현대 언어 런타임 — LLVM, JIT, GC, V8 TurboFan/Maglev, Inline Caching, Escape Analysis, Rust Monomorphization 완벽 가이드 (2025)

|

컴파일러와 현대 언어 런타임 — LLVM, JIT, GC, V8 TurboFan/Maglev, Inline Caching, Escape Analysis, Rust Monomorphization 완벽 가이드 (2025)

왜 컴파일러/런타임을 알아야 하는가

2025년 현실:

  • V8, JVM, Go runtime, CPython, LLVM — 매일 쓰는 도구들.
  • 이들의 내부 최적화가 당신 앱 성능의 70%를 결정.
  • "이 코드 왜 느리지?"의 답이 컴파일러 결정에 숨어 있는 경우 다수.
  • 언어 선택(Go vs Rust vs Python vs TS)은 런타임 특성 선택이다.
  • AI 시대에 수백만 QPS LLM 인퍼런스 런타임은 컴파일러 최적화 싸움.

이 글은 "내 코드가 실행되기까지 무엇이 일어나는가"를 추적한다.

Part 1 — 컴파일러 vs 인터프리터 vs JIT — 스펙트럼

전통 구분

  • 컴파일러: 전체 코드를 기계어로 미리 번역 (AOT, Ahead-of-Time).
  • 인터프리터: 소스를 한 줄씩 즉시 실행.
  • JIT: 실행 중 자주 쓰이는 부분을 기계어로 컴파일.

현실은 하이브리드

  • JVM: 인터프리터 + C1 + C2 JIT + AOT(GraalVM).
  • V8: Ignition(인터프리터) + Sparkplug + Maglev + TurboFan(JIT).
  • CPython: 순수 바이트코드 인터프리터 → 3.13 Specializing Adaptive Interpreter.
  • .NET: 바이트코드 + RyuJIT + AOT(NativeAOT).

"언어는 컴파일러인가 인터프리터인가"는 의미 없는 질문. **"어떤 레이어로 구성되어 있는가"**가 실질적 질문.

Part 2 — LLVM의 지배

왜 LLVM이 표준이 되었나

2000년 Chris Lattner의 박사 프로젝트. 2024년 현재:

  • 언어 프론트엔드가 LLVM IR을 생성하면 이후 최적화·코드생성은 LLVM에 맡김.
  • "LLVM을 쓴 언어": Rust, Swift, Julia, Zig, Crystal, Kotlin/Native, Mojo, Chapel.
  • Apple Silicon 마이그레이션의 핵심 도구.
  • GPU 코드(CUDA/HIP)도 LLVM 경유.

LLVM IR

define i32 @add(i32 %a, i32 %b) {
  %sum = add i32 %a, %b
  ret i32 %sum
}

언어 중립 중간 표현. 최적화 패스 수백 개가 이 위에서 돈다.

  • Constant Folding
  • Dead Code Elimination
  • Loop Invariant Code Motion
  • Inlining
  • Vectorization
  • Instruction Combining

MLIR (2019, Chris Lattner 재등장)

"다단계 IR." LLVM IR 한 단계가 아니라, 도메인별 중간 표현을 여러 레벨로.

왜 필요한가: 머신러닝 프레임워크(TensorFlow/PyTorch)가 고수준 그래프 → 저수준 연산까지 여러 추상 레벨을 관리해야 한다. LLVM IR만으로는 표현력이 부족.

2024년 MLIR 채택:

  • Mojo (Modular AI) — Python 슈퍼셋, MLIR 기반.
  • IREE — ML 컴파일러.
  • Triton (OpenAI) — GPU 커널 언어.

Part 3 — JIT 컴파일러의 대가들

V8의 4계층

2024년 기준:

바이트코드 (Ignition 인터프리터)
   ()
Sparkplug — 바이트코드를 1:1 기계어로(비최적화, 빠른 생성)
   (더 핫)
Maglev — 중간 최적화 JIT (2023 도입)
   (매우 핫)
TurboFan — 최대 최적화 (Sea of Nodes, 오래 걸림)

각 단계는 수집한 타입 피드백을 활용해 다음 단계에서 더 공격적 최적화.

역최적화(Deoptimization): 가정이 틀리면 하위 단계로 돌아감. 동적 언어 최적화의 핵심 매커니즘.

Hidden Class + Inline Caching

JavaScript 객체는 동적. obj.x의 주소가 고정이 아님. V8의 해결:

Hidden Class (aka Shape, Map):

  • 같은 속성 구조의 객체들이 같은 hidden class 공유.
  • obj.x는 "이 hidden class의 offset N"으로 컴파일.

Inline Cache (IC):

  • 속성 접근 결과를 호출 지점에 캐시.
  • 같은 hidden class 객체가 반복 호출되면 캐시 히트 → 네이티브 속도.
  • 형태 바뀌면 IC miss → polymorphic → megamorphic으로 전이.

실무 교훈: JS 객체 형태를 안정적으로 유지하라.

// 나쁨: 조건부 속성 추가
const p = {};
if (cond) p.x = 1;
if (cond2) p.y = 2;

// 좋음: 모든 속성을 생성 시점에 선언
const p = { x: cond ? 1 : undefined, y: cond2 ? 2 : undefined };

Escape Analysis

"이 객체가 함수 밖으로 탈출하지 않는다면 → 스택 할당 or 스칼라 대체(scalar replacement)."

힙 할당은 비싸다. GC 압력. Escape analysis가 잘 되는 코드는 훨씬 빠르다.

  • JVM C2: 강력한 escape analysis.
  • Go 컴파일러: go build -gcflags="-m"으로 결과 확인 가능.
  • V8 TurboFan: 제한적.

Tiered Compilation

JVM: C1(빠른 JIT) → C2(공격적 JIT) → (OpenJDK 17+) GraalVM.

  • -XX:+PrintCompilation 으로 관찰.
  • C2는 여전히 업계에서 가장 강력한 JIT 중 하나.

LuaJIT: 트레이스 기반 JIT. Mike Pall의 걸작. 2020년대에도 여전히 최고의 동적 언어 JIT로 인용됨.

Part 4 — Garbage Collector 계보

Mark & Sweep (1960)

  • 루트부터 도달 가능 객체 마킹.
  • 미마킹된 것 쓸어버림.
  • 단점: Stop-the-world, 단편화.

Copying GC

  • 두 영역 분할, 살아있는 객체를 한쪽으로 복사.
  • 단편화 없음.
  • 메모리 2배 필요.

Generational GC

  • 대부분의 객체가 어리게 죽는다는 관찰(Weak Generational Hypothesis).
  • Young/Old 영역 분리, 젊은 영역을 자주 수집.

Concurrent & Incremental GC

GC를 애플리케이션과 동시에 또는 조각내서 실행 → STW 최소화.

G1 GC (JDK 9+ 기본)

  • Region 기반(2048개 힙 영역).
  • 예측 가능한 pause time target.
  • 대부분의 서버 워크로드에 기본.

ZGC (JDK 11+, 2023 Production-Ready)

  • Colored Pointers — 객체 이동을 참조 자체의 색 비트로 관리.
  • Sub-millisecond pause 보장(수십 TB 힙에서도).
  • 2023년 Generational ZGC로 처리량까지 개선.

Shenandoah (RedHat, JDK 12+)

  • 동시 압축.
  • ZGC와 경쟁.

Go의 GC

  • 동시 mark & sweep.
  • 3색 추상 알고리즘 + write barrier.
  • Stop-the-world 1ms 이하 목표(Go 1.5+에서 달성).
  • Generational 아님 — 대신 "escape analysis로 스택에 많이 올림".

CPython

  • Reference Counting 주 + 순환 참조용 Cycle Collector.
  • 장점: 결정적 해제.
  • 단점: 모든 할당/해제에 카운터 조작, GIL 필요.

V8

  • Orinoco — 동시·증분·병렬.
  • 젊은 세대(new space): Copying.
  • 늙은 세대(old space): Mark-Compact.

GC 선택 철학

GCpause처리량메모리 오버헤드
Parallel (JDK, 기본 X)높다낮다
G1
ZGC극저
Shenandoah극저
Go저-중
CPython RC거의 0낮다(카운터)낮다

Part 5 — Go 스케줄러

G-M-P 모델

  • G: goroutine
  • M: OS thread
  • P: processor (논리 CPU, 보통 GOMAXPROCS)

각 P는 로컬 G 큐 보유. 비어있으면 다른 P에서 work-stealing.

특징

  • 협력적 선점 + Go 1.14+ 비동기 선점 (시간 초과 시 시그널로 중단).
  • Syscall 진입 시 M이 블록되어도 P는 다른 M에 즉시 할당 → 다른 goroutine 계속.
  • Netpoller로 I/O는 epoll/kqueue 통합.

단점

  • NUMA 인지 스케줄링 제한적.
  • GOMAXPROCS 자동 감지가 cgroups 인지 제한 — 컨테이너에서 수동 설정 권장.
  • automaxprocs 라이브러리가 Uber에서 해결.

Part 6 — Rust와 AOT의 힘

Monomorphization

fn max<T: PartialOrd>(a: T, b: T) -> T { if a > b { a } else { b } }

let x = max(1i32, 2i32);    // max::<i32> 생성
let y = max(1.0f64, 2.0);   // max::<f64> 별도 생성

각 타입마다 전용 기계어 생성 → 호출 오버헤드 0, 최적 인라이닝. 단점: 바이너리 크기 증가(code bloat).

Zero-Cost Abstractions

"추상화를 사용한 코드가 직접 작성한 코드보다 느리지 않다."

  • Iterator 체인이 for-loop과 동일 어셈블리.
  • async/await가 state machine으로 완전히 풀림.
  • Trait 객체(dyn Trait)는 비용 있지만, 정적 디스패치는 0.

Rust의 Borrow Checker

런타임 비용 0으로 메모리 안전성 달성. 컴파일 타임에 소유권·수명을 검증.

2024-2025 Rust 발전

  • async trait 네이티브 (1.75).
  • Parallel frontend (Nightly) — 컴파일 속도 개선.
  • Cranelift 백엔드 — 디버그 빌드에서 LLVM 대체 가능.
  • cargo-nextest — 테스트 속도 개선.

Part 7 — Python 3.13의 혁명

Specializing Adaptive Interpreter (PEP 659, 3.11+)

CPython이 바이트코드에 형태별 특화된 명령을 런타임에 끼워넣기.

LOAD_ATTRLOAD_ATTR_INSTANCE_VALUE (dict 기반 객체)
LOAD_ATTR_SLOT (__slots__)
LOAD_ATTR_MODULE
...

V8의 Inline Cache를 CPython에 가져온 것.

3.13의 추가 변화

  • Experimental JIT (copy-and-patch 기법). 실험적 플래그.
  • Free-Threading (PEP 703) — GIL 없는 실행. 별도 빌드 옵션.
  • incremental GC.

PyPy

  • 트레이스 JIT. 수년간 CPython보다 4-10배 빠름.
  • 호환성 이슈 때문에 대중 채택은 제한적.
  • HPy (2020+) — C 확장 API의 이식성 개선 노력.

Part 8 — JavaScript 런타임의 지형

V8 (Chrome, Node.js, Deno)

  • 2008년 등장. Lars Bak.
  • 표준 JS 최적화의 기준.

JavaScriptCore (Safari/WebKit)

  • 4계층 JIT(LLInt → Baseline → DFG → FTL).
  • B3 JIT 컴파일러 자체 개발.
  • 메모리 효율이 V8보다 낫다는 평.

SpiderMonkey (Firefox)

  • IonMonkey JIT.
  • WebAssembly 구현 품질 높음.

Bun의 JSC 선택

Bun은 V8 대신 JSC. Node보다 빠른 벤치 수치의 일부는 이 선택 덕분.

런타임 API 차이

  • Node.js: fs, net, http, CommonJS+ESM.
  • Deno: Web API 우선 + permissions + TypeScript 네이티브.
  • Bun: Node API + Web API + 빠른 번들러/테스트러너 내장.
  • Workerd (Cloudflare): V8 isolate, 제한된 Node 호환.

Part 9 — WebAssembly 런타임

이전 글(WASM)에서 이미 다뤘지만 런타임 관점에서 한 번 더:

런타임특징어디서 쓰나
V8 + Wasm브라우저 표준
WasmtimeBytecode Alliance서버 WASI
Wasmer다양한 백엔드임베디드
WasmEdgeCNCF엣지 컴퓨팅
Wasmer + Cranelift빠른 컴파일개발
Wasmer + LLVM최적 코드프로덕션

Part 10 — AOT vs JIT의 트레이드오프

AOT (Rust, Go, Swift, GraalVM Native Image)

장점:

  • 빠른 시작.
  • 예측 가능한 성능.
  • 런타임 오버헤드 적음.

단점:

  • 동적 최적화 불가(타입 피드백 못 씀).
  • 바이너리 크기.
  • 컴파일 시간.

JIT (V8, JVM C2)

장점:

  • 동적 타입/다형성 최적화.
  • 런타임 정보 활용.

단점:

  • 워밍업 시간(초기 느림).
  • 메모리 오버헤드.
  • 예측 불가능한 pause.

GraalVM — 둘 사이 다리

  • Native Image — Java 코드를 AOT 컴파일.
  • 시작 50ms, 메모리 90% 절감.
  • Spring Native, Quarkus, Micronaut — 서버리스·컨테이너에 적합.
  • 단점: Reflection·Dynamic Class Loading 제약.

Part 11 — 성능 분석 워크플로

CPU 프로파일링

  1. Flame Graph로 전체 그림.
  2. 핫스팟 함수 식별.
  3. 해당 함수의 어셈블리 확인(perf annotate, 또는 Compiler Explorer).
  4. JIT 출력 보려면: Node --print-opt-code, V8 logging.

메모리 프로파일링

  • Chrome DevTools Heap Snapshot (V8).
  • JFR + Mission Control (JVM).
  • pprof (Go).
  • memray (Python).

트레이스

  • Linux perf + flame graph.
  • Parca, Pyroscope continuous profiling.
  • async-profiler (JVM) — safepoint 이슈 없는 JVM 프로파일러.

Part 12 — 체크리스트 (12항목)

  1. 런타임 버전 최신 LTS — V8, JVM, Go, Python 모두 꾸준히 개선.
  2. GC 튜닝은 벤치 후 — 프리매처 최적화는 나쁨.
  3. JS 객체 형태 안정 — Hidden Class 안정화.
  4. Go는 escape analysis 의식-gcflags="-m"으로 확인.
  5. Rust는 #[inline]/PGO — 수동 힌트가 종종 필요.
  6. JVM은 JFR 기본 — 프로덕션 상시 프로파일.
  7. CPython은 3.13+ 검토 — 특화 인터프리터 성능 개선.
  8. Container 내 CPU 인식 — GOMAXPROCS, -XX:ActiveProcessorCount.
  9. Startup 민감 앱은 AOT 고려 — Native Image, Go AOT.
  10. JIT 워밍업 측정 — 벤치마크 초기값은 버려라.
  11. Allocation hot path 최소화 — 대부분 성능 문제의 원인.
  12. Compiler Explorer (godbolt.org) — 어셈블리 직접 확인 습관.

Part 13 — 10대 안티패턴

  1. "언어가 빨라서 빠르다" — 런타임 설정·구조가 더 크다.
  2. 벤치를 time ./app로 1번 측정 — 분산·워밍업 무시.
  3. JIT 워밍업 무시-Xcomp만 보고 판단.
  4. 마이크로벤치에 JMH 없이 System.nanoTime() — 오차 수십배.
  5. Generic 남용(monomorphization 폭발) — 바이너리 수십 MB.
  6. Python에서 tight loop을 pure Python으로 — NumPy/Cython/Numba 고려.
  7. Node.js에서 CPU 바운드를 main 이벤트루프 — worker_threads 필수.
  8. JVM에 Xms=Xmx 동일 없이 프로덕션 운영 — 힙 재할당 비용.
  9. GC 로그 미수집 — 프로덕션 장애 분석 불가.
  10. 컨테이너 기본 메모리로 JVM — OOM Kill 당함. -XX:+UseContainerSupport 기본이지만 확인.

Part 14 — 학습 자료

  • 책: Crafting Interpreters (Robert Nystrom) — 무료 공개. 가장 친절한 인터프리터 책.
  • 책: Engineering a Compiler (Cooper & Torczon).
  • 책: Modern Compiler Implementation in ML/Java/C (Andrew Appel).
  • 책: The Garbage Collection Handbook (Jones, Hosking, Moss).
  • 블로그: V8 blog, Chrome V8 Engineer talks on YouTube.
  • 도구: godbolt.org — 어셈블리 실험 천국.
  • 강의: Stanford CS143 Compilers (공개).

마치며 — 언어는 런타임이다

"어떤 언어를 쓸까"라는 질문은 종종 **"어떤 런타임 특성을 원하는가"**의 문제다.

  • 지연이 극도로 낮아야 한다 → Rust, Go AOT, GraalVM Native.
  • 개발 생산성이 최우선 → Python, TypeScript, Kotlin.
  • 다형성 최적화 필요 → JVM C2, V8 TurboFan.
  • 시작 시간 중요 → AOT.

모든 런타임은 절충이다. 같은 언어도 설정·버전·GC 선택으로 성능 프로필이 달라진다. 엔지니어의 무기는 "나는 V8의 Hidden Class를 깨뜨리지 않는다", "나는 Go의 escape analysis를 확인한다" 같은 런타임에 대한 구체적 이해다.

LLM이 코드를 잘 쓰는 시대에, **"왜 이 코드가 빠르거나 느린가"**를 설명할 수 있는 엔지니어의 가치는 더 커진다. 그 답은 대부분 컴파일러와 런타임에 있다.

다음 글 예고 — "AI 엔지니어링 실전" — LLM API 아키텍처, RAG, 에이전트, 파인튜닝, 벡터 DB, 평가, 프로덕션 운영

14편의 컴퓨터 시스템 여정 끝에, 다음은 그 모든 시스템 위에 서 있는 AI 애플리케이션이다.

  • LLM API 호출의 실전 — 재시도, 타임아웃, 스트리밍, 비용
  • RAG 아키텍처 — 단순 조회부터 Hybrid Search까지
  • 에이전트 디자인 패턴 — ReAct, Plan-and-Execute, Tool Use
  • 파인튜닝 언제, 언제 하지 말까 — LoRA, DPO, RLHF 비교
  • 벡터 DB 실전 — pgvector vs Qdrant vs Pinecone, 언제 무엇
  • LLM 평가(Eval) — 정확도 측정의 진짜 어려움
  • 프롬프트 엔지니어링의 과학 — Structured Output, Few-shot, Chain-of-Thought
  • LLM Observability — OpenTelemetry GenAI, LangSmith, LangFuse
  • 비용 최적화 — 모델 선택, 캐싱, Prompt Compression
  • 보안 — Prompt Injection, Data Leakage

"AI 프로덕트를 실제로 만드는 법." 다음 글에서.

Compilers and Modern Language Runtimes — LLVM, JIT, GC, V8 TurboFan/Maglev, Inline Caching, Escape Analysis, Rust Monomorphization Complete Guide (2025)

Why You Should Know Compilers and Runtimes

Reality in 2025:

  • V8, JVM, Go runtime, CPython, LLVM — tools you use daily.
  • Their internal optimizations decide 70% of your app's performance.
  • "Why is this code slow?" answers often hide in compiler decisions.
  • Picking a language (Go vs Rust vs Python vs TS) is picking a runtime profile.
  • In the AI era, million-QPS LLM inference runtimes are a compiler-optimization battle.

This post traces "what happens until my code runs."

Part 1 — Compiler vs Interpreter vs JIT — A Spectrum

Classical split

  • Compiler: translate to machine code ahead-of-time (AOT).
  • Interpreter: execute source one line at a time.
  • JIT: compile hot parts to machine code during execution.

Reality is hybrid

  • JVM: interpreter + C1 + C2 JIT + AOT (GraalVM).
  • V8: Ignition (interpreter) + Sparkplug + Maglev + TurboFan (JIT).
  • CPython: pure bytecode interpreter → 3.13 Specializing Adaptive Interpreter.
  • .NET: bytecode + RyuJIT + AOT (NativeAOT).

"Is this language compiled or interpreted?" is the wrong question. "What tiers does its runtime have?" is the real one.

Part 2 — LLVM's Dominance

Why LLVM became the standard

Chris Lattner's 2000 PhD project. In 2024:

  • Language frontends emit LLVM IR; optimization and codegen are LLVM's job.
  • Languages using LLVM: Rust, Swift, Julia, Zig, Crystal, Kotlin/Native, Mojo, Chapel.
  • Core tool for Apple Silicon migration.
  • GPU code (CUDA/HIP) goes through LLVM.

LLVM IR

define i32 @add(i32 %a, i32 %b) {
  %sum = add i32 %a, %b
  ret i32 %sum
}

Language-neutral IR. Hundreds of optimization passes run on it:

  • Constant Folding
  • Dead Code Elimination
  • Loop Invariant Code Motion
  • Inlining
  • Vectorization
  • Instruction Combining

MLIR (2019, Chris Lattner returns)

"Multi-level IR." Not one LLVM IR, but domain-specific IRs at multiple levels.

Why needed: ML frameworks (TensorFlow/PyTorch) manage high-level graphs down to low-level ops across multiple abstractions. LLVM IR alone lacks the expressiveness.

MLIR adoption in 2024:

  • Mojo (Modular AI) — Python superset, MLIR-based.
  • IREE — ML compiler.
  • Triton (OpenAI) — GPU kernel language.

Part 3 — JIT Masters

V8's four tiers

As of 2024:

Bytecode (Ignition interpreter)
   (hot)
Sparkplug1:1 bytecode-to-machine (non-optimizing, fast codegen)
   (hotter)
Maglev — mid-tier optimizing JIT (2023)
   (very hot)
TurboFan — peak optimization (Sea of Nodes, slow)

Each tier uses type feedback collected below to optimize more aggressively above.

Deoptimization: if assumptions break, fall back. Core mechanism of dynamic-language optimization.

Hidden Class + Inline Caching

JS objects are dynamic; obj.x has no fixed address. V8's answer:

Hidden Class (aka Shape, Map):

  • Objects with identical property structure share a hidden class.
  • obj.x compiles to "offset N in this hidden class."

Inline Cache (IC):

  • Cache the property-access result at the call site.
  • Repeated access on the same hidden class → cache hit → native speed.
  • Shape changes → IC miss → polymorphic → megamorphic.

Lesson: keep JS object shapes stable.

// Bad: conditional property addition
const p = {};
if (cond) p.x = 1;
if (cond2) p.y = 2;

// Good: declare all properties at construction
const p = { x: cond ? 1 : undefined, y: cond2 ? 2 : undefined };

Escape Analysis

"If this object doesn't escape the function → stack-allocate or scalar-replace."

Heap allocation is expensive; GC pressure. Code friendly to escape analysis is much faster.

  • JVM C2: strong escape analysis.
  • Go compiler: check with go build -gcflags="-m".
  • V8 TurboFan: limited.

Tiered Compilation

JVM: C1 (fast JIT) → C2 (aggressive JIT) → (OpenJDK 17+) GraalVM.

  • Observe with -XX:+PrintCompilation.
  • C2 is still among the strongest JITs in production.

LuaJIT: trace-based JIT. Mike Pall's masterpiece. Still cited as the best dynamic-language JIT in the 2020s.

Part 4 — GC Lineage

Mark & Sweep (1960)

  • Mark reachable objects from roots.
  • Sweep the unmarked.
  • Cons: stop-the-world, fragmentation.

Copying GC

  • Split into two spaces; copy live objects to the other.
  • No fragmentation; 2x memory.

Generational GC

  • Most objects die young (Weak Generational Hypothesis).
  • Young/Old split; collect young often.

Concurrent and Incremental GC

Run GC concurrently or in small chunks to minimize STW.

G1 GC (default in JDK 9+)

  • Region-based (~2048 heap regions).
  • Predictable pause-time targets.
  • Default for most server workloads.

ZGC (JDK 11+, Production-Ready in 2023)

  • Colored Pointers — track object moves via color bits in references.
  • Sub-millisecond pauses even on tens of TB heaps.
  • 2023 Generational ZGC improves throughput too.

Shenandoah (RedHat, JDK 12+)

  • Concurrent compaction.
  • Competitor to ZGC.

Go's GC

  • Concurrent mark & sweep.
  • Tri-color abstraction + write barrier.
  • STW target below 1ms (achieved since Go 1.5).
  • Not generational — uses escape analysis to put more on the stack.

CPython

  • Primarily reference counting + a cycle collector for cycles.
  • Pros: deterministic release.
  • Cons: counter ops on every alloc/dealloc; needs GIL.

V8

  • Orinoco — concurrent, incremental, parallel.
  • Young generation (new space): copying.
  • Old generation (old space): mark-compact.

Choosing a GC

GCpausethroughputmemory overhead
Parallel (JDK, not default)longhighlow
G1midmidmid
ZGCultra-lowmidmid
Shenandoahultra-lowmidmid
Golowmidlow-mid
CPython RCnear 0low (counters)low

Part 5 — Go Scheduler

G-M-P model

  • G: goroutine
  • M: OS thread
  • P: processor (logical CPU, usually GOMAXPROCS)

Each P has a local G queue. If empty, work-steals from another P.

Traits

  • Cooperative preemption + Go 1.14+ asynchronous preemption (signal-based timeout).
  • On syscall: even if M blocks, P is reassigned to another M immediately → other goroutines keep running.
  • Netpoller integrates I/O with epoll/kqueue.

Limitations

  • Limited NUMA-aware scheduling.
  • GOMAXPROCS auto-detect is cgroups-limited — set manually in containers.
  • Uber's automaxprocs library fixes this.

Part 6 — Rust and the Power of AOT

Monomorphization

fn max<T: PartialOrd>(a: T, b: T) -> T { if a > b { a } else { b } }

let x = max(1i32, 2i32);    // generates max::<i32>
let y = max(1.0f64, 2.0);   // generates max::<f64>

Dedicated machine code per type → zero call overhead, optimal inlining. Downside: binary bloat.

Zero-Cost Abstractions

"Code using abstractions is no slower than hand-written equivalent."

  • Iterator chains compile to the same assembly as a for-loop.
  • async/await fully unrolls into a state machine.
  • Trait objects (dyn Trait) cost, but static dispatch is zero.

Rust's Borrow Checker

Zero-runtime-cost memory safety. Compile-time ownership/lifetime verification.

Rust 2024-2025

  • Native async trait (1.75).
  • Parallel frontend (Nightly) — faster compiles.
  • Cranelift backend — replaces LLVM in debug builds.
  • cargo-nextest — faster tests.

Part 7 — Python 3.13's Revolution

Specializing Adaptive Interpreter (PEP 659, 3.11+)

CPython injects shape-specialized bytecode instructions at runtime.

LOAD_ATTRLOAD_ATTR_INSTANCE_VALUE (dict-based object)
LOAD_ATTR_SLOT (__slots__)
LOAD_ATTR_MODULE
...

V8's Inline Cache, brought to CPython.

3.13 additions

  • Experimental JIT (copy-and-patch). Experimental flag.
  • Free-Threading (PEP 703) — GIL-less execution. Separate build option.
  • Incremental GC.

PyPy

  • Tracing JIT. 4-10x faster than CPython for years.
  • Compatibility issues limit mainstream adoption.
  • HPy (2020+) — portable C-extension API effort.

Part 8 — JavaScript Runtime Landscape

V8 (Chrome, Node.js, Deno)

  • Released 2008. Lars Bak.
  • The reference point for JS optimization.

JavaScriptCore (Safari/WebKit)

  • 4-tier JIT (LLInt → Baseline → DFG → FTL).
  • Custom B3 JIT compiler.
  • Reputed to be more memory-efficient than V8.

SpiderMonkey (Firefox)

  • IonMonkey JIT.
  • Strong WebAssembly implementation.

Bun picks JSC

Bun uses JSC instead of V8. Part of its beating-Node benchmarks stems from this choice.

Runtime API differences

  • Node.js: fs, net, http, CommonJS+ESM.
  • Deno: Web API first + permissions + native TypeScript.
  • Bun: Node API + Web API + built-in bundler/test runner.
  • Workerd (Cloudflare): V8 isolates, limited Node compat.

Part 9 — WebAssembly Runtimes

Covered previously, but from a runtime angle:

RuntimeTraitWhere
V8 + WasmBrowser standardWeb
WasmtimeBytecode AllianceServer WASI
WasmerMultiple backendsEmbedded
WasmEdgeCNCFEdge
Wasmer + CraneliftFast compileDev
Wasmer + LLVMBest codeProd

Part 10 — AOT vs JIT Trade-offs

AOT (Rust, Go, Swift, GraalVM Native Image)

Pros:

  • Fast startup.
  • Predictable performance.
  • Low runtime overhead.

Cons:

  • No dynamic optimization (no type feedback).
  • Binary size.
  • Compile time.

JIT (V8, JVM C2)

Pros:

  • Dynamic typing/polymorphism optimization.
  • Uses runtime information.

Cons:

  • Warmup (slow at first).
  • Memory overhead.
  • Unpredictable pauses.

GraalVM — the bridge

  • Native Image — AOT-compile Java.
  • 50ms startup, 90% memory reduction.
  • Spring Native, Quarkus, Micronaut — serverless/container-friendly.
  • Cons: reflection and dynamic class loading constrained.

Part 11 — Performance Analysis Workflow

CPU profiling

  1. Flame graph for the big picture.
  2. Identify hotspot functions.
  3. Inspect their assembly (perf annotate or Compiler Explorer).
  4. For JIT output: Node --print-opt-code, V8 logging.

Memory profiling

  • Chrome DevTools Heap Snapshot (V8).
  • JFR + Mission Control (JVM).
  • pprof (Go).
  • memray (Python).

Tracing

  • Linux perf + flame graph.
  • Parca, Pyroscope — continuous profiling.
  • async-profiler (JVM) — safepoint-free JVM profiler.

Part 12 — Checklist (12 items)

  1. Latest LTS runtime — V8, JVM, Go, Python all keep improving.
  2. GC tune only after benchmarking — premature optimization is bad.
  3. Keep JS object shape stable — stabilize Hidden Class.
  4. Mind Go escape analysis — check with -gcflags="-m".
  5. Rust: #[inline]/PGO — manual hints often needed.
  6. JVM: JFR always on — continuous prod profiling.
  7. Consider CPython 3.13+ — specialized interpreter gains.
  8. Container CPU awareness — GOMAXPROCS, -XX:ActiveProcessorCount.
  9. Startup-sensitive apps → AOT — Native Image, Go AOT.
  10. Measure JIT warmup — discard initial benchmark numbers.
  11. Minimize allocation hot paths — most perf issues are here.
  12. Compiler Explorer (godbolt.org) — habitual assembly inspection.

Part 13 — 10 Anti-patterns

  1. "This language is fast" — runtime config/structure matters more.
  2. Benchmark with time ./app once — ignoring variance/warmup.
  3. Ignoring JIT warmup — judging from -Xcomp only.
  4. Microbenchmarking with System.nanoTime() instead of JMH — tens of times off.
  5. Generic abuse (monomorphization explosion) — tens of MB binaries.
  6. Tight loops in pure Python — consider NumPy/Cython/Numba.
  7. CPU-bound work on Node main event loop — worker_threads required.
  8. JVM prod without Xms=Xmx — heap-resize cost.
  9. No GC logs — impossible to triage prod incidents.
  10. JVM on container default memory — gets OOM-killed. -XX:+UseContainerSupport is default but verify.

Part 14 — Learning Resources

  • Book: Crafting Interpreters (Robert Nystrom) — free online. Kindest intro.
  • Book: Engineering a Compiler (Cooper & Torczon).
  • Book: Modern Compiler Implementation in ML/Java/C (Andrew Appel).
  • Book: The Garbage Collection Handbook (Jones, Hosking, Moss).
  • Blog: V8 blog; Chrome V8 engineer talks on YouTube.
  • Tool: godbolt.org — assembly playground.
  • Course: Stanford CS143 Compilers (open).

Closing — Language Is Runtime

"Which language?" is often really "which runtime profile?"

  • Ultra-low latency → Rust, Go AOT, GraalVM Native.
  • Dev productivity first → Python, TypeScript, Kotlin.
  • Polymorphism optimization → JVM C2, V8 TurboFan.
  • Startup matters → AOT.

Every runtime is a trade-off. The same language shifts performance profile by config, version, GC choice. An engineer's weapon is concrete runtime knowledge: "I don't break V8's Hidden Class," "I verify Go's escape analysis."

In the LLM-writes-code era, engineers who can explain why code is fast or slow grow more valuable. The answer mostly lives in the compiler and the runtime.

Next — "AI Engineering in Practice" — LLM API architecture, RAG, agents, fine-tuning, vector DBs, evaluation, production ops

After 14 systems posts, the next sits on top of them all: AI applications.

  • LLM API calls in practice — retries, timeouts, streaming, cost
  • RAG architecture — from basic retrieval to Hybrid Search
  • Agent design patterns — ReAct, Plan-and-Execute, Tool Use
  • When (and when not) to fine-tune — LoRA, DPO, RLHF
  • Vector DBs — pgvector vs Qdrant vs Pinecone
  • LLM evaluation — the real difficulty of measuring accuracy
  • Prompt engineering science — Structured Output, Few-shot, Chain-of-Thought
  • LLM observability — OpenTelemetry GenAI, LangSmith, LangFuse
  • Cost optimization — model choice, caching, Prompt Compression
  • Security — Prompt Injection, Data Leakage

"How to actually ship an AI product." Next post.