Come viene implementata la concatenazione di stringhe in Java 9?


111

Come scritto in JEP 280: Indify String Concatenation :

Modificare la Stringsequenza del bytecode di concatenazione statica generata da javacper utilizzare le invokedynamicchiamate alle funzioni della libreria JDK. Ciò consentirà future ottimizzazioni della Stringconcatenazione senza richiedere ulteriori modifiche al bytecode emesso da javac.

Qui voglio capire qual è l'uso delle invokedynamicchiamate e in che modo è diversa la concatenazione del bytecode invokedynamic?


11
Ne ho scritto qualche tempo fa - se questo aiuta, lo condenserò in una risposta.
Nicolai

10
Inoltre, dai un'occhiata a questo video che spiega bene il punto del nuovo meccanismo di concatenazione delle stringhe: youtu.be/wIyeOaitmWM?t=37m58s
ZhekaKozlov

3
@ZhekaKozlov Vorrei poter votare due volte il tuo commento, i link che provengono da persone che implementano effettivamente tutto questo sono i migliori.
Eugene

2
@Nicolai: sarebbe fantastico, e sarebbe una risposta migliore di qualsiasi altra qui (compresa la mia). Qualsiasi parte della mia risposta che desideri incorporare quando lo fai, sentiti libero - se includi (fondamentalmente) l'intera cosa come parte della risposta più ampia, cancellerò semplicemente la mia. In alternativa, se vuoi solo aggiungere alla mia risposta perché è abbastanza visibile, l'ho creato un wiki della comunità.
TJ Crowder

Risposte:


95

Il "vecchio" modo produce un mucchio di StringBuilderoperazioni orientate. Considera questo programma:

public class Example {
    public static void main(String[] args)
    {
        String result = args[0] + "-" + args[1] + "-" + args[2];
        System.out.println(result);
    }
}

Se lo compiliamo con JDK 8 o precedente e poi usiamo javap -c Example per vedere il bytecode, vediamo qualcosa del genere:

esempio di classe pubblica {
  public Example ();
    Codice:
       0: aload_0
       1: invokespecial # 1 // Metodo java / lang / Object. "<init>" :() V
       4: ritorno

  public static void main (java.lang.String []);
    Codice:
       0: nuovo # 2 // classe java / lang / StringBuilder
       3: dup
       4: invokespecial # 3 // Metodo java / lang / StringBuilder. "<init>" :() V
       7: aload_0
       8: iconst_0
       9: aaload
      10: invokevirtual # 4 // Metodo java / lang / StringBuilder.append: (Ljava / lang / String;) Ljava / lang / StringBuilder;
      13: ldc # 5 // String -
      15: invokevirtual # 4 // Metodo java / lang / StringBuilder.append: (Ljava / lang / String;) Ljava / lang / StringBuilder;
      18: aload_0
      19: iconst_1
      20: aaload
      21: invokevirtual # 4 // Metodo java / lang / StringBuilder.append: (Ljava / lang / String;) Ljava / lang / StringBuilder;
      24: ldc # 5 // String -
      26: invokevirtual # 4 // Metodo java / lang / StringBuilder.append: (Ljava / lang / String;) Ljava / lang / StringBuilder;
      29: aload_0
      30: iconst_2
      31: aaload
      32: invokevirtual # 4 // Metodo java / lang / StringBuilder.append: (Ljava / lang / String;) Ljava / lang / StringBuilder;
      35: invokevirtual # 6 // Metodo java / lang / StringBuilder.toString :() Ljava / lang / String;
      38: astore_1
      39: getstatic # 7 // Campo java / lang / System.out: Ljava / io / PrintStream;
      42: aload_1
      43: invokevirtual # 8 // Metodo java / io / PrintStream.println: (Ljava / lang / String;) V
      46: ritorno
}

Come puoi vedere, crea un file StringBuildere usa append. Questo è famoso abbastanza inefficiente poiché la capacità predefinita del buffer integrato StringBuilderè di soli 16 caratteri, e non c'è modo per il compilatore di sapere di allocare di più in anticipo, quindi finisce per dover riallocare. È anche un mucchio di chiamate di metodo. (Si noti che la JVM a volte può rilevare e riscrivere questi modelli di chiamate per renderli più efficienti, però.)

