V8 WasmGC 투기적 최적화 — 가상 메서드를 인라인으로 만드는 법
V8이 WasmGC의 가상 메서드 디스패치에 투기적 인라이닝을 도입해 Dart와 Java 앱에서 최대 8% 성능을 끌어낸 방법.
WebAssembly에는 JIT 최적화가 필요 없다는 게 기존 통념이었다. 정적 타입 정보가 있으니 컴파일 시점에 이미 최적화가 끝난다는 논리였다. 그런데 WasmGC가 등장하면서 이 전제가 흔들렸다.
왜 WasmGC에서는 상황이 다른가
기존 WebAssembly는 C나 Rust처럼 가상 메서드 디스패치가 없는 언어를 대상으로 설계됐다. 함수 포인터를 통한 간접 호출이 있어도, 어떤 함수가 호출될지 정적으로 알 수 있는 경우가 많았다.
WasmGC는 다르다. Java나 Dart 같은 관리형 언어를 WebAssembly로 컴파일하기 위한 확장이다. 이 언어들은 다형성이 핵심이다. 같은 메서드 호출이 런타임에 어떤 구체 클래스를 거칠지 컴파일 타임에 알기 어렵다.
[💡 잠깐! 이 용어는?]
가상 메서드 디스패치(Virtual Method Dispatch): 객체지향 언어에서 메서드 호출 시, 실제 호출될 구현을 런타임에 결정하는 메커니즘. animal.speak()를 호출하면 Dog인지 Cat인지에 따라 다른 구현이 실행된다. 호출마다 "어느 함수를 쓸지" 테이블을 조회하는 비용이 생긴다.
투기적 최적화란
투기(Speculation)는 "아마 이럴 거야"라는 가정을 코드에 굽는 것이다. 실행 중에 수집한 피드백을 보고, "이 호출 지점은 거의 항상 Dog.speak()를 부른다"는 걸 알면, 굳이 테이블 조회 없이 바로 Dog.speak()를 인라인으로 심어버릴 수 있다. 속도가 확 빠르다.
물론 가정이 틀릴 때를 대비해야 한다. 예상과 다른 타입이 오면 **디옵티마이제이션(Deoptimization)**이 발동해 최적화 전 코드로 되돌아간다.
[💡 잠깐! 이 용어는?] 디옵티마이제이션(Deoptimization): 투기적 최적화의 가정이 깨졌을 때 최적화 전 상태로 복귀하는 과정. V8은 프로그램 상태를 저장해두었다가, 가정이 실패하면 그 지점부터 기준 코드로 재실행한다. 세 단계(상태 저장 → 변환 → 점프)를 거친다.
V8이 구현한 방식
V8의 WasmGC 최적화는 두 레이어로 동작한다.
[기준 컴파일러 (Baseline)]
→ 런타임에 호출 타겟 기록
→ "이 호출은 A 타입만 왔다", "B와 C가 섞였다" 등 피드백 수집
[최적화 컴파일러 (Turboshaft)]
→ 피드백 기반으로 최대 4개 타겟까지 인라인 처리
→ 인라인된 코드 안에서 추가 최적화 적용 가능
→ 가정 실패 시 → Deopt → 기준 코드로 복귀[💡 잠깐! 이 용어는?] 인라이닝(Inlining): 함수 호출을 함수 본문으로 직접 교체하는 최적화. 호출 오버헤드가 없어지고, 인라인된 코드가 주변 컨텍스트와 함께 추가로 최적화될 여지가 생긴다. 짧은 함수에 특히 효과적이다.
실제 성능 향상 수치
| 환경 | 성능 향상 |
|---|---|
| Dart 마이크로벤치마크 | 평균 1.59배 빠름 |
| Google Sheets (Dart 기반) | 7% 향상 |
| Flutter/Flute (Dart 기반) | 8% 향상 |
| SQLite 포팅 (C 기반 WasmGC) | 1% 향상 |
Dart처럼 가상 메서드 디스패치가 많은 언어에서 효과가 집중된다. SQLite가 1%에 그치는 이유는 간접 호출 자체가 많지 않기 때문이다.
비유하면, 배달 기사가 "이 집은 항상 같은 동에 배달 간다"는 걸 경험으로 알면 네비를 매번 켜지 않고 바로 가는 것과 같다. 습관적 경로에는 최적화가 효과적이다. 가끔 이상한 주소가 오면 그때만 네비를 켜면 된다.
이전과 무엇이 달라졌나
기존 WebAssembly는 투기적 최적화가 필요 없었다. 정적 타입 정보가 있고, 관리형 객체를 다루는 WasmGC가 없었기 때문이다. WasmGC 이전에는 간접 호출이 있더라도 어느 정도 정적 분석이 가능했다.
WasmGC 이후로는 Java/Dart/Kotlin 같은 관리형 언어 전체가 WebAssembly 타깃이 됐다. 이 언어들은 JVM이나 Dart VM에서 수십 년간 쌓인 JIT 최적화 기법들의 혜택을 받아왔다. V8이 이 최적화를 WebAssembly 레이어에서 재구현하는 건 자연스러운 흐름이다.
마무리
WasmGC는 WebAssembly가 "C/C++/Rust용 실행 환경"을 넘어 "모든 관리형 언어의 실행 환경"으로 진화하는 신호다. 투기적 인라이닝은 그 진화에서 필수 기반 기술이다. Dart와 Java 앱이 브라우저에서 네이티브에 가까운 속도로 돌아가는 미래가 조금씩 가까워지고 있다.
참고:
같은 카테고리 · Runtime
비슷한 주제의 최신 글
태그가 겹치는 글
공통 태그가 많을수록 위에 보인다