Methode Inlining in de JVM

1. Inleiding

In deze zelfstudie bekijken we welke methode inlining is in de Java Virtual Machine en hoe deze werkt.

We zullen ook zien hoe we de informatie met betrekking tot inlining van de JVM kunnen krijgen en lezen en wat we met deze informatie kunnen doen om onze code te optimaliseren.

2. Welke methode inlining is?

Eigenlijk, inlining is een manier om de gecompileerde broncode tijdens runtime te optimaliseren door de aanroepen van de meest uitgevoerde methoden te vervangen door de body.

Hoewel er sprake is van compilatie, wordt deze niet uitgevoerd door de traditionele Javac compiler, maar door de JVM zelf. Om preciezer te zijn, het is de verantwoordelijkheid van de Just-In-Time (JIT) -compiler, dat deel uitmaakt van de JVM; Javac produceert alleen een bytecode en laat JIT de magie doen en de broncode optimaliseren.

Een van de belangrijkste consequenties van deze aanpak is dat als we de code compileren met oud Java, hetzelfde geldt.klasse bestand zal sneller zijn op nieuwere JVM's. Op deze manier hoeven we de broncode niet opnieuw te compileren, maar alleen Java te updaten.

3. Hoe JIT doet het?

In wezen is het De JIT-compiler probeert de methoden die we vaak aanroepen in lijn te brengen, zodat we de overhead van een methode-aanroep kunnen vermijden. Er worden twee dingen in overweging genomen bij de beslissing om een ​​methode al dan niet te integreren.

Ten eerste gebruikt het tellers om bij te houden hoe vaak we de methode aanroepen. Als de methode meer dan een bepaald aantal keren wordt aangeroepen, wordt deze "hot". Deze drempel is standaard ingesteld op 10.000, maar we kunnen deze configureren via de JVM-vlag tijdens het opstarten van Java. We willen zeker niet alles inline maken, omdat het tijdrovend zou zijn en een enorme bytecode zou produceren.

We moeten in gedachten houden dat inlining alleen plaatsvindt als we een stabiele toestand bereiken. Dit betekent dat we de uitvoering meerdere keren moeten herhalen om voldoende profileringsinformatie voor de JIT-compiler te leveren.

Bovendien is "hot" zijn geen garantie dat de methode inline zal zijn. Als het te groot is, zal het JIT het niet inlijnen. De acceptabele grootte wordt beperkt door de -XX: FreqInlineSize = vlag, die het maximale aantal bytecode-instructies specificeert dat inline voor een methode moet worden opgenomen.

Desalniettemin wordt het ten zeerste aanbevolen om de standaardwaarde van deze vlag niet te wijzigen, tenzij we absoluut zeker weten welke impact deze kan hebben. De standaardwaarde is afhankelijk van het platform - voor 64-bits Linux is dit 325.

Het JIT inline statisch, privaat, of laatste methoden in het algemeen. En terwijl openbaar methoden zijn ook kandidaten voor inline, niet elke openbare methode zal noodzakelijkerwijs inline zijn. De JVM moet bepalen dat er slechts één implementatie van een dergelijke methode is. Elke extra subklasse zou inlining voorkomen en de prestatie zal onvermijdelijk afnemen.

4. Het vinden van populaire methoden

We willen zeker niet raden wat het JIT doet. Daarom hebben we een manier nodig om te zien welke methoden al dan niet inline zijn. We kunnen dit gemakkelijk bereiken en al deze informatie in de standaarduitvoer loggen door tijdens het opstarten enkele extra JVM-vlaggen in te stellen:

-XX: + PrintCompilation -XX: + UnlockDiagnosticVMOptions -XX: + PrintInlining

De eerste vlag zal loggen wanneer JIT-compilatie plaatsvindt. De tweede vlag maakt extra vlaggen mogelijk, waaronder -XX: + PrintInlining, die zal afdrukken welke methoden worden geïntegreerd en waar.

Dit laat ons de inline methoden zien in de vorm van een boom. De bladeren zijn geannoteerd en gemarkeerd met een van de volgende opties:

  • inline (heet) - deze methode is gemarkeerd als hot en is inline
  • te groot - de methode is niet hot, maar ook de gegenereerde bytecode is te groot, dus het is niet inline
  • hete methode te groot - dit is een populaire methode, maar deze is niet inline omdat de bytecode te groot is