Diamo un'occhiata a cosa genera Java 9:

esempio di classe pubblica {
  public Example ();
    Codice:
       0: aload_0
       1: invokespecial # 1 // Metodo java / lang / Object. "<init>" :() V
       4: ritorno

  public static void main (java.lang.String []);
    Codice:
       0: aload_0
       1: iconst_0
       2: aaload
       3: aload_0
       4: iconst_1
       5: aaload
       6: aload_0
       7: iconst_2
       8: aaload
       9: invokedynamic # 2, 0 // InvokeDynamic # 0: makeConcatWithConstants: (Ljava / lang / String; Ljava / lang / String; Ljava / lang / String;) Ljava / lang / String;
      14: astore_1
      15: getstatic # 3 // Campo java / lang / System.out: Ljava / io / PrintStream;
      18: aload_1
      19: invokevirtual # 4 // Metodo java / io / PrintStream.println: (Ljava / lang / String;) V
      22: ritorno
}

Oh mio ma è più breve. :-) Fa una singola chiamata a makeConcatWithConstantsfrom StringConcatFactory, che dice questo nel suo Javadoc:

Metodi per facilitare la creazione di metodi di concatenazione di stringhe, che possono essere utilizzati per concatenare in modo efficiente un numero noto di argomenti di tipi noti, possibilmente dopo l'adattamento del tipo e la valutazione parziale degli argomenti. Questi metodi vengono generalmente utilizzati come metodi di bootstrap per i invokedynamicsiti di chiamata, per supportare la funzione di concatenazione di stringhe del linguaggio di programmazione Java.


41
Questo mi ricorda una risposta che ho scritto quasi 6 anni fa al giorno: stackoverflow.com/a/7586780/330057 - Qualcuno ha chiesto se dovevano creare uno StringBuilder o semplicemente usare il vecchio vecchio +=nel loro ciclo for. Ho detto loro che dipende, ma non dimentichiamo che potrebbero trovare un modo migliore per stringere il concatenamento prima o poi lungo la strada. La linea chiave è davvero la penultima linea:So by being smart, you have caused a performance hit when Java got smarter than you.
corsiKa

3
@corsiKa: LOL! Ma wow, ci è voluto molto tempo per arrivarci (non intendo sei anni, intendo 22 o giù di lì ... :-))
TJ Crowder

1
@supercat: a quanto ho capito, ci sono un paio di motivi, non ultimo il fatto che la creazione di un array varargs da passare a un metodo su un percorso critico per le prestazioni non è l'ideale. Inoltre, using invokedynamicconsente di scegliere diverse strategie di concatenazione in fase di esecuzione e di vincolarle alla prima invocazione, senza l'overhead di una chiamata al metodo e di una tabella di invio a ogni invocazione; altro nell'articolo di nicolai qui e nel PEC .
TJ Crowder

1
@supercat: E poi c'è il fatto che non funzionerebbe bene con le stringhe non String, poiché dovrebbero essere pre-convertite in String invece di essere convertite nel risultato finale; più inefficienza. Potresti farcela Object, ma poi dovresti inscatolare tutte le primitive ... (Che Nicolai copre nel suo eccellente articolo, btw.)
TJ Crowder

2
@supercat Mi riferivo al String.concat(String)metodo già esistente la cui implementazione sta creando l'array di stringhe risultante sul posto. Il vantaggio diventa discutibile quando dobbiamo invocare toString()oggetti arbitrari. Allo stesso modo, quando si chiama un metodo che accetta un array, il chiamante deve creare e riempire l'array che riduce il vantaggio complessivo. Ma ora è irrilevante, poiché la nuova soluzione è fondamentalmente ciò che stavi considerando, tranne per il fatto che non ha alcun sovraccarico di boxe, non necessita di creazione di array e il backend può generare gestori ottimizzati per scenari particolari.
Holger

20

Prima di entrare nei dettagli invokedynamicdell'implementazione utilizzata per l'ottimizzazione della concatenazione di stringhe, a mio parere, è necessario approfondire cosa è invocato dinamico e come lo si usa?

