Bagaimana penggabungan String diimplementasikan di Java 9?

111

Seperti yang tertulis di JEP 280: Indify String Concatenation :

Ubah Stringurutan bytecode -concatenation statis yang dihasilkan oleh javacuntuk menggunakan invokedynamicpanggilan ke fungsi library JDK. Ini akan memungkinkan pengoptimalan Stringpenggabungan di masa mendatang tanpa memerlukan perubahan lebih lanjut pada bytecode yang dikeluarkan oleh javac.

Di sini saya ingin memahami apa kegunaan invokedynamicpanggilan dan bagaimana penggabungan bytecode berbeda dari invokedynamic?

Mohit Tyagi
sumber
11
Saya menulis tentang itu beberapa waktu lalu - jika itu membantu, saya akan meringkasnya menjadi sebuah jawaban.
Nicolai
10
Juga, lihat video ini yang menjelaskan dengan baik poin dari mekanisme penggabungan string baru: youtu.be/wIyeOaitmWM?t=37m58s
ZhekaKozlov
3
@ZhekaKozlov Saya berharap saya dapat memberi suara positif pada komentar Anda dua kali, tautan yang berasal dari orang-orang yang benar-benar menerapkan semua ini adalah yang terbaik.
Eugene
2
@Nicolai: Itu akan bagus, dan akan menjadi jawaban yang lebih baik daripada yang lain di sini (termasuk milik saya). Setiap bagian dari jawaban saya yang ingin Anda masukkan saat melakukannya, silakan - jika Anda menyertakan (pada dasarnya) semuanya sebagai bagian dari jawaban yang lebih luas, saya akan menghapus milik saya. Sebagai alternatif, jika Anda ingin menambahkan jawaban saya karena cukup terlihat, saya telah membuatnya menjadi wiki komunitas.
TJ Crowder

Jawaban:

95

Cara "lama" menghasilkan banyak StringBuilderoperasi yang berorientasi. Pertimbangkan program ini:

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

Jika kita mengkompilasinya dengan JDK 8 atau sebelumnya dan kemudian menggunakan javap -c Exampleuntuk melihat bytecode, kita akan melihat sesuatu seperti ini:

Contoh kelas publik {
  Contoh publik ();
    Kode:
       0: aload_0
       1: invokespecial # 1 // Metode java / lang / Object. "<init>" :() V
       4: kembali

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

Seperti yang Anda lihat, ini membuat StringBuilderdan menggunakan append. Ini terkenal cukup tidak efisien karena kapasitas default buffer internal StringBuilderhanya 16 karakter, dan tidak ada cara bagi compiler untuk mengetahui mengalokasikan lebih banyak, sehingga akhirnya harus mengalokasikan ulang. Ini juga banyak panggilan metode. (Perhatikan bahwa JVM terkadang dapat mendeteksi dan menulis ulang pola panggilan ini untuk membuatnya lebih efisien.)

Mari kita lihat apa yang dihasilkan Java 9:

Contoh kelas publik {
  Contoh publik ();
    Kode:
       0: aload_0
       1: invokespecial # 1 // Metode java / lang / Object. "<init>" :() V
       4: kembali

  public static void main (java.lang.String []);
    Kode:
       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 // Bidang java / lang / System.out: Ljava / io / PrintStream;
      18: aload_1
      19: invokevirtual # 4 // Metode java / io / PrintStream.println: (Ljava / lang / String;) V
      22: kembali
}

Ya ampun, tapi itu lebih pendek. :-) Itu membuat satu panggilan ke makeConcatWithConstantsdari StringConcatFactory, yang mengatakan ini di Javadoc-nya:

Metode untuk memfasilitasi pembuatan metode penggabungan String, yang dapat digunakan untuk secara efisien menggabungkan sejumlah argumen dari tipe yang diketahui, kemungkinan setelah adaptasi tipe dan evaluasi parsial argumen. Metode ini biasanya digunakan sebagai metode bootstrap untuk invokedynamicsitus panggilan, untuk mendukung fitur penggabungan string dari Bahasa Pemrograman Java.

