Rentetan Ringkas di Java 9

1. Gambaran keseluruhan

Strings di Jawa secara dalaman diwakili oleh char [] yang mengandungi watak-watak yang String . Dan, setiap arang terdiri dari 2 bait kerana Java menggunakan UTF-16 secara dalaman.

Sebagai contoh, jika String mengandungi kata dalam bahasa Inggeris, 8 bit yang terdahulu semuanya akan menjadi 0 untuk setiap char , kerana watak ASCII dapat diwakili menggunakan satu bait.

Banyak watak memerlukan 16 bit untuk mewakili mereka tetapi kebanyakan statistik memerlukan hanya 8 bit - perwakilan watak LATIN-1. Oleh itu, terdapat ruang lingkup untuk meningkatkan penggunaan dan prestasi memori.

Yang penting juga ialah String s biasanya menempati sebahagian besar ruang timbunan JVM. Dan, kerana cara ia disimpan oleh JVM, dalam kebanyakan kes, yang String contoh boleh mengambil masa sehingga dua ruang ia sebenarnya memerlukan .

Dalam artikel ini, kita akan membincangkan pilihan Compressed String, yang diperkenalkan di JDK6 dan Compact String baru, yang baru diperkenalkan dengan JDK9. Kedua-duanya dirancang untuk mengoptimumkan penggunaan memori Strings pada JMV.

2. Rentetan Mampat - Java 6

JDK 6 kemas kini 21 Performance Release, memperkenalkan pilihan VM baru:

-XX:+UseCompressedStrings

Apabila pilihan ini diaktifkan, String disimpan sebagai bait [] , bukannya char [] - dengan itu, menjimatkan banyak memori. Walau bagaimanapun, pilihan ini akhirnya dikeluarkan di JDK 7, terutamanya kerana ia mempunyai beberapa akibat prestasi yang tidak diingini.

3. Rentetan Ringkas - Java 9

Java 9 telah membawa konsep Strings padat ba ck.

Ini bermaksud bahawa setiap kali kita membuat String jika semua watak String dapat direpresentasikan menggunakan representasi byte - LATIN-1, array byte akan digunakan secara dalaman, sehingga satu byte diberikan untuk satu karakter.

Dalam kes lain, jika mana-mana watak memerlukan lebih dari 8-bit untuk menggambarkannya, semua watak disimpan menggunakan dua bait untuk masing-masing - representasi UTF-16.

Jadi pada dasarnya, bila boleh, ia hanya akan menggunakan satu bait untuk setiap watak.

Sekarang, persoalannya - bagaimana semua operasi String akan berfungsi? Bagaimana ia akan membezakan antara representasi LATIN-1 dan UTF-16?

Nah, untuk mengatasi masalah ini, perubahan lain dibuat terhadap pelaksanaan dalaman String . Kami mempunyai pengekod bidang terakhir , yang menyimpan maklumat ini.

3.1. Pelaksanaan String di Java 9

Sehingga kini, String disimpan sebagai char [] :

private final char[] value;

Mulai sekarang, ia akan menjadi bait []:

private final byte[] value;

Pengekod pemboleh ubah :

private final byte coder;

Di mana pengekod boleh:

static final byte LATIN1 = 0; static final byte UTF16 = 1;

Sebilangan besar operasi String kini memeriksa pengekod dan menghantar ke pelaksanaan tertentu:

public int indexOf(int ch, int fromIndex) { return isLatin1() ? StringLatin1.indexOf(value, ch, fromIndex) : StringUTF16.indexOf(value, ch, fromIndex); } private boolean isLatin1() { return COMPACT_STRINGS && coder == LATIN1; } 

Dengan semua maklumat yang diperlukan JVM siap dan tersedia, pilihan CompactString VM diaktifkan secara lalai. Untuk melumpuhkannya, kita boleh menggunakan:

+XX:-CompactStrings

3.2. Bagaimana pengekod berfungsi

Dalam pelaksanaan kelas Java 9 String , panjangnya dihitung sebagai:

public int length() { return value.length >> coder; }

Sekiranya String hanya mengandungi LATIN-1, nilai coder akan menjadi 0 sehingga panjang String akan sama dengan panjang array byte.

Dalam kes lain, jika String berada dalam perwakilan UTF-16, nilai pengekod akan menjadi 1, dan dengan itu panjangnya akan menjadi setengah ukuran array bait sebenar.

Perhatikan bahawa semua perubahan yang dibuat untuk Compact String, adalah dalam pelaksanaan dalaman kelas String dan sepenuhnya telus untuk pembangun yang menggunakan String .

4. Rentetan Ringkas vs Rentetan Mampat

Dalam kes JDK 6 Compressed Strings, masalah utama yang dihadapi adalah bahawa String konstruktor hanya menerima char [] sebagai hujah. Selain itu, banyak operasi String bergantung pada representasi char [] dan bukan array byte. Oleh kerana itu, banyak pembongkaran terpaksa dilakukan, yang mempengaruhi prestasi.

Sedangkan jika terdapat Compact String, mempertahankan "coder" bidang tambahan juga dapat meningkatkan overhead. Untuk mengurangkan kos pengekod dan pembongkaran byte s ke char (sekiranya terdapat perwakilan UTF-16), beberapa kaedah diinspirasikan dan kod ASM yang dihasilkan oleh penyusun JIT juga telah diperbaiki.

Perubahan ini menghasilkan beberapa hasil intuitif. IndexOf LATIN-1 (String) memanggil kaedah intrinsik, sedangkan indexOf (char) tidak. Dalam kes UTF-16, kedua-dua kaedah ini memanggil kaedah intrinsik. Masalah ini hanya mempengaruhi Rentetan LATIN-1 dan akan diperbaiki dalam keluaran yang akan datang.

Oleh itu, Compact Strings lebih baik daripada Compressed Strings dari segi prestasi.

Untuk mengetahui berapa banyak memori yang disimpan menggunakan Compact Strings, pelbagai timbunan aplikasi Java dianalisis. Dan, walaupun hasilnya sangat bergantung pada aplikasi tertentu, peningkatan keseluruhan hampir selalu dapat dipertimbangkan.

4.1. Perbezaan Prestasi

Let's see a very simple example of the performance difference between enabling and disabling Compact Strings:

long startTime = System.currentTimeMillis(); List strings = IntStream.rangeClosed(1, 10_000_000) .mapToObj(Integer::toString) .collect(toList()); long totalTime = System.currentTimeMillis() - startTime; System.out.println( "Generated " + strings.size() + " strings in " + totalTime + " ms."); startTime = System.currentTimeMillis(); String appended = (String) strings.stream() .limit(100_000) .reduce("", (l, r) -> l.toString() + r.toString()); totalTime = System.currentTimeMillis() - startTime; System.out.println("Created string of length " + appended.length() + " in " + totalTime + " ms.");

Here, we are creating 10 million Strings and then appending them in a naive manner. When we run this code (Compact Strings are enabled by default), we get the output:

Generated 10000000 strings in 854 ms. Created string of length 488895 in 5130 ms.

Similarly, if we run it by disabling the Compact Strings using: -XX:-CompactStrings option, the output is:

Generated 10000000 strings in 936 ms. Created string of length 488895 in 9727 ms.

Clearly, this is a surface level test, and it can't be highly representative – it's only a snapshot of what the new option may do to improve performance in this particular scenario.

5. Conclusion

In this tutorial, we saw the attempts to optimize the performance and memory consumption on the JVM – by storing Strings in a memory efficient way.

Seperti biasa, keseluruhan kod boleh didapati di Github.