Ahead of Time-compilatie (AoT)

1. Inleiding

In dit artikel zullen we kijken naar de Java Ahead of Time (AOT) Compiler, die wordt beschreven in JEP-295 en is toegevoegd als een experimentele functie in Java 9.

Ten eerste zullen we zien wat AOT is, en ten tweede zullen we naar een eenvoudig voorbeeld kijken. Ten derde zullen we enkele beperkingen van AOT zien, en ten slotte zullen we enkele mogelijke use-cases bespreken.

2. Wat is een compilatie vóór de tijd?

AOT-compilatie is een manier om de prestaties van Java-programma's en in het bijzonder de opstarttijd van de JVM te verbeteren. De JVM voert Java-bytecode uit en compileert vaak uitgevoerde code naar native code. Dit wordt Just-in-Time (JIT) -compilatie genoemd. De JVM beslist welke code JIT compileert op basis van profileringsinformatie die tijdens de uitvoering wordt verzameld.

Hoewel deze techniek de JVM in staat stelt sterk geoptimaliseerde code te produceren en de piekprestaties verbetert, is de opstarttijd waarschijnlijk niet optimaal, aangezien de uitgevoerde code nog niet JIT is gecompileerd. AOT streeft ernaar deze zogenaamde opwarmperiode te verbeteren. De compiler die voor AOT wordt gebruikt, is Graal.

In dit artikel zullen we JIT en Graal niet in detail bekijken. Raadpleeg onze andere artikelen voor een overzicht van prestatieverbeteringen in Java 9 en 10, evenals een diepe duik in de Graal JIT-compiler.

3. Voorbeeld

Voor dit voorbeeld gebruiken we een heel eenvoudige klasse, compileren we deze en kijken we hoe we de resulterende bibliotheek kunnen gebruiken.

3.1. AOT-compilatie

Laten we een korte blik werpen op onze voorbeeldklasse:

openbare klasse JaotCompilation {openbare statische leegte hoofd (String [] argv) {System.out.println (bericht ()); } public static String message () {return "De JAOT-compiler zegt 'Hallo'"; }} 

Voordat we de AOT-compiler kunnen gebruiken, moeten we de klasse compileren met de Java-compiler:

javac JaotCompilation.java 

We passeren dan het resultaat JaotCompilation.class naar de AOT-compiler, die zich in dezelfde map bevindt als de standaard Java-compiler:

jaotc --output jaotCompilation.so JaotCompilation.class 

Dit levert de bibliotheek op jaotCompilation.so in de huidige directory.

3.2. Het programma uitvoeren

We kunnen dan het programma uitvoeren:

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

Het argument -XX: AOTLibrary accepteert een relatief of volledig pad naar de bibliotheek. Als alternatief kunnen we de bibliotheek kopiëren naar het lib map in de Java-homedirectory en geef alleen de naam van de bibliotheek door.

3.3. Controleren of de bibliotheek wordt gebeld en gebruikt

We kunnen zien dat de bibliotheek inderdaad is geladen door toe te voegen -XX: + AfdrukkenAOT als een JVM-argument:

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

De uitvoer ziet er als volgt uit:

77 1 geladen ./jaotCompilation.so aot bibliotheek 

Dit vertelt ons echter alleen dat de bibliotheek is geladen, maar niet dat deze daadwerkelijk is gebruikt. Door het argument door te geven -verbosekunnen we zien dat de methoden in de bibliotheek inderdaad worden genoemd:

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

De uitvoer bevat de regels:

11 1 geladen ./jaotCompilation.so aot bibliotheek 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 De JAOT-compiler zegt 'Hallo' 

De door AOT samengestelde bibliotheek bevat een klasse vingerafdruk, die moet overeenkomen met de vingerafdruk van het .klasse het dossier.

Laten we de code in de klas veranderen JaotCompilation.java om een ​​ander bericht terug te sturen:

public static String message () {return "De JAOT-compiler zegt 'Good morning'"; } 

Als we het programma uitvoeren zonder AOT de gewijzigde klasse te compileren:

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

De uitvoer bevat dan alleen:

 11 1 geladen ./jaotCompilation.so aot bibliotheek De JAOT-compiler zegt 'Goedemorgen'

We kunnen zien dat de methoden in de bibliotheek niet worden aangeroepen, omdat de bytecode van de klasse is gewijzigd. Het idee hierachter is dat het programma altijd hetzelfde resultaat zal opleveren, ongeacht of een door AOT gecompileerde bibliotheek is geladen of niet.

4. Meer AOT- en JVM-argumenten

4.1. AOT Compilatie van Java-modules

Het is ook mogelijk om AOT een module te compileren:

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

De resulterende bibliotheek javaBase.so is ongeveer 320 MB groot en het laden duurt even. De grootte kan worden verkleind door de pakketten en klassen te selecteren die AOT moeten worden gecompileerd.

We zullen hieronder bekijken hoe we dat moeten doen, maar we zullen niet diep op alle details ingaan.

4.2. Selectieve compilatie met compileeropdrachten

Om te voorkomen dat de door AOT gecompileerde bibliotheek van een Java-module te groot wordt, kunnen we compileeropdrachten toevoegen om de reikwijdte te beperken van wat AOT gecompileerd krijgt. Deze opdrachten moeten in een tekstbestand staan ​​- in ons voorbeeld gebruiken we het bestand complileCommands.txt:

compileOnly java.lang. *

Vervolgens voegen we het toe aan het compileercommando:

jaotc --output javaBaseLang.so --module java.base --compile-opdrachten compileCommands.txt 

