2011-10-14 7 views
23

mi chiedo se JVM/javac è abbastanza intelligente per trasformareI metodi in linea Java sarebbero durante l'ottimizzazione?

// This line... 
string a = foo(); 

string foo() 
{ 
    return bar(); 
} 

string bar() 
{ 
    return some-complicated-string computation; 
} 

in

string a = bar(); 

o nastri chiamata inutile foo() in caso di rilascio (perché il 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?

+0

Risposta breve: Sì, la JVM eseguirà questo tipo di ottimizzazioni. (A condizione che le chiamate di funzione non siano polimorfiche e possano essere determinate staticamente.) – Mysticial

+2

@Mysticial: cosa ti fa pensare così? :) Ho letto questo http://www.ibm.com/developerworks/java/library/j-benchmark1/index.html ma non sono positivo, ho delle risposte. – Schultz9999

+0

Non ho mai detto che * farà * sempre le ottimizzazioni. Ma quel link che hai dato è una buona lettura. – Mysticial

risposta

26

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

Per il primo scenario sembra che la JVM supporta inline (vedi sotto Metodihere e vedere here per un esempio inlining sulla JVM).

Non sono riuscito a trovare esempi di inlining del metodo eseguiti dallo stesso javac. 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 Metodihere sembra essere il compilatore JIT di JSM di HotSpot e non javac.

Da quello che posso vedere, javac supporta eliminazione dead-code (vedi esempio per il secondo caso) e costante piegatura. Nel piegamento costante, il compilatore calcolerà in anticipo le espressioni costanti e utilizzerà il valore calcolato anziché eseguire il calcolo durante il runtime. Ad 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 il seguente 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."<init>":()V 
    4: return 

public final void baz(); 
    Code: 
    0: sipush 300 
    3: istore_1 
    4: return 

} 

noti che il bytecode ha un sipush 300 anziché s' aloadgetfield s ed un iadd. 300 è il valore calcolato. Questo vale anche per le variabili private final. Se a e b non erano statiche, 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."<init>":()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, un sipush 300 viene utilizzato.

Per il secondo caso (eliminazione dead-code), 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 ha pronunciato la seguente 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."<init>":()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 si può vedere, la foo è non chiamato affatto in baz perché il codice all'interno del blocco if è effettivamente "morto".

HotSpot Sun di Sun's (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 l'analisi statica del bytecode mentre viene interpretata e caricata per eseguire ulteriori ottimizzazioni.

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

+0

e se le funzioni e 'debug' sono' final'? –

+0

@ratchetfreak Oops è un buon punto. Mi ero completamente dimenticato di questo. Se sono definitivi, 'javac' ottimizza la chiamata. Modificherà la mia risposta. Grazie per la segnalazione. –

+0

e qual è l'effetto di rendere la finale 'bar'? –

0

Se si genera un'eccezione nella barra() e si stampa lo stacktrace, verrà visualizzato l'intero percorso delle chiamate ... Penso che java li onorasse tutti.

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

+0

Il debug può essere "solo una variabile" o può essere un valore 'finale'. –

+0

Se debug è finale e falso, non deve essere compilato (nessun ritorno in questa funzione) – yoprogramo

+0

In realtà, tale metodo non si compilerebbe in nessun caso. –

2

nello stesso file di classe del javac sarà in grado di inline static e final (file altra classe potrebbe cambiare la funzione inline)

tuttavia JIT sarà in grado di ottimizzare molto di più (tra cui inlining superflua la rimozione bounds- e null checks, ecc.) perché sa di più sul codice

+2

Sei sicuro? Come ho scritto in un altro commento, come ho capito, javac non è autorizzato a funzioni inline perché si nasconderebbe in quale funzione viene eseguita qualcosa (ad esempio, improvvisamente si ottengono posizioni estremamente strane in eccezioni e più importante l'intero concetto di sicurezza in java sarebbe rotto). Quindi il JLS vieta al compilatore di inlining le funzioni e il JIT deve salvare le informazioni necessarie se qualcuno vuole camminare nello stack, ecc. Poiché l'unico risultato di javac è il bytecode, non vedo come memorizzare le informazioni aggiuntive in quel caso? – Voo

+0

Ho appena cercato e sono stato effettivamente corretto: 'Notiamo che un compilatore non può espandere un metodo in linea in fase di compilazione. 3 ° JLS' 13.4.22 Metodo e corpo costruttore ' – Voo

+0

@Voo avrebbero potuto aggiungere qualcosa come 'invokeinline' e le operazioni 'returninline' per simulare le chiamate di funzione quando sono effettivamente state allineate. ma aggiungendo 2 operazioni solo per evitare il sovraccarico della funzione probabilmente non ne vale la pena –

2

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

È possibile 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.

+0

Con Sun JVM, esiste un limite sintonizzabile sulla dimensione (di bytecode) del metodo che sarà in linea. –

+0

Sì, ci sono varie manopole, interne ed esterne. Ci sarà generalmente un limite su quanto totale "bloat" sarà permesso dall'inlining, ecc. Le manopole esternamente visibili tendono ad essere "advisory", sebbene - non i valori duri in un modo o nell'altro. –

-1

Potrei sbagliarmi, ma la mia sensazione è "no in tutti i casi". Perché il tuo string bar() può essere sovrascritto da sovraccarico di altre classi nello stesso pacchetto. I metodi final sono buoni candidati, ma dipende da JIT.

Un'altra nota interessante è here.

+1

Il downvoting è facile, ma scrivere un commento è difficile ... Per quelli che lo fanno, per favore lascia un messaggio, dove mi sbaglio. –

1

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

esperto JVM Brian Goetz saysfinal non ha alcun impatto sui metodi in corso.

+0

In condizione 'x', o è in linea o non è in linea. Non c'è alcun inbetween, il tuo "più probabile" significa "sì, è in linea" o "no, non è in linea"? – Pacerier

+0

@Pacerier nel runtime Java, la "condizione x" è sufficientemente complicata che "molto probabilmente" è una guida migliore del tentativo di descrivere la condizione. Se la routine non viene eseguita molte volte, verrà interpretata senza ottimizzazione. Solo dopo un certo numero di invocazioni sarà JIT-compilato al codice nativo e potenzialmente ottimizzato con passaggi come l'inlining. – slim