PGO(Profile-Guided Optimization)

1,007 단어·3 분·원문(.md)

PGO는 프로그램이 실제 실행될 때 수집된 데이터(프로파일)를 바탕으로 컴파일러가 기계어를 더 빠르고 효율적으로 생성하도록 돕는 최적화 기법이다.

JIT Compiler(c2)가 런타임에 자연스럽게 수행하는 AOT(Native Image) 빌드 환경에서도 유사하게 구현할 수 있도록 만들어주는 핵심 기술이다

PGO가 필요한 이유? #

GraalVM Native Image와 같은 AOT 컴파일 방식은 실행전(빌드시점)에 기계어를 모두 생성한다.

실행중에 어떤 코드가 자주 쓰이는지 알 수 없기 때문에 어떤 분기문이 주로 실행될건지, 인터페이스 변수에 실제로 어떤 구현체 객체가 할당될지 확신할 수 없어 모든 가능성을 열어둔 보수적인 코드를 생성한다.

PGO는 AOT 컴파일러에 이러한 실행 통계를 외부 파일 형태로 미리 주입하여, 마치 JIT 컴파일러처럼 공격적이고 효율적인 기계어가 생성되도록 유도한다.

GraalVM에서 PGO가 동작하는 3단계 과정 #

실제 환경에서 PGO를 적용하여 네이티브 이미지를 최적화하는 과정은 다음과 같다.

  1. 계측(Instrumentation) 빌드: 먼저 실행 통계를 수집할 수 있는 코드가 삽입된 특수한 네이티브 이미지 빌드를 한다. (예: --pgo-instrument 옵션 활성화)
  2. 프로파일링 데이터 수집: 생성된 특수 이미지를 운영 환경과 가장 유사한 워크로드(가짜 트래픽 발생등)으로 실행한다. 프로그램이 실행을 마치면 자주 호출된 메서드, 분기 도달 확률, 타입 통계 등이 담긴 프로파일 파일(주로 *.iprof)이 디스크에 추출된다.
  3. 최종 최적화 빌드: 수집된 프로파일 파일을 AOT 컴파일러에 입력값을 제공하여 최종 네이티브 이미지를 다시 빌드한다. (예: --pgo=default.iprof 옵션 사용)

PGO를 통해 얻는 주요 효과 #

컴파일러는 전달 받은 데이터를 분석하여 다음과 같은 C2 수준에 최적화를 수행한다.

  • 정확한 Inlining: 실제로 가장 많이 호출되는 경로 Hot Path의 메서드를 식별하여, 함수 호출 비용(오버헤드)를 없애고 코드를 직접 호출부에 병합한다
  • 탈가상화(Devirtualization): 예를 들어 List 타입 변수에 실제 런타임 통계상 ArrayList 객체만 100% 들어온다는 것이 확인되면, 복잡한 가상 메서드 테이블 조회를 생략하고 ArrayList의 메서드를 직접 호출하도록 기계어를 직결한다.
  • 분기 예측 최적화(Branch Predication) if-else 문에서 99% 확률로 실행되는 if 블록의 코드를 cpu가 읽기 쉬운 위치에 일렬로 배치하여, 명령어 파이프라인의 효율을 극대화한다.

결과적으로 PGO를 적용한 네이티브 바이너리는 기존 AOT의 고유한 장점(즉각적인 시작 시간, 적은 메모리 사용량)을 그대로 유지하면서도 JIT 컴파일러의 최고성능 Peak Performace에 근접하는 높은 처리량을 확보할 수 있다.

JVM/pgo.md