L' invokedynamic istruzione semplifica e potenzialmente migliora le implementazioni di compilatori e sistemi runtime per linguaggi dinamici sulla JVM . Lo fa consentendo all'implementatore del linguaggio di definire un comportamento di collegamento personalizzato con l' invokedynamicistruzione che implica i seguenti passaggi.


Probabilmente proverei a guidarti attraverso questi con le modifiche apportate per l'implementazione dell'ottimizzazione della concatenazione di stringhe.

  • Definizione del metodo Bootstrap : - Con Java9, i metodi bootstrap per i invokedynamicsiti di chiamata, per sostenere la concatenazione di stringhe in primo luogo makeConcate makeConcatWithConstantssono stati introdotti con l' StringConcatFactoryimplementazione.

    L'utilizzo di invokedynamic fornisce un'alternativa per selezionare una strategia di traduzione fino al runtime. La strategia di traduzione utilizzata in StringConcatFactoryè simile a quella LambdaMetafactoryintrodotta nella precedente versione java. Inoltre, uno degli obiettivi del PEC menzionato nella domanda è di estendere ulteriormente queste strategie.

  • Specificare le voci del pool di costanti : - Questi sono gli argomenti statici aggiuntivi invokedynamicdell'istruzione diversi da (1) MethodHandles.Lookupoggetto che è una factory per la creazione di handle di metodo nel contesto invokedynamicdell'istruzione, (2) un Stringoggetto, il nome del metodo menzionato nella chiamata dinamica site e (3) l' MethodTypeoggetto, la firma del tipo risolto del sito di chiamata dinamica.

    Sono già collegati durante il collegamento del codice. In fase di esecuzione, il metodo bootstrap viene eseguito e si collega al codice effettivo che esegue la concatenazione. Riscrive la invokedynamicchiamata con una invokestaticchiamata appropriata . Questo carica la stringa costante dal pool di costanti, gli argomenti statici del metodo bootstrap vengono utilizzati per passare queste e altre costanti direttamente alla chiamata al metodo bootstrap.

  • Utilizzando l'istruzione invokedynamic : - Offre i servizi per un collegamento pigro, fornendo i mezzi per eseguire il bootstrap della destinazione della chiamata una volta, durante l'invocazione iniziale. L'idea concreta per l'ottimizzazione qui è sostituire l'intera StringBuilder.appenddanza con una semplice invokedynamicchiamata ajava.lang.invoke.StringConcatFactory , che accetterà i valori da concatenare.

La proposta Indify String Concatenation afferma con un esempio il benchmarking dell'applicazione con Java9 dove un metodo simile a quello condiviso da @TJ Crowder viene compilato e la differenza nel bytecode è abbastanza visibile tra l'implementazione variabile.


17

Aggiungerò leggermente un po 'di dettagli qui. La parte principale da capire è che il modo in cui viene eseguita la concatenazione di stringhe è una decisione in fase di esecuzione, non più in fase di compilazione . Quindi può cambiare, il che significa che hai compilato il tuo codice una volta contro java-9 e può cambiare l'implementazione sottostante come preferisce, senza la necessità di ricompilare.

E il secondo punto è che al momento ci sono 6 possible strategies for concatenation of String:

 private enum Strategy {
    /**
     * Bytecode generator, calling into {@link java.lang.StringBuilder}.
     */
    BC_SB,

    /**
     * Bytecode generator, calling into {@link java.lang.StringBuilder};
     * but trying to estimate the required storage.
     */
    BC_SB_SIZED,

    /**
     * Bytecode generator, calling into {@link java.lang.StringBuilder};
     * but computing the required storage exactly.
     */
    BC_SB_SIZED_EXACT,

    /**
     * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
     * This strategy also tries to estimate the required storage.
     */
    MH_SB_SIZED,

    /**
     * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
     * This strategy also estimate the required storage exactly.
     */
    MH_SB_SIZED_EXACT,

    /**
     * MethodHandle-based generator, that constructs its own byte[] array from
     * the arguments. It computes the required storage exactly.
     */
    MH_INLINE_SIZED_EXACT
}

È possibile scegliere qualsiasi di loro tramite un parametro: -Djava.lang.invoke.stringConcat. Notare che StringBuilderè ancora un'opzione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.