Ahead of Time Compilation (AoT)

1. Introduktion

I denne artikel ser vi på Java Ahead of Compiler (AOT) Compiler, som er beskrevet i JEP-295 og blev tilføjet som en eksperimentel funktion i Java 9.

Først ser vi, hvad AOT er, og for det andet ser vi på et simpelt eksempel. For det tredje vil vi se nogle begrænsninger for AOT, og til sidst vil vi diskutere nogle mulige brugssager.

2. Hvad ligger foran tidssammensætningen?

AOT-kompilering er en måde at forbedre ydeevnen på Java-programmer på og især opstartstiden for JVM. JVM udfører Java bytecode og kompilerer ofte udført kode til native kode. Dette kaldes Just-in-Time (JIT) Compilation. JVM bestemmer hvilken kode JIT skal kompilere baseret på profiloplysninger indsamlet under udførelse.

Mens denne teknik gør det muligt for JVM at producere stærkt optimeret kode og forbedrer peak-ydeevne, er opstartstiden sandsynligvis ikke optimal, da den udførte kode endnu ikke er JIT-kompileret. AOT sigter mod at forbedre denne såkaldte opvarmningsperiode. Compileren, der bruges til AOT, er Graal.

I denne artikel vil vi ikke se på JIT og Graal i detaljer. Se vores andre artikler for en oversigt over ydeevne forbedringer i Java 9 og 10 samt et dybt dyk i Graal JIT Compiler.

3. Eksempel

I dette eksempel bruger vi en meget enkel klasse, kompilerer den og ser, hvordan man bruger det resulterende bibliotek.

3.1. AOT kompilering

Lad os se hurtigt på vores prøveklasse:

offentlig klasse JaotCompilation {public static void main (String [] argv) {System.out.println (message ()); } offentlig statisk strengmeddelelse () {return "JAOT-kompilatoren siger 'Hej'"; }} 

Inden vi kan bruge AOT-kompilatoren, skal vi kompilere klassen med Java-kompilatoren:

javac JaotCompilation.java 

Vi videregiver derefter det resulterende JaotCompilation.class til AOT-kompilatoren, som er placeret i samme bibliotek som standard Java-kompilatoren:

jaotc - output jaotCompilation.so JaotCompilation.class 

Dette producerer biblioteket jaotCompilation.so i det aktuelle bibliotek.

3.2. Kørsel af programmet

Vi kan derefter udføre programmet:

java -XX: AOTLibrary =. / jaotCompilation.so JaotCompilation 

Argumentet -XX: AOTBibliotek accepterer en relativ eller fuld sti til biblioteket. Alternativt kan vi kopiere biblioteket til lib mappen i Java-hjemmekataloget og kun videregive navnet på biblioteket.

3.3. Bekræftelse af, at biblioteket kaldes og bruges

Vi kan se, at biblioteket faktisk blev indlæst ved at tilføje -XX: + PrintAOT som et JVM-argument:

java -XX: + PrintAOT -XX: AOTLibrary =. / jaotCompilation.so JaotCompilation 

Outputtet ser ud som:

77 1 indlæst ./jaotCompilation.so et bibliotek 

Dette fortæller os kun, at biblioteket blev indlæst, men ikke at det faktisk blev brugt. Ved at videregive argumentet -ordrig, kan vi se, at metoderne i biblioteket faktisk kaldes:

java -XX: AOTLibrary =. / jaotCompilation.so -verbose -XX: + PrintAOT JaotCompilation 

Outputtet indeholder linjerne:

