Method Inlining nella JVM

1. Introduzione

In questo tutorial, daremo un'occhiata a quale metodo è inlining nella Java Virtual Machine e come funziona.

Vedremo anche come ottenere e leggere le informazioni relative all'inlining dalla JVM e cosa possiamo fare con queste informazioni per ottimizzare il nostro codice.

2. Qual è il metodo inlining?

Fondamentalmente, l' inlining è un modo per ottimizzare il codice sorgente compilato in fase di esecuzione sostituendo le invocazioni dei metodi più spesso eseguiti con i suoi corpi.

Sebbene sia coinvolta la compilazione, non viene eseguita dal compilatore javac tradizionale , ma dalla stessa JVM. Per essere più precisi, è responsabilità del compilatore Just-In-Time (JIT) , che fa parte della JVM; javac produce solo un bytecode e lascia che JIT faccia la magia e ottimizzi il codice sorgente.

Una delle conseguenze più importanti di questo approccio è che se compiliamo il codice usando il vecchio Java, lo stesso. Il file class sarà più veloce sulle JVM più recenti. In questo modo non è necessario ricompilare il codice sorgente, ma solo aggiornare Java.

3. Come funziona JIT?

In sostanza, il compilatore JIT cerca di inline i metodi che spesso chiamiamo in modo da evitare l'overhead di una chiamata di metodo . Prende in considerazione due cose quando si decide se incorporare un metodo o meno.

Innanzitutto, utilizza i contatori per tenere traccia di quante volte invochiamo il metodo. Quando il metodo viene chiamato più di un numero specifico di volte, diventa "caldo". Questa soglia è impostata su 10.000 per impostazione predefinita, ma possiamo configurarla tramite il flag JVM durante l'avvio di Java. Sicuramente non vogliamo inline tutto poiché richiederebbe molto tempo e produrrebbe un enorme bytecode.

Dobbiamo tenere presente che l'inlining avrà luogo solo quando arriveremo a uno stato stabile. Ciò significa che avremo bisogno di ripetere l'esecuzione più volte per fornire informazioni di profilazione sufficienti per il compilatore JIT.

Inoltre, essere "caldo" non garantisce che il metodo sarà inline. Se è troppo grande, il JIT non lo inlineerà. La dimensione accettabile è limitata dal flag -XX: FreqInlineSize = , che specifica il numero massimo di istruzioni bytecode da inserire inline per un metodo.

Tuttavia, si consiglia vivamente di non modificare il valore predefinito di questo flag a meno che non siamo assolutamente certi di sapere quale impatto potrebbe avere. Il valore predefinito dipende dalla piattaforma: per Linux a 64 bit, è 325.

Il JIT inline statico , private , o finali metodi in generale . E mentre i metodi pubblici sono anche candidati per l'inlining, non tutti i metodi pubblici saranno necessariamente inline. La JVM deve determinare che esiste solo una singola implementazione di tale metodo . Qualsiasi sottoclasse aggiuntiva impedirebbe l'inlining e le prestazioni diminuiranno inevitabilmente.

4. Trovare metodi caldi

Sicuramente non vogliamo indovinare cosa sta facendo la JIT. Pertanto, abbiamo bisogno di un modo per vedere quali metodi sono inline o non inline. Possiamo facilmente raggiungere questo obiettivo e registrare tutte queste informazioni nell'output standard impostando alcuni flag JVM aggiuntivi durante l'avvio:

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

Il primo flag verrà registrato quando avviene la compilazione JIT. Il secondo flag abilita flag aggiuntivi tra cui -XX: + PrintInlining , che stamperà quali metodi vengono inseriti e dove.

Questo ci mostrerà i metodi inline sotto forma di un albero. Le foglie sono annotate e contrassegnate con una delle seguenti opzioni:

  • inline (hot) : questo metodo è contrassegnato come hot ed è inline
  • troppo grande : il metodo non è caldo, ma anche il bytecode generato è troppo grande, quindi non è inline
  • metodo a caldo troppo grande : questo è un metodo a caldo, ma non è inline poiché il bytecode è troppo grande