De resulterende bibliotheek bevat alleen de AOT-gecompileerde klassen in de pakket java.lang.

Om echte prestatieverbetering te krijgen, moeten we uitzoeken welke klassen worden aangeroepen tijdens de warming-up van de JVM.

Dit kan worden bereikt door verschillende JVM-argumenten toe te voegen:

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

In dit artikel gaan we niet dieper in op deze techniek.

4.3. AOT Compilatie van een enkele klas

We kunnen een enkele klasse compileren met het argument -naam van de klasse:

jaotc --output javaBaseString.so --klassenaam java.lang.String 

De resulterende bibliotheek bevat alleen de klasse Draad.

4.4. Compileren voor Tiered

Standaard wordt de AOT-gecompileerde code altijd gebruikt en vindt er geen JIT-compilatie plaats voor de klassen die in de bibliotheek zijn opgenomen. Als we de profielinformatie in de bibliotheek willen opnemen, kunnen we het argument toevoegen compileren voor tiered:

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

De vooraf gecompileerde code in de bibliotheek wordt gebruikt totdat de bytecode in aanmerking komt voor JIT-compilatie.

5. Mogelijke use cases voor AOT-compilatie

Een use case voor AOT zijn kortlopende programma's, die de uitvoering voltooien voordat er een JIT-compilatie plaatsvindt.

Een andere use-case zijn embedded omgevingen, waar JIT niet mogelijk is.

Op dit punt moeten we ook opmerken dat de AOT-gecompileerde bibliotheek alleen kan worden geladen vanuit een Java-klasse met identieke bytecode, en dus niet kan worden geladen via JNI.

6. AOT en Amazon Lambda

Een mogelijke use case voor AOT-gecompileerde code zijn kortstondige lambda-functies waarbij een korte opstarttijd belangrijk is. In deze sectie zullen we bekijken hoe we AOT gecompileerde Java-code kunnen draaien op AWS Lambda.

Het gebruik van AOT-compilatie met AWS Lambda vereist dat de bibliotheek wordt gebouwd op een besturingssysteem dat compatibel is met het besturingssysteem dat wordt gebruikt op AWS. Op het moment van schrijven is dit het geval Amazon Linux 2.

Bovendien moet de Java-versie overeenkomen. AWS biedt de Amazon Corretto Java 11 JVM. Om een ​​omgeving te hebben om onze bibliotheek te compileren, zullen we installeren Amazon Linux 2 en Amazon Corretto in Docker.

We zullen niet alle details van het gebruik van Docker en AWS Lambda bespreken, maar alleen de belangrijkste stappen beschrijven. Raadpleeg hier de officiële documentatie voor meer informatie over het gebruik van Docker.

Voor meer details over het maken van een Lambda-functie met Java, kun je ons artikel AWS Lambda With Java bekijken.

6.1. Configuratie van onze ontwikkelomgeving

Eerst moeten we de Docker-afbeelding ophalen voor Amazon Linux 2 en installeer Amazon Corretto:

# download Amazon Linux docker pull amazonlinux # in de Docker-container, installeer Amazon Corretto yum installeer java-11-amazon-corretto # enkele extra bibliotheken nodig voor jaotc yum installeer binutils.x86_64 

6.2. Compileer de klas en bibliotheek

In onze Docker-container voeren we de volgende opdrachten uit:

# maak map aot mkdir aot cd aot mkdir jaotc cd jaotc

De naam van de map is slechts een voorbeeld en kan natuurlijk elke andere naam zijn.

pakket jaotc; openbare klasse JaotCompilation {openbaar statisch int bericht (int invoer) {retour invoer * 2; }}

De volgende stap is om de klasse en bibliotheek te compileren:

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

Hier is het belangrijk om dezelfde garbage collector te gebruiken als op AWS. Als onze bibliotheek niet op AWS Lambda kan worden geladen, willen we misschien controleren welke garbage collector daadwerkelijk wordt gebruikt met het volgende commando:

java -XX: + PrintCommandLineFlags -versie

Nu kunnen we een zip-bestand maken dat onze bibliotheek en klassenbestand bevat:

zip -r jaot.zip jaotCompilation.so jaotc /

6.3. Configureer AWS Lambda

De laatste stap is om in te loggen op de AWS Lamda-console, het zip-bestand te uploaden en Lambda te configureren met de volgende parameters:

  • Looptijd: Java 11
  • Verwerker: jaotc.JaotCompilation :: bericht

Verder moeten we een omgevingsvariabele maken met de naam JAVA_TOOL_OPTIONS en de waarde ervan instellen op:

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

Met deze variabele kunnen we parameters doorgeven aan de JVM.

De laatste stap is het configureren van de invoer voor onze Lambda. De standaard is een JSON-invoer, die niet kan worden doorgegeven aan onze functie, daarom moeten we deze instellen op een String die een geheel getal bevat, bijv. "1".

Ten slotte kunnen we onze Lambda-functie uitvoeren en in het logboek zien dat onze AOT-gecompileerde bibliotheek is geladen:

57 1 geladen ./jaotCompilation.so aot bibliotheek

7. Conclusie

In dit artikel hebben we gezien hoe je via AOT Java-klassen en -modules compileert. Omdat dit nog een experimentele functie is, maakt de AOT-compiler niet deel uit van alle distributies. Echte voorbeelden zijn nog steeds zeldzaam en het is aan de Java-gemeenschap om de beste use-cases te vinden voor het toepassen van AOT.

Alle codefragmenten in dit artikel zijn te vinden in onze GitHub-repository.


$config[zx-auto] not found$config[zx-overlay] not found