11 1 indlæst ./jaotCompilation.so aot-bibliotek 116 1 aot [1] jaotc.JaotCompilation. () V 116 2 aot [1] jaotc.JaotCompilation.message () Ljava / lang / String; 116 3 aot [1] jaotc.JaotCompilation.main ([Ljava / lang / String;) V JAOT-kompilatoren siger 'Hej' 

Det AOT-sammensatte bibliotek indeholder en klasse fingeraftryk, som skal matche fingeraftrykket på .klasse fil.

Lad os ændre koden i klassen JaotCompilation.java for at returnere en anden besked:

offentlig statisk strengmeddelelse () {return "JAOT-kompilatoren siger 'God morgen'"; } 

Hvis vi udfører programmet uden AOT at sammensætte den ændrede klasse:

java -XX: AOTLibrary =. / jaotCompilation.so -verbose -XX: + PrintAOT JaotCompilation 

Derefter indeholder output kun:

 11 1 indlæst ./jaotCompilation.so et bibliotek JAOT-kompilatoren siger 'God morgen'

Vi kan se, at metoderne i biblioteket ikke kaldes, da klassens bytecode er ændret. Ideen bag dette er, at programmet altid vil give det samme resultat, uanset om et AOT-kompileret bibliotek er indlæst eller ej.

4. Flere AOT- og JVM-argumenter

4.1. AOT-kompilering af Java-moduler

Det er også muligt at AOT kompilere et modul:

jaotc --output javaBase.so --modul java.base 

Det resulterende bibliotek javaBase.so er cirka 320 MB i størrelse og tager noget tid at indlæse. Størrelsen kan reduceres ved at vælge de pakker og klasser, der skal kompileres AOT.

Vi ser på, hvordan man gør det nedenfor, men vi dykker ikke dybt ned i alle detaljerne.

4.2. Selektiv kompilering med kompileringskommandoer

For at forhindre, at AOT-kompileret bibliotek i et Java-modul bliver for stort, kan vi tilføje kompileringskommandoer for at begrænse omfanget af, hvad der bliver AOT-kompileret. Disse kommandoer skal være i en tekstfil - i vores eksempel bruger vi filen complileCommands.txt:

kompilér kun java.lang. *

Derefter føjer vi det til kompileringskommandoen:

jaotc --output javaBaseLang.so --modul java.base --compile-kommandoer compileCommands.txt 

Det resulterende bibliotek indeholder kun de AOT-kompilerede klasser i pakke java.lang.

For at opnå reel præstationsforbedring er vi nødt til at finde ud af, hvilke klasser der påberåbes under opvarmningen af ​​JVM.

Dette kan opnås ved at tilføje flere JVM-argumenter:

java -XX: + UnlockDiagnosticVMOptions -XX: + LogTouchedMethods -XX: + PrintTouchedMethodsAtExit JaotCompilation 

I denne artikel dykker vi ikke dybere ned i denne teknik.

4.3. AOT kompilering af en enkelt klasse

Vi kan sammensætte en enkelt klasse med argumentet –Klasse-navn:

jaotc --output javaBaseString.so - klasse-navn java.lang.String 

Det resulterende bibliotek indeholder kun klassen Snor.

4.4. Kompilér til Tiered

Som standard vil den AOT-kompilerede kode altid blive brugt, og der sker ingen JIT-kompilering for de klasser, der er inkluderet i biblioteket. Hvis vi vil inkludere profiloplysningerne i biblioteket, kan vi tilføje argumentet kompilere-til-niveauet:

jaotc --output jaotCompilation.so --compile-for-tiered JaotCompilation.class 

Den forud kompilerede kode i biblioteket vil blive brugt, indtil bytekoden bliver kvalificeret til JIT-kompilering.

5. Mulige anvendelsestilfælde til AOT-kompilering

En brugssag til AOT er kortkørende programmer, som afslutter udførelsen, før der opstår JIT-kompilering.

En anden brugssag er indlejrede miljøer, hvor JIT ikke er mulig.

På dette tidspunkt skal vi også bemærke, at det AOT-kompilerede bibliotek kun kan indlæses fra en Java-klasse med identisk bytekode, og det kan derfor ikke indlæses via JNI.

6. AOT og Amazon Lambda

En mulig brugstilfælde for AOT-kompileret kode er kortvarige lambda-funktioner, hvor kort opstartstid er vigtig. I dette afsnit ser vi på, hvordan vi kan køre AOT-kompileret Java-kode på AWS Lambda.

Brug af AOT-kompilering med AWS Lambda kræver, at biblioteket er bygget på et operativsystem, der er kompatibelt med det operativsystem, der bruges på AWS. I skrivende stund er dette Amazon Linux 2.

Desuden skal Java-versionen matche. AWS leverer Amazon Corretto Java 11 JVM. For at have et miljø til at kompilere vores bibliotek installerer vi Amazon Linux 2 og Amazon Corretto i Docker.

Vi diskuterer ikke alle detaljer ved brug af Docker og AWS Lambda, men skitserer kun de vigtigste trin. For mere information om, hvordan du bruger Docker, henvises til dens officielle dokumentation her.

For flere detaljer om oprettelse af en Lambda-funktion med Java, kan du se på vores artikel AWS Lambda With Java.

6.1. Konfiguration af vores udviklingsmiljø

Først skal vi trække Docker-billedet til Amazon Linux 2 og installer Amazon Corretto:

# download Amazon Linux-docker træk amazonlinux # inde i Docker-containeren, installer Amazon Corretto yum install java-11-amazon-corretto # nogle ekstra biblioteker nødvendige for jaotc yum install binutils.x86_64 

6.2. Kompilér klassen og biblioteket

Inde i vores Docker-container udfører vi følgende kommandoer:

# Opret mappe aot mkdir aot cd aot mkdir jaotc cd jaotc

Navnet på mappen er kun et eksempel og kan selvfølgelig være et hvilket som helst andet navn.

pakke jaotc; offentlig klasse JaotCompilation {public static int message (int input) {return input * 2; }}

Det næste trin er at kompilere klassen og biblioteket:

javac JaotCompilation.java cd .. jaotc -J-XX: + UseSerialGC --output jaotCompilation.so jaotc / JaotCompilation.class

Her er det vigtigt at bruge den samme affaldssamler, som bruges på AWS. Hvis vores bibliotek ikke kan indlæses på AWS Lambda, vil vi måske kontrollere, hvilken affaldssamler der faktisk bruges med følgende kommando:

java -XX: + PrintCommandLineFlags -version

Nu kan vi oprette en zip-fil, der indeholder vores bibliotek og klassefil:

zip -r jaot.zip jaotCompilation.so jaotc /

6.3. Konfigurer AWS Lambda

Det sidste trin er at logge ind på AWS Lamda-konsollen, uploade zip-filen og konfigurere Lambda med følgende parametre:

  • Kørselstid: Java 11
  • Handler: jaotc.JaotCompilation :: besked

Desuden er vi nødt til at oprette en miljøvariabel med navnet JAVA_TOOL_OPTIONS og indstille dens værdi til:

-XX: + UnlockExperimentalVMOptions -XX: + PrintAOT -XX: AOTLibrary =. / JaotCompilation.so

Denne variabel lader os videregive parametre til JVM.

Det sidste trin er at konfigurere input til vores Lambda. Standard er en JSON-indgang, som ikke kan overføres til vores funktion, derfor skal vi indstille den til en streng, der indeholder et heltal, f.eks. "1".

Endelig kan vi udføre vores Lambda-funktion og skulle se i loggen, at vores AOT-kompilerede bibliotek blev indlæst:

57 1 indlæst ./jaotCompilation.so et bibliotek

7. Konklusion

I denne artikel så vi, hvordan man AOT kompilerer Java-klasser og moduler. Da dette stadig er en eksperimentel funktion, er AOT-compileren ikke en del af alle distributioner. Reelle eksempler er stadig sjældne at finde, og det vil være op til Java-samfundet at finde de bedste anvendelsessager til anvendelse af AOT.

Alle kodestykker i denne artikel kan findes i vores GitHub-arkiv.