Dovremmo prestare attenzione al terzo valore e cercare di ottimizzare i metodi con l'etichetta "metodo a caldo troppo grande".

Generalmente, se troviamo un metodo caldo con un'istruzione condizionale molto complessa, dovremmo provare a separare il contenuto dell'istruzione if e aumentare la granularità in modo che JIT possa ottimizzare il codice. Lo stesso vale per l' interruttore e per- istruzioni cicliche.

Possiamo concludere che un metodo manuale inlining è qualcosa che non dobbiamo fare per ottimizzare il nostro codice. La JVM lo fa in modo più efficiente e probabilmente renderemmo il codice lungo e difficile da seguire.

4.1. Esempio

Vediamo ora come possiamo verificarlo in pratica. Creeremo prima una classe semplice che calcola la somma dei primi N numeri interi positivi consecutivi:

public class ConsecutiveNumbersSum { private long totalSum; private int totalNumbers; public ConsecutiveNumbersSum(int totalNumbers) { this.totalNumbers = totalNumbers; } public long getTotalSum() { totalSum = 0; for (int i = 0; i < totalNumbers; i++) { totalSum += i; } return totalSum; } }

Successivamente, un semplice metodo utilizzerà la classe per eseguire il calcolo:

private static long calculateSum(int n) { return new ConsecutiveNumbersSum(n).getTotalSum(); }

Infine, chiameremo il metodo un numero diverso di volte e vedremo cosa succede:

for (int i = 1; i < NUMBERS_OF_ITERATIONS; i++) { calculateSum(i); }

Nella prima esecuzione, eseguiremo 1.000 volte (inferiore al valore di soglia di 10.000 menzionato sopra). Se cerchiamo l'output per il metodo calcolaSum () , non lo troveremo. Questo è previsto poiché non l'abbiamo chiamato abbastanza volte.

Se ora cambiamo il numero di iterazioni a 15.000 e cerchiamo di nuovo l'output, vedremo:

664 262 % com.baeldung.inlining.InliningExample::main @ 2 (21 bytes) @ 10 com.baeldung.inlining.InliningExample::calculateSum (12 bytes) inline (hot)

Possiamo vedere che questa volta il metodo soddisfa le condizioni per l'inlining e la JVM lo ha inserito.

È degno di nota menzionare ancora che se il metodo è troppo grande, il JIT non lo inlineerà, indipendentemente dal numero di iterazioni. Possiamo verificarlo aggiungendo un altro flag durante l'esecuzione dell'applicazione:

-XX:FreqInlineSize=10

Come possiamo vedere nell'output precedente, la dimensione del nostro metodo è di 12 byte. Il flag -XX: FreqInlineSize limiterà la dimensione del metodo idonea per l'inlining a 10 byte. Di conseguenza, questa volta l'inlining non dovrebbe avvenire. E in effetti, possiamo confermarlo dando un'altra occhiata all'output:

330 266 % com.baeldung.inlining.InliningExample::main @ 2 (21 bytes) @ 10 com.baeldung.inlining.InliningExample::calculateSum (12 bytes) hot method too big

Sebbene abbiamo modificato il valore del flag qui a scopo illustrativo, dobbiamo sottolineare la raccomandazione di non modificare il valore predefinito del flag -XX: FreqInlineSize a meno che non sia assolutamente necessario.

5. conclusione

In questo articolo, abbiamo visto qual è il metodo inlining nella JVM e come lo fa JIT. Abbiamo descritto come possiamo verificare se i nostri metodi sono idonei per l'inlining o meno e abbiamo suggerito come utilizzare queste informazioni cercando di ridurre la dimensione dei metodi lunghi chiamati spesso che sono troppo grandi per essere incorporati.

Infine, abbiamo illustrato come identificare in pratica un metodo a caldo.

Tutti gli snippet di codice menzionati nell'articolo possono essere trovati nel nostro repository GitHub.