JIT 컴파일러의 코드 최적화 방법

컴파일용 메소드가 선택되면 JVM은 JIT(just-in-time) 컴파일러에 해당 바이트 코드를 공급합니다. JIT에서 메소드를 올바르게 컴파일하려면 바이트 코드의 시맨틱과 구문을 이해해야 합니다.

JIT 컴파일러에서 메소드를 분석하는 데 도움이 될 수 있도록 먼저 해당 바이트 코드가 트리라는 내부 표시로 다시 작성됩니다. 그런 다음, 메소드 트리에서 분석 및 최적화가 수행됩니다. 마지막으로 트리가 원시 코드로 변환됩니다. 이 섹션의 나머지 부분에서는 JIT 컴파일 단계에 대한 간단한 개요가 제공됩니다. 자세한 정보는 JIT 또는 AOT 문제점 진단을 참조하십시오.

JIT 컴파일러는 둘 이상의 컴파일 스레드를 사용하여 JIT 컴파일 태스크를 수행할 수 있습니다. 다중 스레드를 사용하면 Java 애플리케이션을 보다 빠르게 시작하는 데 도움이 될 수 있습니다. 실제로 복수 JIT 컴파일 스레드는 사용되지 않는 처리 코어가 있는 시스템에서만 성능을 개선합니다.

기본적인 컴파일 스레드 수는 JVM에서 식별되며 시스템 구성에 따라 결정됩니다. 결과 스레드 수가 최적이 아닌 경우, -XcompilationThreads 옵션을 사용하여 JVM 결정을 대체할 수 있습니다. 이 옵션 사용에 대한 정보는 -X 옵션을 참조하십시오.
참고: 시스템에 사용하지 않는 처리 코어가 없는 경우 컴파일 스레드 수를 늘리면 성능이 향상되지 않습니다.

컴파일은 다음과 같은 단계로 구성됩니다. 원시 코드 생성을 제외한 모든 단계는 크로스 플랫폼 코드입니다.

1단계 - 인라이닝

인라이닝은 소형 메소드 트리가 해당 호출자 트리로 병합되거나 "인라인되는" 프로세스입니다. 이를 통해 자주 실행되는 메소드 호출의 속도가 향상됩니다. 현재 최적화 레벨에 따라 강도 레벨이 다른 두 개의 인라이닝 알고리즘이 사용됩니다. 이 단계에서 수행되는 최적화에는 다음이 포함됩니다.
  • 단순 인라이닝
  • 호출 그래프 인라이닝
  • 후미 순환 제거
  • 가상 호출 보호 최적화

2단계 - 로컬 최적화

로컬 최적화에서는 한 번에 하나의 작은 코드 섹션을 분석하고 개선합니다. 여러 로컬 최적화 구현 시 일반 정적 컴파일러에서 사용되는 기술을 시도하고 테스트했습니다. 최적화에는 다음이 포함됩니다.
  • 로컬 데이터 플로우 분석 및 최적화
  • 레지스터 사용 최적화
  • Java 관용구의 단순화
이러한 기술은 특히 많은 개선 기회가 제시될 수 있는 글로벌 최적화 후에 반복적으로 적용됩니다.

3단계 - 제어 플로우 최적화

제어 플로우 최적화는 메소드(또는 메소드의 특정 섹션) 내부의 제어 플로우를 분석하고 코드 경로를 재배열하여 효율성을 향상시킵니다. 최적화의 내용은 다음과 같습니다.
  • 코드 다시 정렬, 분할 및 제거
  • 루프 감소 및 반전
  • 루프 진행 속도 조정 및 루프 고정 코드 동작
  • 루프 롤 해제 및 분리
  • 루프 버전화 및 특수화
  • 예외 지시 최적화
  • 전환 분석

4단계 - 글로벌 최적화

글로벌 최적화는 전체 메소드에서 동시에 작동합니다. 글로벌 최적화는 더 많은 컴파일 시간이 필요한 "비용이 많이 드는" 작업이지만 성능을 크게 향상시킬 수 있습니다. 최적화의 내용은 다음과 같습니다.
  • 글로벌 데이터 플로우 분석 및 최적화
  • 부분 중복 제거
  • 이스케이프 분석
  • GC 및 메모리 할당 최적화
  • 동기화 최적화

5단계 - 원시 코드 생성

원시 코드 생성 프로세스는 플랫폼 아키텍처에 따라 다양합니다. 일반적으로 이 컴파일 단계 동안에는 메소드 트리가 시스템 코드 명령어로 변환되고 아키텍처 특성에 따라 일부 소규모 최적화가 수행됩니다. 컴파일된 코드는 코드 캐시라는 JVM 프로세스 공간의 파트에 배치됩니다. 향후 호출 시 컴파일된 코드가 호출될 수 있도록 코드 캐시에 있는 메소드의 위치가 기록됩니다. 임의의 지정된 시간에 JVM 프로세스는 JVM 실행 파일과 JVM의 바이트 코드 인터프리터에 동적으로 링크된 JIT 컴파일 코드 세트로 구성되어 있습니다.