T.J. Crowder
sumber
41
Ini mengingatkan saya pada jawaban yang saya tulis hampir 6 tahun yang lalu pada hari ini: stackoverflow.com/a/7586780/330057 - Seseorang bertanya apakah mereka harus membuat StringBuilder atau hanya menggunakan yang lama biasa +=di loop for mereka. Saya mengatakan kepada mereka itu tergantung, tetapi jangan lupa bahwa mereka mungkin menemukan cara yang lebih baik untuk merangkai concat suatu saat nanti. Garis kuncinya sebenarnya adalah garis kedua dari belakang:So by being smart, you have caused a performance hit when Java got smarter than you.
corsiKa
3
@corsiKa: LOL! Tapi wow, butuh waktu lama untuk sampai ke sana (maksud saya bukan enam tahun, maksud saya 22 atau lebih ... :-))
TJ Crowder
1
@supercat: Seperti yang saya pahami, ada beberapa alasan, paling tidak membuat larik varargs untuk diteruskan ke metode pada jalur kinerja-kritis tidaklah ideal. Selain itu, penggunaan invokedynamicmemungkinkan strategi penggabungan yang berbeda untuk dipilih pada waktu proses dan terikat pada pemanggilan pertama, tanpa overhead dari pemanggilan metode dan tabel pengiriman pada setiap pemanggilan; lebih lanjut di artikel nicolai di sini dan di JEP .
TJ Crowder
1
@supercat: Dan kemudian ada fakta bahwa itu tidak akan berjalan baik dengan non-Strings, karena mereka harus dikonversikan sebelumnya ke String daripada diubah menjadi hasil akhir; lebih banyak inefisiensi. Bisa membuatnya Object, tapi kemudian Anda harus mengemas semua primitif ... (Yang Nicolai liput dalam artikelnya yang sangat bagus, btw.)
TJ Crowder
2
@supercat Saya mengacu pada String.concat(String)metode yang sudah ada yang implementasinya menciptakan array string yang dihasilkan di tempat. Keuntungannya menjadi diperdebatkan ketika kita harus memanggil toString()objek sewenang-wenang. Demikian juga, saat memanggil metode yang menerima larik, pemanggil harus membuat dan mengisi larik yang mengurangi manfaat secara keseluruhan. Tapi sekarang, itu tidak relevan, karena solusi baru pada dasarnya adalah apa yang Anda pertimbangkan, kecuali bahwa ia tidak memiliki overhead tinju, tidak memerlukan pembuatan array, dan backend dapat menghasilkan penangan yang dioptimalkan untuk skenario tertentu.
Holger
20

Sebelum masuk ke detail invokedynamicimplementasi yang digunakan untuk optimasi penggabungan String, menurut pendapat saya, seseorang harus mendapatkan beberapa latar belakang tentang Apa yang dipanggil dan bagaimana cara menggunakannya?

The invokedynamic menyederhanakan instruksi dan berpotensi meningkatkan implementasi dari compiler dan sistem runtime untuk bahasa dinamis pada JVM . Ini dilakukan dengan mengizinkan pelaksana bahasa untuk menentukan perilaku tautan khusus dengan invokedynamicinstruksi yang melibatkan langkah-langkah berikut ini.


Saya mungkin akan mencoba dan membawa Anda melalui ini dengan perubahan yang dibawa untuk implementasi optimasi penggabungan String.

  • Mendefinisikan Metode Bootstrap : - Dengan Java9, metode bootstrap untuk invokedynamicsitus panggilan, untuk mendukung penggabungan string terutama makeConcatdan makeConcatWithConstantsdiperkenalkan dengan StringConcatFactoryimplementasi.

    Penggunaan invokedynamic memberikan alternatif untuk memilih strategi terjemahan hingga runtime. Strategi terjemahan yang digunakan StringConcatFactorymirip dengan yang LambdaMetafactorydiperkenalkan pada versi java sebelumnya. Selain itu, salah satu tujuan JEP yang disebutkan dalam pertanyaan tersebut adalah untuk memperluas strategi ini lebih jauh.

  • Menentukan Entri Kumpulan Konstan : - Ini adalah argumen statis tambahan untuk invokedynamicinstruksi selain (1) MethodHandles.Lookupobjek yang merupakan pabrik untuk membuat pegangan metode dalam konteks invokedynamicinstruksi, (2) Stringobjek, nama metode yang disebutkan dalam panggilan dinamis situs dan (3) MethodTypeobjek, tanda tangan jenis yang diselesaikan dari situs panggilan dinamis.

    Ada yang sudah tertaut selama penautan kode. Saat runtime, metode bootstrap berjalan dan menautkan kode sebenarnya yang melakukan penggabungan. Ini menulis ulang invokedynamicpanggilan dengan panggilan yang sesuai invokestatic. Ini memuat string konstan dari kumpulan konstan, argumen statis metode bootstrap dimanfaatkan untuk meneruskan konstanta ini dan konstanta lainnya langsung ke panggilan metode bootstrap.

  • Menggunakan Instruksi invokedynamic : - Ini menawarkan fasilitas untuk lazy linkage, dengan menyediakan sarana untuk mem-bootstrap target panggilan satu kali, selama pemanggilan awal. Ide konkret untuk pengoptimalan di sini adalah mengganti seluruh StringBuilder.appendtarian dengan invokedynamicpanggilan sederhana ke java.lang.invoke.StringConcatFactory, yang akan menerima nilai-nilai yang diperlukan untuk penggabungan.

The Indify String concatenation negara usulan dengan contoh pembandingan aplikasi dengan Java9 mana metode yang sama seperti dimiliki oleh @TJ Crowder dikompilasi dan perbedaan dalam bytecode ini cukup terlihat antara pelaksanaan yang bervariasi.

Naman
sumber
17

Saya akan sedikit menambahkan sedikit detail di sini. Bagian utama untuk mendapatkan adalah bagaimana penggabungan string dilakukan adalah keputusan runtime, bukan waktu kompilasi lagi . Dengan demikian dapat berubah, artinya Anda telah mengkompilasi kode Anda sekali terhadap java-9 dan dapat mengubah implementasi yang mendasarinya sesuka hati, tanpa perlu melakukan kompilasi ulang.

Dan poin kedua adalah saat ini ada 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
}

Anda dapat memilih salah satu dari mereka melalui parameter: -Djava.lang.invoke.stringConcat. Perhatikan bahwa StringBuildermasih ada pilihan.

Eugene
sumber