11. 백엔드 컴파일과 최적화
- 백엔드 컴파일 :
코드 --> 바이트코드 -(백엔드 컴파일)-> 네이티브 코드
- JIT, AOT 컴파일러
11.2 JIT 컴파일러
- 인터프리터로 해석해 실행.
- 핫코드 (자주 실행되는 메서드나 코드블럭을)를 네이티브 코드로 컴파일하고, 최적화.
11.2.1 인터프리터와 컴파일러
- 인터프리터는 적극적으로 최적화하는 컴파일러의 비상구 역할도 한다. 적극적 최적화의 가정이 무너지는 경우 최적화를 취소하고 다시 인터프리터에 실행을 맡기기도한다.
- 핫스팟 가상머신의 JIT 컴파일러
- C1 : 클라이언트 컴파일러
- C2 : 서버 컴파일러
- Gral 컴파일러
- 인터프리터와 컴파일러 사용 모드 조정 옵션
-Xint
인터프리터만 사용-Xcomp
컴파일모드로 고정 = 컴파일을 완료한 후 실행, 인터프리터가 실행에 개입된다.- 기본은 mixedMode
- 계층형 컴파일
계층형 컴파일은 JVM HotSpot의 성능 최적화 전략으로, 코드 실행 시 여러 단계를 거쳐 점진적으로 최적화하는 방식입니다. JDK 7부터 도입되었으며, -XX:+TieredCompilation
옵션으로 활성화됩니다(JDK 8부터는 기본 활성화).
계층형 컴파일 단계
HotSpot VM의 계층형 컴파일은 총 5개의 단계(티어)로 구성됩니다:
[더 많은 최적화]
↑
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 티어 0 │→→→│ 티어 1 │→→→│ 티어 2 │→→→│ 티어 3 │→→→│ 티어 4 │
│ 인터프리터 │ │ C1 컴파일러 │ │ C1 컴파일러 │ │ C1 컴파일러 │ │ C2 컴파일러 │
│ │ │ 제한된 프로 │ │ 기본 프로파 │ │ 전체 프로파 │ │ 최대 최적화 │
│ 프로파일링: │ │ 파일링 │ │ 일링 │ │ 일링 │ │ │
│ 없음 │ │ │ │ │ │ │ │ 프로파일링: │
│ │ │ 최적화: 최소 │ │ 최적화: 기본 │ │ 최적화: 기본 │ │ 없음 │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
[시작 단계] [최종 단계]
각 단계별 특징
- 티어 0 (인터프리터)
- 모든 코드는 처음에 인터프리터로 실행됩니다
- 성능 모니터링: 없음
- 최적화: 없음
- 용도: 초기 실행 및 프로파일링 정보 수집
- 티어 1 (C1 컴파일러, 제한적 프로파일링)
- 간단한 형태의 JIT 컴파일 수행
- 성능 모니터링: 일부 (메서드 호출 횟수, 분기 카운터)
- 최적화: 최소 (인라이닝 없음)
- 용도: 빠른 컴파일과 기본 최적화
- 티어 2 (C1 컴파일러, 기본 프로파일링)
- 성능 모니터링: 일부 (티어 1보다 더 많은 데이터 수집)
- 최적화: 제한적 (기본적인 인라이닝)
- 용도: 중간 수준의 최적화
- 티어 3 (C1 컴파일러, 전체 프로파일링)
- 성능 모니터링: 모두 (모든 프로파일링 정보 수집)
- 최적화: 중간 (더 광범위한 인라이닝, 루프 최적화)
- 용도: C2 컴파일러를 위한 상세 프로파일링 데이터 수집
- 티어 4 (C2 컴파일러)
- 성능 모니터링: 없음 (프로파일링 정보만 활용)
- 최적화: 최대 (공격적인 인라이닝, 루프 최적화, 탈출 분석 등)
- 용도: 장기 실행 애플리케이션의 최대 성능
최적화 전략 및 특징
- 적응형 최적화(Adaptive Optimization): 코드의 실행 빈도에 따라 최적화 수준을 조정
- OSR(On-Stack Replacement): 현재 실행 중인 메서드를 최적화된 버전으로 교체
- 탈최적화(Deoptimization): 최적화 가정이 깨졌을 때 인터프리터 모드로 회귀
- 프로파일 기반 최적화(Profile-Guided Optimization): 실행 패턴을 기반으로 최적화 결정
최적화 기법
티어 | 컴파일러 | 인라이닝 | 루프 최적화 | 탈출 분석 | 락 생략 | 벡터화 |
---|---|---|---|---|---|---|
0 | 인터프리터 | ❌ | ❌ | ❌ | ❌ | ❌ |
1 | C1 | 제한적 | ❌ | ❌ | 제한적 | ❌ |
2,3 | C1 | 중간 | 제한적 | ❌ | 기본 | ❌ |
4 | C2 | 광범위 | 고급 | ✅ | 고급 | ✅ |
코드 실행 경로 예시
일반적인 메서드 실행 경로:
- 인터프리터로 시작 (티어 0)
- 호출 횟수 임계값 도달 시 C1 컴파일 (티어 1/2/3)
- 충분한 프로파일링 데이터 수집 후 C2 컴파일 (티어 4)
- 최종 최적화된 네이티브 코드로 실행
자주 실행되지 않는 코드는 인터프리터나 C1 단계에 머물 수 있으며, “핫” 코드만 C2 컴파일러까지 도달합니다.
JVM 옵션 설정
-XX:+TieredCompilation
: 계층형 컴파일 활성화 (JDK 8+ 기본값)-XX:TieredStopAtLevel=N
: N 티어에서 컴파일 중단 (1~4)-XX:CompileThreshold=N
: 컴파일 임계값 설정-XX:+PrintCompilation
: 컴파일 정보 출력
11.2.2 컴파일 대상과 촉발 조건
컴파일 대상
- 여러번 호출되는 메서드 : 메서드 전체
- 여러번 실행되는 순환문의 본문 : 메서드 전체
- 메서드의 실행 진입점이 달라짐.
- 온스택 치환 : 메서드 스택 프레임에서 치환됨.
촉발 조건 (여러번?)
- 샘플 기반 핫스팟 코드 탐지 : 스레드의 호출스택 상단을 샘플링하여 메서드가 자주 발견되는지 확인
- J9
- 카운터 기반 : 메서드와 코드블록에 대한(백엣지) 카운터를 설정
- 백엣지 : 순환문 경계에서 순환문 처음으로 점프하는것.
- 핫스팟 VM
- 기본 문턱값은 클라이언트 모드(c1)에서 1500회, 서버모드에서(c2) 1만회
- 단위시간당 호출 횟수를 계산하며, 반감기 이후 카운터값을 반 줄인다.
11.2.3 컴파일 과정
클라이언트 컴파일러(C1)
그림참고
서버 컴파일러(C2)
- 느리지만, 성능최적화 결과물이 훨씬 좋아서 JDK9부터 기본모드가 됨.
11.2.4 실전: JIT컴파일 결과 확인 및 분석
- 일부 변수를 넣어서 돌리면 컴파일 과정 로그를 받을 수 있음.
- 컴파일 순서 경향
- 클라이언트 컴파일러가 최적화
- 서버 컴파일러가 최적화
- 1 최적화 취소
- 서버컴파일러가 추가 최적화
- 컴파일 과정 분석은 IGV 기능을 사용해서 볼수있음.
- 코드를 이루고있는 블럭들의 변화를 단계별로 볼수있음