Il metodo / i in linea Java sarebbe durante l’ottimizzazione?

Mi chiedo se JVM / javac sia abbastanza intelligente da girare

// This line... string a = foo(); string foo() { return bar(); } string bar() { return some-complicated-string computation; } 

in

 string a = bar(); 

Oppure spoglia inutili chiamate a foo () in caso di rilascio (perché codice non raggiungibile):

 string a = foo(bar()); // bar is the same ... string foo(string b) { if (debug) do-something-with(b); } 

Il mio sentimento è sì per il primo esempio e “non così sicuro” per il secondo, ma qualcuno potrebbe darmi qualche suggerimento / link per confermarlo?

javac presenterà bytecode che è una rappresentazione fedele del programma Java originale che ha generato il bytecode (tranne in alcune situazioni in cui è ansible ottimizzare: eliminazione costante e eliminazione di codice morto ). Tuttavia, l’ottimizzazione può essere eseguita da JVM quando utilizza il compilatore JIT.

Per il primo scenario sembra che la JVM supporti l’inlining (si veda sotto Methods here e si veda qui per un esempio inlining sulla JVM).

Non sono riuscito a trovare alcun esempio del metodo di inlining eseguito da javac stesso. Ho provato a compilare alcuni programmi di esempio (simile a quello che hai descritto nella tua domanda) e nessuno di loro sembrava allineare direttamente il metodo anche quando era final . Sembrerebbe che questo tipo di ottimizzazioni sia fatto dal compilatore JIT della JVM e non da javac . Il “compilatore” menzionato sotto Methods sembra essere il compilatore JIT di JSM di HotSpot e non javac .

Da quello che vedo, javac supporta l’ eliminazione dei codici morti (vedi l’esempio per il secondo caso) e il piegamento costante . Nel piegamento costante, il compilatore calcolerà in anticipo le espressioni costanti e utilizzerà il valore calcolato anziché eseguire il calcolo durante il runtime. Per esempio:

 public class ConstantFolding { private static final int a = 100; private static final int b = 200; public final void baz() { int c = a + b; } } 

compila al seguente codice bytecode:

 Compiled from "ConstantFolding.java" public class ConstantFolding extends java.lang.Object{ private static final int a; private static final int b; public ConstantFolding(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return public final void baz(); Code: 0: sipush 300 3: istore_1 4: return } 

Si noti che il bytecode ha un sipush 300 invece dei aload di getfield e un iadd . 300 è il valore calcolato. Questo è anche il caso delle variabili private final . Se b non sono statici, il bytecode risultante sarà:

 Compiled from "ConstantFolding.java" public class ConstantFolding extends java.lang.Object{ private final int a; private final int b; public ConstantFolding(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: aload_0 5: bipush 100 7: putfield #2; //Field a:I 10: aload_0 11: sipush 200 14: putfield #3; //Field b:I 17: return public final void baz(); Code: 0: sipush 300 3: istore_1 4: return } 

Anche qui viene usato un sipush 300 .

Per il secondo caso (eliminazione del codice morto), ho usato il seguente programma di test:

 public class InlineTest { private static final boolean debug = false; private void baz() { if(debug) { String a = foo(); } } private String foo() { return bar(); } private String bar() { return "abc"; } } 

che fornisce il seguente codice bytecode:

 Compiled from "InlineTest.java" public class InlineTest extends java.lang.Object{ private static final boolean debug; public InlineTest(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return private void baz(); Code: 0: return private java.lang.String foo(); Code: 0: aload_0 1: invokespecial #2; //Method bar:()Ljava/lang/String; 4: areturn private java.lang.String bar(); Code: 0: ldc #3; //String abc 2: areturn } 

Come puoi vedere, il foo non viene chiamato affatto in baz perché il codice all’interno del blocco if è effettivamente “morto”.

HotSpot JVM di Sun (ora Oracle) combina l’interpretazione del bytecode e della compilazione JIT. Quando il codice byte viene presentato alla JVM, il codice viene inizialmente interpretato, ma la JVM controllerà il bytecode e individuerà le parti che vengono eseguite frequentemente. Copre queste parti in codice nativo in modo che funzionino più velocemente. Per una parte di bytecode che non viene utilizzata così frequentemente, questa compilazione non viene eseguita. Questo è altrettanto valido perché la compilazione ha un sovraccarico. Quindi è davvero una questione di compromesso. Se decidi di compilare tutti i bytecode in nativecode, il codice può avere un ritardo di avvio molto lungo.

Oltre a monitorare il bytecode, la JVM può anche eseguire analisi statiche del bytecode mentre lo interpreta e lo carica per eseguire ulteriori ottimizzazioni.

Se si desidera conoscere i tipi specifici di ottimizzazioni eseguite dalla JVM, questa pagina su Oracle è piuttosto utile. Descrive le tecniche di performance utilizzate in HotSpot JVM.

nello stesso file di class il javac sarà in grado di allineare static e final (altri file di class potrebbero cambiare la funzione inline)

tuttavia, la JIT sarà in grado di ottimizzare molto di più (inclusa l’inlining dei superflui limiti di rimozione – e assegni nulli, ecc.) perché sa di più sul codice

Un compilatore JIT “altamente ottimizzante” integrerà entrambi i casi (e, @Mysticial, potrebbe anche integrare alcuni casi polimorfici, utilizzando varie forms di inganno).

Puoi aumentare le possibilità di inlining rendendo i metodi finali e alcuni altri trucchi.

javac esegue alcuni inlining primitivi, principalmente di metodi final / private, principalmente intesi ad aiutare alcuni paradigmi di compilazione condizionale.

La JVM sarà molto probabilmente in linea. In generale è meglio ottimizzare per la leggibilità umana. Lascia che JVM esegua l’ottimizzazione del runtime.

L’esperto di JVM, Brian Goetz, afferma che la final non ha alcun impatto sui metodi in corso.

Se lanci un’eccezione nella barra () e stampi lo stacktrace, vedrai l’intero percorso delle chiamate … Penso che java onorerà tutti loro.

Il secondo caso è lo stesso, il debug è solo una variabile del tuo sistema, non una definizione come in C ++, quindi è obbligatorio valutarla prima.

Potrei sbagliarmi, ma la mia sensazione è “no in tutti i casi”. Perché la string bar() può essere sovrascritta da un sovraccarico di altre classi nello stesso pacchetto. final metodi final sono buoni candidati, ma dipende dal JIT.

Un’altra nota interessante è qui .