We moeten aandacht besteden aan de derde waarde en proberen methoden te optimaliseren met het label "hete methode te groot".

Als we een populaire methode vinden met een zeer complexe voorwaardelijke instructie, moeten we in het algemeen proberen de inhoud van de als-statement en vergroot de granulariteit zodat het JIT de code kan optimaliseren. Hetzelfde geldt voor de schakelaar en voor-loop-instructies.

We kunnen concluderen dat een handmatige methode inlining iets is dat we niet hoeven te doen om onze code te optimaliseren. De JVM doet het efficiënter, en we zouden de code mogelijk lang en moeilijk te volgen maken.

4.1. Voorbeeld

Laten we nu kijken hoe we dit in de praktijk kunnen controleren. We maken eerst een eenvoudige klasse die de som van de eerste berekent N opeenvolgende positieve gehele getallen:

openbare klasse ConsecutiveNumbersSum {privé long totalSum; private int totalNumbers; openbare ConsecutiveNumbersSum (int totalNumbers) {this.totalNumbers = totalNumbers; } openbaar lang getTotalSum () {totalSum = 0; voor (int i = 0; i <totalNumbers; i ++) {totalSum + = i; } retourneer totalSum; }}

Vervolgens maakt een eenvoudige methode gebruik van de klasse om de berekening uit te voeren:

privé statisch lang berekenSom (int n) {retourneer nieuwe ConsecutiveNumbersSum (n) .getTotalSum (); }

Ten slotte noemen we de methode een aantal keren en kijken wat er gebeurt:

voor (int i = 1; i <NUMBERS_OF_ITERATIONS; i ++) {berekenSom (i); }

In de eerste run gaan we het 1.000 keer draaien (minder dan de hierboven genoemde drempelwaarde van 10.000). Als we de uitvoer zoeken naar de berekenenSom () methode, we zullen het niet vinden. Dit wordt verwacht omdat we het niet vaak genoeg hebben gebeld.

Als we nu het aantal iteraties wijzigen in 15.000 en de uitvoer opnieuw doorzoeken, zullen we zien:

664262% com.baeldung.inlining.InliningExample :: main @ 2 (21 bytes) @ 10 com.baeldung.inlining.InliningExample :: berekenSum (12 bytes) inline (hot)

We kunnen zien dat de methode deze keer voldoet aan de voorwaarden voor inlining en dat de JVM deze heeft geintegreerd.

Het is opmerkelijk om nogmaals te vermelden dat als de methode te groot is, de JIT deze niet inline zal plaatsen, ongeacht het aantal iteraties. We kunnen dit controleren door een andere vlag toe te voegen tijdens het uitvoeren van de applicatie:

-XX: FreqInlineSize = 10

Zoals we in de vorige uitvoer kunnen zien, is de grootte van onze methode 12 bytes. De -XX:FreqInlineSize flag beperkt de methodegrootte die in aanmerking komt voor inlining tot 10 bytes. Bijgevolg zou de inlining deze keer niet moeten plaatsvinden. En inderdaad, we kunnen dit bevestigen door nog een keer naar de output te kijken:

330266% com.baeldung.inlining.InliningExample :: main @ 2 (21 bytes) @ 10 com.baeldung.inlining.InliningExample :: berekenSum (12 bytes) hete methode te groot

Hoewel we de vlagwaarde hier ter illustratie hebben gewijzigd, moeten we de aanbeveling benadrukken om de standaardwaarde van de -XX: FreqInlineSize vlag tenzij absoluut noodzakelijk.

5. Conclusie

In dit artikel hebben we gezien welke methode inlining is in de JVM en hoe het JIT dit doet. We hebben beschreven hoe we kunnen controleren of onze methoden in aanmerking komen voor inlinen of niet, en hebben voorgesteld hoe we deze informatie kunnen gebruiken door te proberen de omvang van vaak genoemde lange methoden die te groot zijn om inline te krijgen, te verkleinen.

Ten slotte hebben we geïllustreerd hoe we een hete methode in de praktijk kunnen identificeren.

Alle codefragmenten die in het artikel worden genoemd, zijn te vinden in onze GitHub-repository.