이 기사는 JVM 성능 최적화 시리즈(첫 번째 기사: 포털)의 두 번째 기사가 될 것이며, Java 컴파일러는 이 기사에서 논의되는 핵심 내용이 될 것입니다.
이 기사에서 저자(Eva Andreasson)는 먼저 다양한 유형의 컴파일러를 소개하고 클라이언트 측 컴파일, 서버 측 컴파일러 및 멀티 레이어 컴파일의 실행 성능을 비교합니다. 그런 다음 기사 마지막 부분에서는 데드 코드 제거, 코드 임베딩, 루프 본문 최적화 등 몇 가지 일반적인 JVM 최적화 방법을 소개합니다.
Java의 가장 자랑스러운 기능인 "플랫폼 독립성"은 Java 컴파일러에서 비롯됩니다. 소프트웨어 개발자는 가능한 최고의 Java 애플리케이션을 작성하기 위해 최선을 다하고, 컴파일러는 무대 뒤에서 실행되어 대상 플랫폼을 기반으로 효율적으로 실행 가능한 코드를 생성합니다. 다양한 컴파일러는 다양한 애플리케이션 요구 사항에 적합하므로 다양한 최적화 결과를 생성합니다. 따라서 컴파일러의 작동 방식을 더 잘 이해하고 더 많은 유형의 컴파일러를 알 수 있다면 Java 프로그램을 더 잘 최적화할 수 있습니다.
이 기사에서는 다양한 JVM(Java Virtual Machine) 컴파일러 간의 차이점을 강조하고 설명합니다. 동시에 JIT(Just-In-Time 컴파일러)에서 일반적으로 사용되는 몇 가지 최적화 솔루션에 대해서도 논의하겠습니다.
컴파일러란 무엇입니까?
간단히 말해서, 컴파일러는 프로그래밍 언어 프로그램을 입력으로 사용하고 다른 실행 가능한 언어 프로그램을 출력으로 사용합니다. Javac은 가장 일반적인 컴파일러입니다. 모든 JDK에 존재합니다. Javac은 Java 코드를 출력으로 가져와 이를 JVM 실행 코드(바이트코드)로 변환합니다. 이러한 바이트코드는 .class로 끝나는 파일에 저장되며 Java 프로그램이 시작될 때 Java 런타임 환경에 로드됩니다.
바이트코드는 CPU에서 직접 읽을 수 없으며 현재 플랫폼이 이해할 수 있는 기계 명령 언어로 번역되어야 합니다. JVM에는 바이트코드를 대상 플랫폼에서 실행 가능한 명령어로 변환하는 또 다른 컴파일러가 있습니다. 일부 JVM 컴파일러에는 여러 수준의 바이트코드 코드 단계가 필요합니다. 예를 들어 컴파일러는 바이트 코드를 기계 명령어로 변환하기 전에 여러 가지 형태의 중간 단계를 거쳐야 할 수 있습니다.
플랫폼에 구애받지 않는 관점에서 우리는 코드가 가능한 한 플랫폼에 구애받지 않기를 원합니다.
이를 달성하기 위해 우리는 실행 가능한 코드를 특정 플랫폼의 아키텍처에 실제로 바인딩하는 가장 낮은 바이트 코드 표현부터 실제 기계어 코드까지의 마지막 변환 수준에서 작업합니다. 가장 높은 수준에서 컴파일러를 정적 컴파일러와 동적 컴파일러로 나눌 수 있습니다. 우리는 목표 실행 환경, 원하는 최적화 결과, 충족해야 하는 리소스 제약 조건을 기반으로 적절한 컴파일러를 선택할 수 있습니다. 이전 기사에서는 정적 컴파일러와 동적 컴파일러에 대해 간략하게 설명했으며 다음 섹션에서는 이에 대해 더 자세히 설명하겠습니다.
정적 컴파일과 동적 컴파일
앞서 언급한 javac는 정적 컴파일의 예입니다. 정적 컴파일러를 사용하면 입력 코드가 한 번 해석되고 출력은 향후 프로그램이 실행될 형식입니다. 소스 코드를 업데이트하고 (컴파일러를 통해) 다시 컴파일하지 않는 한 프로그램의 실행 결과는 절대 변경되지 않습니다. 이는 입력이 정적 입력이고 컴파일러가 정적 컴파일러이기 때문입니다.
정적 컴파일을 사용하면 다음 프로그램이 실행됩니다.
다음과 같이 코드 코드를 복사합니다.
staticint add7(int x ){ return x+7;}
다음과 유사한 바이트코드로 변환됩니다.
다음과 같이 코드 코드를 복사합니다.
iload0 bipush 7 iadd ireturn
동적 컴파일러는 한 언어를 다른 언어로 동적으로 컴파일합니다. 소위 동적이란 프로그램이 실행되는 동안 컴파일하는 것을 말합니다. 동적 컴파일 및 최적화의 장점은 애플리케이션이 로드될 때 일부 변경 사항을 처리할 수 있다는 것입니다. Java 런타임은 예측할 수 없거나 심지어 변화하는 환경에서도 실행되는 경우가 많으므로 동적 컴파일은 Java 런타임에 매우 적합합니다. 대부분의 JVM은 JIT 컴파일러와 같은 동적 컴파일러를 사용합니다. 동적 컴파일 및 코드 최적화에는 일부 추가 데이터 구조, 스레드 및 CPU 리소스를 사용해야 한다는 점은 주목할 가치가 있습니다. 최적화 프로그램이나 바이트코드 컨텍스트 분석기가 고급화될수록 더 많은 리소스를 소비합니다. 그러나 이러한 비용은 상당한 성능 향상에 비해 미미합니다.
Java의 JVM 유형 및 플랫폼 독립성
모든 JVM 구현의 공통 기능은 바이트코드를 기계 명령어로 컴파일하는 것입니다. 일부 JVM은 애플리케이션이 로드될 때 코드를 해석하고 성능 카운터를 사용하여 "핫" 코드를 찾습니다. 다른 JVM은 컴파일을 통해 이를 수행합니다. 컴파일의 주요 문제점은 중앙 집중화에 많은 리소스가 필요하지만 더 나은 성능 최적화로 이어진다는 것입니다.
Java를 처음 사용하는 경우 JVM의 복잡성으로 인해 확실히 혼란스러울 것입니다. 하지만 좋은 소식은 그것을 알아낼 필요가 없다는 것입니다! JVM은 코드의 컴파일과 최적화를 관리하므로 기계 지침과 프로그램이 실행되는 플랫폼의 아키텍처에 가장 잘 맞는 코드를 작성하는 방법에 대해 걱정할 필요가 없습니다.
자바 바이트코드에서 실행 파일로
Java 코드가 바이트코드로 컴파일되면 다음 단계는 바이트코드 명령어를 기계어 코드로 변환하는 것입니다. 이 단계는 인터프리터나 컴파일러를 통해 구현될 수 있습니다.
설명하다
해석은 바이트코드를 컴파일하는 가장 간단한 방법입니다. 인터프리터는 각 바이트코드 명령어에 해당하는 하드웨어 명령어를 룩업 테이블 형태로 찾아 CPU로 보내 실행한다.
인터프리터를 사전처럼 생각할 수 있습니다. 각 특정 단어(바이트코드 명령어)에 대해 그에 해당하는 특정 번역(기계어 명령어)이 있습니다. 인터프리터는 명령어를 읽을 때마다 즉시 실행하기 때문에 이 방법은 명령어 세트를 최적화할 수 없습니다. 동시에 바이트코드가 호출될 때마다 즉시 해석되어야 하므로 인터프리터가 매우 느리게 실행됩니다. 인터프리터는 매우 정확한 방식으로 코드를 실행하지만 출력 명령 세트가 최적화되지 않았기 때문에 대상 플랫폼의 프로세서에 최적의 결과를 생성하지 못할 수 있습니다.
엮다
컴파일러는 실행할 모든 코드를 런타임에 로드합니다. 이런 방식으로 바이트코드를 변환할 때 런타임 컨텍스트의 전부 또는 일부를 참조할 수 있습니다. 그것이 내리는 결정은 코드 그래프 분석 결과를 기반으로 합니다. 예를 들어 다양한 실행 분기를 비교하고 런타임 컨텍스트 데이터를 참조합니다.
바이트 코드 시퀀스가 기계 코드 명령어 세트로 변환된 후 이 기계 코드 명령어 세트를 기반으로 최적화가 수행될 수 있습니다. 최적화된 명령어 세트는 코드 버퍼라는 구조에 저장됩니다. 이러한 바이트코드가 다시 실행되면 이 코드 버퍼에서 최적화된 코드를 직접 얻어서 실행할 수 있습니다. 어떤 경우에는 컴파일러가 코드를 최적화하기 위해 최적화 프로그램을 사용하지 않고 새로운 최적화 시퀀스인 "성능 계산"을 사용합니다.
코드 캐시를 사용하면 결과 집합 명령을 재해석이나 컴파일 없이 즉시 실행할 수 있다는 장점이 있습니다.
이는 특히 메소드가 여러 번 호출되는 Java 애플리케이션의 경우 실행 시간을 크게 줄일 수 있습니다.
최적화
동적 컴파일이 도입되면서 성능 카운터를 삽입할 수 있는 기회가 생겼습니다. 예를 들어, 컴파일러는 바이트코드 블록(특정 메서드에 해당)이 호출될 때마다 증가하는 성능 카운터를 삽입합니다. 컴파일러는 이러한 카운터를 사용하여 "핫 블록"을 찾아 응용 프로그램의 성능을 최대한 향상시키기 위해 최적화할 수 있는 코드 블록을 결정할 수 있습니다. 런타임 성능 분석 데이터는 컴파일러가 온라인 상태에서 더 많은 최적화 결정을 내리는 데 도움을 주어 코드 실행 효율성을 더욱 향상시킵니다. 점점 더 정확한 코드 성능 분석 데이터를 얻을 수 있기 때문에 더 많은 최적화 지점을 찾고 더 나은 최적화 결정을 내릴 수 있습니다. 예를 들어 명령어를 더 잘 배열하는 방법, 더 효율적인 명령어 세트를 사용할지 여부 등이 있습니다. 중복 작업 제거 여부 등
예를 들어
다음 Java 코드를 고려하십시오. 코드 복사 코드는 다음과 같습니다.
staticint add7(int x ){ return x+7;}
Javac은 이를 다음 바이트코드로 정적으로 변환합니다.
다음과 같이 코드 코드를 복사합니다.
iload0
바이푸시 7
iadd
돌아오다
이 메소드가 호출되면 바이트코드는 기계 명령어로 동적으로 컴파일됩니다. 성능 카운터(존재하는 경우)가 지정된 임계값에 도달하면 메서드가 최적화될 수 있습니다. 최적화된 결과는 다음 기계 명령어 세트와 유사할 수 있습니다.
다음과 같이 코드 코드를 복사합니다.
레아 렉스,[rdx+7] ret
다양한 컴파일러는 다양한 애플리케이션에 적합합니다.
다양한 애플리케이션에는 다양한 요구 사항이 있습니다. 엔터프라이즈 서버 측 애플리케이션은 일반적으로 오랜 시간 동안 실행되어야 하므로 일반적으로 더 많은 성능 최적화를 원하지만 클라이언트 측 애플릿은 더 빠른 응답 시간과 더 적은 리소스 소비를 원할 수 있습니다. 세 가지 다른 컴파일러와 각각의 장단점에 대해 논의해 보겠습니다.
클라이언트 측 컴파일러
C1은 잘 알려진 최적화 컴파일러입니다. JVM을 시작할 때 -client 매개변수를 추가하여 컴파일러를 시작하십시오. 이름을 보면 C1이 클라이언트 컴파일러라는 것을 알 수 있습니다. 사용 가능한 시스템 리소스가 적거나 빠른 시작이 필요한 클라이언트 애플리케이션에 이상적입니다. C1은 성능 카운터를 사용하여 코드 최적화를 수행합니다. 이는 소스 코드에 대한 개입이 적은 간단한 최적화 방법입니다.
서버측 컴파일러
장기 실행 애플리케이션(예: 서버 측 엔터프라이즈 애플리케이션)의 경우 클라이언트 측 컴파일러를 사용하는 것만으로는 충분하지 않을 수 있습니다. 이때 우리는 C2와 같은 서버측 컴파일러를 선택해야 합니다. JVM 시작 라인에 서버를 추가하여 최적화 프로그램을 시작할 수 있습니다. 대부분의 서버 측 애플리케이션은 일반적으로 장기간 실행되므로 C2 컴파일러를 사용하면 단기 실행 경량 클라이언트 측 애플리케이션보다 더 많은 성능 최적화 데이터를 수집할 수 있습니다. 따라서 더욱 발전된 최적화 기술과 알고리즘을 적용할 수도 있습니다.
팁: 서버측 컴파일러를 준비하세요.
서버 측 배포의 경우 컴파일러가 해당 "핫" 코드를 최적화하는 데 시간이 걸릴 수 있습니다. 따라서 서버측 배포에는 "준비" 단계가 필요한 경우가 많습니다. 따라서 서버 측 배포에서 성능 측정을 수행할 때 항상 애플리케이션이 안정적인 상태에 도달했는지 확인하십시오! 컴파일러에 컴파일할 시간을 충분히 주면 애플리케이션에 많은 이점을 가져올 수 있습니다.
서버측 컴파일러는 클라이언트측 컴파일러보다 더 많은 성능 튜닝 데이터를 얻을 수 있으므로 더 복잡한 분기 분석을 수행하고 더 나은 성능으로 최적화 경로를 찾을 수 있습니다. 성능 분석 데이터가 많을수록 애플리케이션 분석 결과가 더 좋아집니다. 물론 광범위한 성능 분석을 수행하려면 더 많은 컴파일러 리소스가 필요합니다. 예를 들어 JVM이 C2 컴파일러를 사용하는 경우 더 많은 CPU 주기, 더 큰 코드 캐시 등을 사용해야 합니다.
다단계 컴파일
다중 계층 컴파일은 클라이언트측 컴파일과 서버측 컴파일을 혼합합니다. Azul은 Zing JVM에서 최초로 다중 계층 컴파일을 구현했습니다. 최근 이 기술은 Oracle Java Hotspot JVM(Java SE7 이후)에 채택되었습니다. 다중 레벨 컴파일은 클라이언트측 컴파일러와 서버측 컴파일러의 장점을 결합합니다. 클라이언트 컴파일러는 두 가지 상황, 즉 애플리케이션이 시작될 때와 성능 카운터가 성능 최적화를 수행하기 위해 더 낮은 수준의 임계값에 도달할 때 활성화됩니다. 또한 클라이언트 컴파일러는 성능 카운터를 삽입하고 나중에 고급 최적화를 위해 서버측 컴파일러에서 사용할 명령 세트를 준비합니다. 다층 컴파일은 리소스 활용도가 높은 성능 분석 방법입니다. 영향이 적은 컴파일러 활동 중에 데이터를 수집하므로 이 데이터는 나중에 고급 최적화에 사용될 수 있습니다. 이 접근 방식은 해석 코드를 사용하여 카운터를 분석하는 것보다 더 많은 정보를 제공합니다.
그림 1은 인터프리터, 클라이언트 측 컴파일, 서버 측 컴파일 및 멀티 레이어 컴파일의 성능 비교를 설명합니다. X축은 실행 시간(시간 단위)이고, Y축은 성능(단위 시간당 작업 횟수)입니다.
그림 1. 컴파일러 성능 비교
순수하게 해석된 코드에 비해 클라이언트측 컴파일러를 사용하면 성능이 약 5~10배 향상될 수 있습니다. 얻을 수 있는 성능 향상 정도는 컴파일러의 효율성, 사용 가능한 최적화 프로그램의 종류, 애플리케이션 디자인이 대상 플랫폼과 얼마나 잘 일치하는지에 따라 달라집니다. 그러나 프로그램 개발자의 경우 마지막 항목을 무시하는 경우가 많습니다.
클라이언트측 컴파일러와 비교하여 서버측 컴파일러는 종종 30%~50%의 성능 향상을 가져올 수 있습니다. 대부분의 경우 성능 향상은 리소스 소비를 대가로 이루어지는 경우가 많습니다.
다중 레벨 컴파일은 두 컴파일러의 장점을 결합합니다. 클라이언트 측 컴파일은 시작 시간이 더 짧고 빠른 최적화를 수행할 수 있습니다. 서버 측 컴파일은 후속 실행 프로세스 중에 보다 고급 최적화 작업을 수행할 수 있습니다.
몇 가지 일반적인 컴파일러 최적화
지금까지 코드 최적화의 의미와 JVM이 코드 최적화를 수행하는 방법과 시기를 살펴보았습니다. 다음으로 컴파일러에서 실제로 사용하는 몇 가지 최적화 방법을 소개하며 이 글을 마무리하겠습니다. JVM 최적화는 실제로 바이트코드 단계(또는 하위 수준 언어 표현 단계)에서 발생하지만 여기서는 이러한 최적화 방법을 설명하기 위해 Java 언어를 사용합니다. 물론 이 섹션에서 모든 JVM 최적화 방법을 다루는 것은 불가능합니다. 이러한 소개가 여러분이 수백 가지의 고급 최적화 방법을 배우고 컴파일러 기술을 혁신하는 데 영감을 주기를 바랍니다.
데드 코드 제거
데드 코드 제거는 이름에서 알 수 있듯이 절대 실행되지 않는 코드, 즉 "데드" 코드를 제거하는 것입니다.
컴파일러가 작업 중에 중복된 명령어를 발견하면 실행 명령어 세트에서 이러한 명령어를 제거합니다. 예를 들어, 목록 1에서는 변수 중 하나가 할당된 후에 절대 사용되지 않으므로 실행 중에 할당 문을 완전히 무시할 수 있습니다. 바이트코드 수준의 작업에 따라 변수 값은 레지스터에 로드될 필요가 없습니다. 로드할 필요가 없다는 것은 CPU 시간이 덜 소비된다는 것을 의미하므로 코드 실행 속도가 빨라지고 궁극적으로 애플리케이션 속도가 빨라집니다. 로드 코드가 초당 여러 번 호출되면 최적화 효과가 더욱 분명해집니다.
목록 1에서는 Java 코드를 사용하여 절대 사용되지 않는 변수에 값을 할당하는 예를 보여줍니다.
목록 1. 데드 코드 복사 코드 코드는 다음과 같습니다.
int timeToScaleMyApp(boolean unlimitedOfResources){
int reArchitect =24;
int patchByClustering =15;
int useZing =2;
if(endlessOfResources)
reArchitect + useZing을 반환합니다.
또 다른
return useZing;
}
바이트코드 단계 중에 변수가 로드되었지만 전혀 사용되지 않은 경우 목록 2에 표시된 대로 컴파일러는 데드 코드를 감지하고 제거할 수 있습니다. 이 로딩 작업을 절대 수행하지 않으면 CPU 시간을 절약하고 프로그램 실행 속도를 향상시킬 수 있습니다.
목록 2. 최적화된 코드 복사 코드는 다음과 같습니다.
int timeToScaleMyApp(boolean unlimitedOfResources){
int reArchitect =24; //여기서 불필요한 작업이 제거되었습니다...
int useZing =2;
if(endlessOfResources)
reArchitect + useZing을 반환합니다.
또 다른
return useZing;
}
중복 제거는 중복된 명령을 제거하여 애플리케이션 성능을 향상시키는 최적화 방법입니다.
많은 최적화에서는 기계 명령어 수준 점프 명령어(예: x86 아키텍처의 JMP)를 제거하려고 시도하여 명령어 포인터 레지스터를 변경하여 프로그램 실행 흐름을 전환합니다. 이 점프 명령은 다른 ASSEMBLY 명령에 비해 리소스를 많이 소모하는 명령입니다. 그렇기 때문에 우리는 이런 종류의 교육을 줄이거나 없애고자 합니다. 코드 임베딩은 전송 명령을 제거하기 위한 매우 실용적이고 잘 알려진 최적화 방법입니다. 점프 명령을 실행하는 데 비용이 많이 들기 때문에 자주 호출되는 일부 작은 메서드를 함수 본문에 포함하면 많은 이점을 얻을 수 있습니다. 목록 3-5는 임베딩의 이점을 보여줍니다.
목록 3. 메소드 복사 코드 호출 코드는 다음과 같습니다.
int whenToEvaluateZing(int y){ return daysLeft(y)+ daysLeft(0)+ daysLeft(y+1);}
Listing 4. 호출된 메소드 복사 코드 코드는 다음과 같습니다.
int daysLeft(int x){ if(x ==0) return0; else return x -1;}
목록 5. 인라인 메소드 복사 코드 코드는 다음과 같습니다.
int whenToEvaluateZing(int y){
정수 온도 =0;
만약(y==0)
온도 +=0;
또 다른
온도 += y -1;
만약(0==0)
온도 +=0;
또 다른
온도 +=0-1;
if(y+1==0)
온도 +=0;
또 다른
온도 +=(y +1)-1;
복귀온도;
}
Listing 3-5에서 우리는 다른 메소드 본문에서 작은 메소드가 세 번 호출되는 것을 볼 수 있으며, 우리가 설명하고 싶은 것은 호출된 메소드를 코드에 직접 삽입하는 비용이 세 번의 점프를 실행하는 것보다 적다는 것입니다. 지시사항을 전달합니다.
자주 호출되지 않는 메소드를 내장하면 큰 차이가 없을 수도 있지만 소위 "핫" 메소드(자주 호출되는 메소드)를 내장하면 많은 성능 향상을 가져올 수 있습니다. Listing 6에 표시된 것처럼 임베디드 코드를 더욱 최적화할 수 있는 경우가 많습니다.
목록 6. 코드를 삽입한 후 다음과 같이 코드를 복사하여 추가 최적화를 달성할 수 있습니다.
int whenToEvaluateZing(int y){ if(y ==0)return y; elseif(y ==-1)return y -1; elsereturn y + y -1;
루프 최적화
루프 최적화는 루프 본문을 실행하는 데 드는 추가 비용을 줄이는 데 중요한 역할을 합니다. 여기서 추가 비용은 비용이 많이 드는 점프, 많은 조건 확인 및 최적화되지 않은 파이프라인(즉, 실제 작업을 수행하지 않고 추가 CPU 주기를 소비하는 일련의 명령 세트)을 의미합니다. 루프 최적화에는 다양한 유형이 있습니다. 다음은 가장 널리 사용되는 루프 최적화 중 일부입니다.
루프 본문 병합: 두 개의 인접한 루프 본문이 동일한 수의 루프를 실행하면 컴파일러는 두 루프 본문을 병합하려고 시도합니다. 두 개의 루프 본문이 서로 완전히 독립적인 경우 동시에(병렬로) 실행될 수도 있습니다.
반전 루프: 가장 기본적으로 while 루프를 do-while 루프로 대체합니다. 이 do-while 루프는 if 문 안에 배치됩니다. 이렇게 교체하면 두 번의 점프 작업이 줄어들지만 조건부 판단이 늘어 코드의 양이 늘어납니다. 이러한 종류의 최적화는 보다 효율적인 코드를 위해 더 많은 리소스를 교환하는 좋은 예입니다. 컴파일러는 비용과 이점을 고려하여 런타임에 동적으로 결정을 내립니다.
루프 본문 재구성: 전체 루프 본문을 캐시에 저장할 수 있도록 루프 본문을 재구성합니다.
루프 본문 확장: 루프 조건 확인 및 점프 횟수를 줄입니다. 조건부 검사를 수행할 필요 없이 "인라인"으로 여러 반복을 실행하는 것으로 생각할 수 있습니다. 루프 본문을 펼치면 파이프라인과 다수의 중복 명령 가져오기에 영향을 미쳐 성능이 저하될 수 있으므로 특정 위험도 발생합니다. 다시 한 번 말하지만, 런타임 시 루프 본문을 언롤링할지 여부를 결정하는 것은 컴파일러의 몫이며, 성능이 더 크게 향상된다면 언롤링할 가치가 있습니다.
위는 바이트코드 수준(또는 하위 수준)의 컴파일러가 대상 플랫폼에서 애플리케이션의 성능을 향상시킬 수 있는 방법에 대한 개요입니다. 우리가 논의한 내용은 몇 가지 일반적이고 널리 사용되는 최적화 방법입니다. 공간이 제한되어 있으므로 몇 가지 간단한 예만 제시합니다. 우리의 목표는 위의 간단한 토론을 통해 최적화에 대한 심층적인 연구에 대한 관심을 불러일으키는 것입니다.
결론: 반성사항 및 핵심사항
다양한 목적에 따라 다양한 컴파일러를 선택하십시오.
1. 인터프리터는 바이트 코드를 기계 명령어로 변환하는 가장 간단한 형태입니다. 구현은 명령어 조회 테이블을 기반으로 합니다.
2. 컴파일러는 성능 카운터를 기반으로 최적화할 수 있지만 일부 추가 리소스(코드 캐시, 최적화 스레드 등)를 소비해야 합니다.
3. 클라이언트 컴파일러는 인터프리터에 비해 5~10배의 성능 향상을 가져올 수 있습니다.
4. 서버측 컴파일러는 클라이언트측 컴파일러에 비해 약 30~50%의 성능 향상을 가져올 수 있지만 더 많은 리소스가 필요합니다.
5. 다층 컴파일은 두 가지의 장점을 결합합니다. 더 빠른 응답 시간을 위해 클라이언트 측 컴파일을 사용한 다음, 자주 호출되는 코드를 최적화하려면 서버 측 컴파일러를 사용하십시오.
여기에서 코드를 최적화하는 방법에는 여러 가지가 있습니다. 컴파일러의 중요한 임무는 가능한 모든 최적화 방법을 분석한 다음 최종 기계 명령으로 인한 성능 향상과 다양한 최적화 방법의 비용을 비교하는 것입니다.