AOT
프로그램을 실행하기 전에 미리 네이티브 기계어로 컴파일하는 방식입니다.
AOT vs JIT 핵심 비교
[ AOT (Ahead-Of-Time) ]
소스코드 → 컴파일러 → 네이티브 기계어 → 실행
↑ 실행 전에 완료 ↑ CPU가 바로 실행
[ JIT (Just-In-Time) ]
소스코드 → 컴파일러 → 바이트코드 → VM 실행 중 → 핫스팟을 기계어로 변환
↑ 실행 중에 컴파일 발생
| 항목 | AOT | JIT |
| 변환 시점 | 실행 전 | 실행 중 |
| 결과물 | 네이티브 바이너리 | 바이트코드 + 런타임 변환 |
| 시작 속도 | 빠름 (이미 기계어) | 느림 (워밍업 필요) |
| 장기 실행 성능 | 일정 | 워밍업 후 더 빠를 수 있음 |
| VM 필요 여부 | 불필요 | 필요 (JVM, CLR 등) |
언어별 AOT 적용 현황
원래부터 AOT인 언어
C → gcc → 기계어 바이너리
C++ → g++ → 기계어 바이너리
Rust → rustc → 기계어 바이너리
Go → go build → 기계어 바이너리
이 언어들은 태생부터 AOT 컴파일이 기본입니다.
AOT를 선택할 수 있는 언어
| 언어 | 기본 방식 | AOT 도구 |
| Java | JVM (JIT) | GraalVM Native Image |
| Kotlin | JVM (JIT) | Kotlin/Native, GraalVM |
| C# | .NET CLR (JIT) | .NET Native AOT |
| Dart | Dart VM (JIT) | dart compile exe |
| JavaScript | V8 (JIT) | Snapshot (제한적 AOT) |
JIT만 사용할 때의 문제
클라이언트 요청 → 서버리스 함수 시작
│
▼
JVM 초기화 (~500ms)
클래스 로딩 (~300ms)
Interpreter 실행
JIT 워밍업 (~1000ms)
│
▼
실제 응답 ← 이미 2초 가까이 지남 (콜드 스타트)
이 콜드 스타트 문제가 AOT의 핵심 동기입니다.
AOT 적용 시
클라이언트 요청 → 서버리스 함수 시작
│
▼
네이티브 바이너리 즉시 실행 (~50ms)
│
▼
실제 응답 ← 거의 즉시
AOT의 장단점
| 장점 | 상세 |
| 즉시 시작 | JVM 초기화·JIT 워밍업 없이 바로 실행 |
| 낮은 메모리 | JVM 자체의 메모리 오버헤드 없음 |
| 배포 간편 | 단일 바이너리, 런타임 설치 불필요 |
| 예측 가능한 성능 | 처음부터 끝까지 일정한 속도 |
| 단점 | 상세 |
| 런타임 최적화 부재 | 프로파일링 기반 최적화가 없어 장기 실행 시 JIT보다 느릴 수 있음 |
| 리플렉션 제한 | 동적으로 클래스를 탐색하는 기능에 별도 설정 필요 |
| 빌드 시간 증가 | AOT 컴파일 자체가 오래 걸림 (수 분 이상) |
| 플랫폼 종속 | OS·CPU마다 별도 빌드 필요 ("Write Once, Run Anywhere" 상실) |
| 바이너리 크기 | 필요한 라이브러리가 바이너리에 포함되어 커질 수 있음 |
AOT가 JIT보다 느릴 수 있는 이유
[ AOT ]
컴파일 시점에 알 수 있는 정보만으로 최적화
→ "이 분기는 보통 true일 것이다" 같은 런타임 정보 없음
[ JIT ]
실행 중 프로파일링 데이터 수집
→ "이 메서드는 99% String 타입으로 호출된다"
→ 해당 케이스에 특화된 기계어 생성 (Speculative Optimization)
성능
▲
│ ┌─── JIT (워밍업 후 최적화)
│ ╱
│ ╱
│ ─────╱────── AOT (일정한 성능)
│ ╱
│ ╱ ← JIT 워밍업 구간
└──────────────────────────────► 시간
실무 선택 기준
| 상황 | 추천 | 이유 |
| AWS Lambda, Cloud Functions | AOT | 콜드 스타트 최소화 |
| CLI 도구 | AOT | 즉시 시작, 단일 바이너리 배포 |
| 컨테이너 마이크로서비스 | AOT | 이미지 경량화, 빠른 스케일 아웃 |
| 대규모 장기 운영 서버 | JIT | JIT 최적화로 최대 처리량 |
| 개발·디버깅 단계 | JIT | 빠른 빌드, JVM 디버깅 도구 활용 |
핵심 정리
AOT는 실행 전에 미리 기계어로 컴파일하는 방식으로, C/C++/Rust/Go는 태생부터 AOT이고, Java/Kotlin/C#은 GraalVM 등을 통해 선택적으로 AOT를 적용할 수 있습니다. 빠른 시작과 낮은 메모리가 강점이지만, 런타임 최적화 부재와 플랫폼 종속성이 트레이드오프입니다. 결국 AOT와 JIT는 우열이 아니라 실행 환경에 따른 선택의 문제입니다.