Cara menurunkan kinerja dengan meningkatkannya

Kami menginginkan yang terbaik, tetapi ternyata seperti biasa.
Victor Chernomyrdin,
Negarawan Rusia


Ada saat-saat dalam hidup ketika Anda tampaknya melakukan segalanya dengan benar, tetapi ada sesuatu yang salah.
Kisah ini tentang satu kasus seperti itu.


Suatu kali saya melihat kode ini dan berpikir untuk mempercepatnya:


public String appendBounds(Data data) { int beginIndex = data.beginIndex; int endIndex = data.endIndex; return new StringBuilder() .append('L') .append(data.str, beginIndex, endIndex) .append(';') .toString(); } 

Pertama, saya ingin menghitung panjang total string menggunakan variabel beginIndex dan endIndex (serta fakta bahwa selain string terpotong, 2 karakter lagi akan ditambahkan ke StringBuilder ) dan meneruskan nilai ini ke konstruktor StringBuilder untuk segera memilih array ukuran yang diperlukan. . Pikiran ini tampak terlalu jelas bagi saya, jadi saya memutuskan untuk mencoba sesuatu yang lain. Fakta bahwa kode ini tidak disorot oleh "Ide" mendorong saya ke pemikiran yang benar, meskipun gadis pintar ini biasanya menyarankan untuk mengganti string pendek dari StringBuilder::append dengan penambahan string, yang lebih pendek dan lebih mudah dibaca.


Hambatan untuk penyederhanaan ini adalah penggunaan metode StringBuilder.append(CharSequence, int, int) . Mengingat bahwa bidang data.str adalah string, menggunakan String.substring(beginIndex, endIndex) Anda dapat memilih substring darinya dan meneruskannya ke StringBuilder.append(String) .


Kode setelah konversi:


 public String appendBounds(Data data) { int beginIndex = data.beginIndex; int endIndex = data.endIndex; String subString = data.str.substring(beginIndex, endIndex); return new StringBuilder() .append('L') .append(subString) .append(';') .toString(); } 

Dan sekarang Ide menawarkan penyederhanaan:


 public String appendBounds(Data data) { int beginIndex = data.beginIndex; int endIndex = data.endIndex; return 'L' + data.str.substring(beginIndex, endIndex) + ';'; } 

Namun, tujuan kami dalam hal ini bukan keterbacaan, melainkan produktivitas. Bandingkan kedua metode:


 @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Fork(jvmArgsAppend = {"-Xms2g", "-Xmx2g"}) public class StringBuilderAppendBenchmark { @Benchmark public String appendSubString(Data data) { String latinStr = data.latinStr; String nonLatinStr = data.nonLatinStr; int beginIndex = data.beginIndex; int endIndex = data.endIndex; String substring = data.nonLatin ? nonLatinStr.substring(beginIndex, endIndex) : latinStr.substring(beginIndex, endIndex); return new StringBuilder() .append('L') .append(substring) .append(';') .toString(); } @Benchmark public String appendBounds(Data data) { String latinStr = data.latinStr; String nonLatinStr = data.nonLatinStr; int beginIndex = data.beginIndex; int endIndex = data.endIndex; String appended = data.nonLatin ? nonLatinStr : latinStr; return new StringBuilder() .append('L') .append(appended, beginIndex, endIndex) .append(';') .toString(); } @State(Scope.Thread) public static class Data { String latinStr; String nonLatinStr; @Param({"true", "false"}) boolean nonLatin; @Param({"5", "10", "50", "100", "500", "1000"}) private int length; private int beginIndex; private int endIndex; private ThreadLocalRandom random = ThreadLocalRandom.current(); @Setup public void setup() { latinStr = randomString("abcdefghijklmnopqrstuvwxyz"); nonLatinStr = randomString(""); beginIndex = 1; endIndex = length + 1; } private String randomString(String alphabet) { char[] chars = alphabet.toCharArray(); StringBuilder sb = new StringBuilder(length + 2); for (int i = 0; i < length + 2; i++) { char c = chars[random.nextInt(chars.length)]; sb.append(c); } return sb.toString(); } } } 

Benchmark ini sesederhana dua sen: string acak ditambahkan ke StringBuilder , ukurannya ditentukan oleh bidang length , dan karena halaman adalah 2019, Anda perlu memeriksanya sebagai string yang hanya berisi karakter alfabet Latin utama (yang disebut garis terkompresi, di mana setiap karakter sesuai dengan 1 byte), dan sebuah string dengan karakter non-Latin (masing-masing karakter diwakili oleh 2 byte).


Pada pemeriksaan sepintas, metode appendSubString bagi kita appendSubString lebih lambat, karena jumlah data yang direkatkan bertepatan dengan metode appendBounds , namun, dalam metode appendSubString juga penciptaan eksplisit dari substring, yaitu, mengalokasikan memori untuk objek baru dan menyalin konten ke dalam data data. / data.nonLatinStr .


Semakin mengejutkan (tetapi hanya pada pandangan pertama) hasil pengukuran yang saya lakukan menggunakan JDK11 pada mesin rumahan (Intel Core i5-4690, 3,50 GHz) tampaknya:


 Benchmark nonLatin length Score Error Units appendBounds true 5 44,6 ± 0,4 ns/op appendBounds true 10 45,7 ± 0,7 ns/op appendBounds true 50 129,0 ± 0,5 ns/op appendBounds true 100 218,7 ± 0,8 ns/op appendBounds true 500 907,1 ± 5,5 ns/op appendBounds true 1000 1626,4 ± 13,0 ns/op appendSubString true 5 28,6 ± 0,2 ns/op appendSubString true 10 30,8 ± 0,2 ns/op appendSubString true 50 65,6 ± 0,4 ns/op appendSubString true 100 106,6 ± 0,6 ns/op appendSubString true 500 430,1 ± 2,4 ns/op appendSubString true 1000 839,1 ± 8,6 ns/op appendBounds:·gc.alloc.rate.norm true 5 184,0 ± 0,0 B/op appendBounds:·gc.alloc.rate.norm true 10 200,0 ± 0,0 B/op appendBounds:·gc.alloc.rate.norm true 50 688,0 ± 0,0 B/op appendBounds:·gc.alloc.rate.norm true 100 1192,0 ± 0,0 B/op appendBounds:·gc.alloc.rate.norm true 500 5192,0 ± 0,0 B/op appendBounds:·gc.alloc.rate.norm true 1000 10200,0 ± 0,0 B/op appendSubString:·gc.alloc.rate.norm true 5 136,0 ± 0,0 B/op appendSubString:·gc.alloc.rate.norm true 10 160,0 ± 0,0 B/op appendSubString:·gc.alloc.rate.norm true 50 360,0 ± 0,0 B/op appendSubString:·gc.alloc.rate.norm true 100 608,0 ± 0,0 B/op appendSubString:·gc.alloc.rate.norm true 500 2608,0 ± 0,0 B/op appendSubString:·gc.alloc.rate.norm true 1000 5104,0 ± 0,0 B/op appendBounds false 5 20,8 ± 0,1 ns/op appendBounds false 10 24,0 ± 0,2 ns/op appendBounds false 50 66,4 ± 0,4 ns/op appendBounds false 100 111,0 ± 0,8 ns/op appendBounds false 500 419,2 ± 2,7 ns/op appendBounds false 1000 840,4 ± 7,8 ns/op appendSubString false 5 25,3 ± 0,3 ns/op appendSubString false 10 25,7 ± 0,2 ns/op appendSubString false 50 36,0 ± 0,1 ns/op appendSubString false 100 52,8 ± 0,4 ns/op appendSubString false 500 206,1 ± 6,1 ns/op appendSubString false 1000 388,1 ± 1,6 ns/op appendBounds:·gc.alloc.rate.norm false 5 80,0 ± 0,0 B/op appendBounds:·gc.alloc.rate.norm false 10 88,0 ± 0,0 B/op appendBounds:·gc.alloc.rate.norm false 50 320,0 ± 0,0 B/op appendBounds:·gc.alloc.rate.norm false 100 544,0 ± 0,0 B/op appendBounds:·gc.alloc.rate.norm false 500 2144,0 ± 0,0 B/op appendBounds:·gc.alloc.rate.norm false 1000 4152,0 ± 0,0 B/op appendSubString:·gc.alloc.rate.norm false 5 96,0 ± 0,0 B/op appendSubString:·gc.alloc.rate.norm false 10 112,0 ± 0,0 B/op appendSubString:·gc.alloc.rate.norm false 50 192,0 ± 0,0 B/op appendSubString:·gc.alloc.rate.norm false 100 288,0 ± 0,0 B/op appendSubString:·gc.alloc.rate.norm false 500 1088,0 ± 0,0 B/op appendSubString:·gc.alloc.rate.norm false 1000 2088,0 ± 0,0 B/op 

Menolak asumsi kami, metode appendSubString dalam sebagian besar kasus (termasuk selalu untuk string non-Latin) ternyata menjadi lebih cepat dan kurang rakus (meskipun String::substring mengembalikan objek baru). Bagaimana itu bisa terjadi?


Saya melihat di buku, saya melihat ara


Mempelajari kode sumber StringBuilder akan membantu StringBuilder tabir kerahasiaan. Kedua metode yang digunakan meneruskan panggilan ke metode yang sama dari AbstractStringBuilder :


 public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, Comparable<StringBuilder>, CharSequence { @Override public StringBuilder append(String str) { super.append(str); return this; } @Override public StringBuilder append(CharSequence s, int start, int end) { super.append(s, start, end); return this; } } 

Pergi ke AbstractStringBuilder.append(String) :


 public AbstractStringBuilder append(String str) { if (str == null) { return appendNull(); } int len = str.length(); ensureCapacityInternal(count + len); putStringAt(count, str); count += len; return this; } private final void putStringAt(int index, String str) { if (getCoder() != str.coder()) { inflate(); } str.getBytes(value, index, coder); } 

Apa yang menarik di sini? Metode AbstractStringBuilder::inflate , seperti namanya, memperluas array AbstractStringBuilder.value saat menggabungkan string yang berbeda. Data String::getBytes dalam metode String::getBytes :


 void getBytes(byte[] dst, int dstBegin, byte coder) { if (coder() == coder) { System.arraycopy(value, 0, dst, dstBegin << coder, value.length); } else { // this.coder == LATIN && coder == UTF16 StringLatin1.inflate(value, 0, dst, dstBegin, value.length); } } 

Apa yang penting Jika string bersifat homogen, maka System::arraycopy intrinsik digunakan untuk memindahkan data, jika tidak StringLatin1::inflate , yang, melalui delegasi, mengarahkan kita ke metode StringUTF16::inflate :


 // inflatedCopy byte[] -> byte[] @HotSpotIntrinsicCandidate public static void inflate(byte[] src, int srcOff, byte[] dst, int dstOff, int len) { // We need a range check here because 'putChar' has no checks checkBoundsOffCount(dstOff, len, dst); for (int i = 0; i < len; i++) { putChar(dst, dstOff++, src[srcOff++] & 0xff); } } @HotSpotIntrinsicCandidate static void putChar(byte[] val, int index, int c) { assert index >= 0 && index < length(val) : "Trusted caller missed bounds check"; index <<= 1; val[index++] = (byte)(c >> HI_BYTE_SHIFT); val[index] = (byte)(c >> LO_BYTE_SHIFT); } 

Jadi, jika barisnya homogen, maka metode platform-dependent System::arraycopy digunakan untuk memindahkan data, jika tidak, sebuah loop (juga intrinsik) digunakan. Ini berarti bahwa ketika menempelkan dua baris, di mana semua karakter berada dalam himpunan alfabet Latin utama (dengan kata lain, muat dalam 1 byte), kinerjanya harus jauh lebih baik daripada ketika menempelkan garis heterogen. Patokan mengkonfirmasi ini (lihat output untuk nonLatin = false ).


Sekarang metode AbstractStringBuilder.append(CharSequence, int, int) :


 @Override public AbstractStringBuilder append(CharSequence s, int start, int end) { if (s == null) { s = "null"; } checkRange(start, end, s.length()); int len = end - start; ensureCapacityInternal(count + len); appendChars(s, start, end); return this; } private final void appendChars(CharSequence s, int off, int end) { if (isLatin1()) { byte[] val = this.value; for (int i = off, j = count; i < end; i++) { char c = s.charAt(i); if (StringLatin1.canEncode(c)) { val[j++] = (byte)c; } else { count = j; inflate(); StringUTF16.putCharsSB(this.value, j, s, i, end); count += end - i; return; } } } else { StringUTF16.putCharsSB(this.value, count, s, off, end); } count += end - off; } 

Di sini, pendekatannya mirip dengan yang ada dalam contoh sebelumnya: untuk string homogen, mekanisme yang lebih sederhana digunakan (di sini, tandatangani penyalinan dalam satu lingkaran), untuk string heterogen kita menggunakan StringUTF16 , namun, perhatikan bahwa metode StringUTF16::putCharsSB disebut tidak StringUTF16::putCharsSB .


 public static void putCharsSB(byte[] val, int index, CharSequence s, int off, int end) { checkBoundsBeginEnd(index, index + end - off, val); for (int i = off; i < end; i++) { putChar(val, index++, s.charAt(i)); } } 

Jadi, struktur internal kedua metode dan alasan untuk kinerja mereka yang berbeda kurang lebih jelas bagi kami. Pertanyaan secara alami muncul - apa yang harus dilakukan dengan pengetahuan yang diperoleh selanjutnya? Ada beberapa opsi sekaligus:


1) ingatlah ini dan ketika ia mendeteksi kode yang mencurigakan, ubahlah dengan tangan Anda
2) pergi ke Tagir dan memintanya untuk mengajukan cek yang akan melakukan pekerjaan, bukan kita
3) membuat perubahan pada JDK sehingga kode tidak berubah sama sekali.


Tentu saja, kita mulai dengan yang ketiga. Siap mengambil risiko?


Jurang maut


Kami akan berlatih pada kucing pada kode sumber Java kesebelas, Anda dapat mengunduh di sini .


Cara paling sederhana dan paling jelas untuk meningkatkan adalah memilih substring yang sudah ada di dalam metode AbstractStringBuilder.append(CharSequence, int, int) :


 //  public AbstractStringBuilder append(CharSequence s, int start, int end) { if (s == null) { s = "null"; } checkRange(start, end, s.length()); int len = end - start; ensureCapacityInternal(count + len); appendChars(s, start, end); return this; } //  public AbstractStringBuilder append(CharSequence s, int start, int end) { if (s == null) { s = "null"; } checkRange(start, end, s.length()); return append(s.subSequence(start, end).toString()); } 

Sekarang Anda perlu membangun JDK, menjalankan tes dan menjalankan tolok ukur StringBuilderAppendBenchmark::appendBounds di atasnya, hasil yang perlu dibandingkan dengan hasil patokan yang sama pada JDK asli:


 #   before      JDK, # after -   Benchmark nonLatin length before after Units avgt true 5 44,6 64,4 ns/op avgt true 10 45,7 66,3 ns/op avgt true 50 129,0 168,9 ns/op avgt true 100 218,7 281,9 ns/op avgt true 500 907,1 1116,2 ns/op avgt true 1000 1626,4 2002,5 ns/op gc.alloc.rate.norm true 5 184,0 264,0 B/op gc.alloc.rate.norm true 10 200,0 296,0 B/op gc.alloc.rate.norm true 50 688,0 904,0 B/op gc.alloc.rate.norm true 100 1192,0 1552,0 B/op gc.alloc.rate.norm true 500 5192,0 6752,0 B/op gc.alloc.rate.norm true 1000 10200,0 13256,0 B/op avgt false 5 20,8 38,0 ns/op avgt false 10 24,0 37,8 ns/op avgt false 50 66,4 82,9 ns/op avgt false 100 111,0 138,8 ns/op avgt false 500 419,2 531,9 ns/op avgt false 1000 840,4 1002,7 ns/op gc.alloc.rate.norm false 5 80,0 152,0 B/op gc.alloc.rate.norm false 10 88,0 168,0 B/op gc.alloc.rate.norm false 50 320,0 440,0 B/op gc.alloc.rate.norm false 100 544,0 688,0 B/op gc.alloc.rate.norm false 500 2144,0 2688,0 B/op gc.alloc.rate.norm false 1000 4152,0 5192,0 B/op 

Apa yang disebut, tiba-tiba! Perbaikan tidak hanya tidak terjadi, tetapi juga terjadi kemunduran. Sial, tapi bagaimana caranya?


Faktanya adalah bahwa pada awal, dalam deskripsi metode StringBuilder::append saya membuat satu kelalaian kecil, tetapi sangat penting. Metode itu dijelaskan seperti ini:


 public final class StringBuilder { @Override public StringBuilder append(String str) { super.append(str); return this; } } 

Dan inilah tampilan lengkapnya:


 public final class StringBuilder { @Override @HotSpotIntrinsicCandidate public StringBuilder append(String str) { super.append(str); return this; } } 

Kode Java yang kami periksa di atas, dipanaskan dan dikompilasi di tingkat C2, tidak masalah, karena tidak dijalankan, tetapi intrinsik. Ini mudah dibuktikan dengan menghapus profil menggunakan async-profiler . Selanjutnya, profil dihapus untuk length = 1000 dan nonLatin = true :


 #   `appendSubString`, JDK    ns percent samples top ---------- ------- ------- --- 19096340914 43.57% 1897673 jbyte_disjoint_arraycopy <--------- 13500185356 30.80% 1343343 jshort_disjoint_arraycopy <--------- 4124818581 9.41% 409533 java.lang.String.<init> #   2177311938 4.97% 216375 java.lang.StringUTF16.compress #   1557269661 3.55% 154253 java.util.Arrays.copyOfRange #   349344451 0.80% 34823 appendSubString_avgt_jmhStub 279803769 0.64% 27862 java.lang.StringUTF16.newString 274388920 0.63% 27312 org.openjdk.jmh.infra.Blackhole.consume 160962540 0.37% 15946 SpinPause 122418222 0.28% 11795 __memset_avx2 

Kode StringBuilder (dan AbstractStringBuilder ) bahkan tidak berbau di sini, hampir 3/4 dari profil ditempati oleh intrinsik. Saya ingin mengamati tentang gambar yang sama di profil StringBuilder.append(CharSequence, int, int) "ditingkatkan" kami StringBuilder.append(CharSequence, int, int) .


Faktanya, kita memiliki ini:


  ns percent samples top ---------- ------- ------- --- 19071221451 43.78% 1897827 jbyte_disjoint_arraycopy 6409223440 14.71% 638348 jlong_disjoint_arraycopy 3933622128 9.03% 387403 java.lang.StringUTF16.newBytesFor 2067248311 4.75% 204193 java.lang.AbstractStringBuilder.ensureCapacityInternal 1929218737 4.43% 194751 java.lang.StringUTF16.compress 1678321343 3.85% 166458 java.util.Arrays.copyOfRange 1621470408 3.72% 160849 java.lang.String.checkIndex 969180099 2.22% 96018 java.util.Arrays.copyOf 581600786 1.34% 57818 java.lang.AbstractStringBuilder.<init> 417818533 0.96% 41611 appendBounds_jmhTest 406565329 0.93% 40479 java.lang.String.<init> 340972882 0.78% 33727 java.lang.AbstractStringBuilder.append 299895915 0.69% 29982 java.lang.StringBuilder.toString 183885595 0.42% 18136 SpinPause 168666033 0.39% 16755 org.openjdk.jmh.infra.Blackhole.consume 

Anda akan berkata: "Ini mereka, intrinsik, di puncak!" Memang, hanya ini bukan intrinsik yang sama (termasuk. Bandingkan nama yang kedua dari atas). Ingat:


 public final class StringBuilder { @Override @HotSpotIntrinsicCandidate public StringBuilder append(String str) { super.append(str); return this; } } 

Di sini intrinsik menggantikan panggilan ke StringBuilder.append(String) , tetapi dalam tambalan kami panggilan ini tidak! Disebut AbstractStringBuilder.append(String) . Panggilan jbyte_disjoint_arraycopy kami jbyte_disjoint_arraycopy adalah intrinsik untuk StringLatin1::inflate , dipanggil dari AbstractStringBuider::putStringAt via String::getBytes . Artinya, tidak seperti StringBuilder::append tidak hanya memproses platform-spesifik, tetapi juga kode Java,


Pahami penyebab kegagalan, cobalah untuk berhasil sebaliknya. Mudah ditebak bahwa kita perlu merujuk StringBuilder::append . Anda dapat melakukan ini dengan merobek tambalan sebelumnya dan membuat perubahan pada StringBuilder itu sendiri:


 public final class StringBuilder { //  @Override public StringBuilder append(CharSequence s, int start, int end) { super.append(s, start, end); return this; } //  @Override public StringBuilder append(CharSequence s, int start, int end) { if (s == null) { s = "null"; } checkRange(start, end, s.length()); return this.append(s.subSequence(start, end).toString()); } } 

Sekarang semuanya dilakukan dengan bijak: StringBuilder :: append yang sudah diinseminasi disebut.
Bangun kembali, jalankan, bandingkan:


 #   before      JDK, # after -   Benchmark nonLatin length before after Units avgt true 5 44,6 60,2 ns/op avgt true 10 45,7 59,1 ns/op avgt true 50 129,0 164,6 ns/op avgt true 100 218,7 276,2 ns/op avgt true 500 907,1 1088,8 ns/op avgt true 1000 1626,4 1959,4 ns/op gc.alloc.rate.norm true 5 184,0 264,0 B/op gc.alloc.rate.norm true 10 200,0 296,0 B/op gc.alloc.rate.norm true 50 688,0 904,0 B/op gc.alloc.rate.norm true 100 1192,0 1552,0 B/op gc.alloc.rate.norm true 500 5192,0 6752,0 B/op gc.alloc.rate.norm true 1000 10200,0 13256,0 B/op avgt false 5 20,8 37,9 ns/op avgt false 10 24,0 37,9 ns/op avgt false 50 66,4 80,9 ns/op avgt false 100 111,0 125,6 ns/op avgt false 500 419,2 483,6 ns/op avgt false 1000 840,4 893,8 ns/op gc.alloc.rate.norm false 5 80,0 152,0 B/op gc.alloc.rate.norm false 10 88,0 168,0 B/op gc.alloc.rate.norm false 50 320,0 440,0 B/op gc.alloc.rate.norm false 100 544,0 688,0 B/op gc.alloc.rate.norm false 500 2144,0 2688,0 B/op gc.alloc.rate.norm false 1000 4152,0 5187,2 B/op 

Saya benar-benar merasa sangat sedih, tetapi tidak membaik. Sekarang profil baru:


  ns percent samples top ---------- ------- ------- --- 19614374885 44.12% 1953620 jbyte_disjoint_arraycopy 6645299702 14.95% 662146 jlong_disjoint_arraycopy 4065789919 9.15% 400167 java.lang.StringUTF16.newBytesFor 2374627822 5.34% 234746 java.lang.AbstractStringBuilder.ensureCapacityInternal 1837858014 4.13% 183822 java.lang.StringUTF16.compress 1472039604 3.31% 145956 java.util.Arrays.copyOfRange 1316397864 2.96% 130747 appendBounds_jmhTest 956823151 2.15% 94959 java.util.Arrays.copyOf 573091712 1.29% 56933 java.lang.AbstractStringBuilder.<init> 434454076 0.98% 43202 java.lang.String.<init> 368480388 0.83% 36439 java.lang.AbstractStringBuilder.append 304409057 0.68% 30442 java.lang.StringBuilder.toString 272437989 0.61% 26833 SpinPause 201051696 0.45% 19985 java.lang.StringBuilder.<init> 198934052 0.45% 19810 appendBounds_avgt_jmhStub 

Sedikit yang berubah. Bagi saya, masih belum jelas mengapa intrinsik tidak berfungsi saat mengakses StringBuilder.append(String) dari dalam StringBuilder . Ada kecurigaan bahwa menempel (inlining) tubuh metode StringBuilder.append(String) ke tubuh StringBuilder.append(CharSequence, int, int) mengubah sesuatu dalam pemrosesan panggilan metode VM.


Bagaimanapun, ini kegagalan, kawan. Tidak mungkin untuk menambal JDK, tetapi kita masih bisa melakukan penggantian secara manual di tempat yang masuk akal.


Retret Sastra Gagal
Enkripsi respons datang dalam dua hari. Navigator tidak ingin berpisah dengan Oto Velara, dengan perusahaan yang membangun kapal perang yang sangat cepat dan kuat. Navigator tidak ingin membaca enkripsi kepada saya. Dia hanya mengulangi respons dari pos komando: "Tidak." Enkripsi tidak menjelaskan mengapa "tidak." “Tidak” berarti bahwa ia adalah orang yang dikenal dengan komputer besar. Jika tidak ada yang diketahui tentang dia, jawabannya adalah ya: coba saja. Terlalu buruk Sayang kehilangan orang yang begitu menarik. Dan komandan harus kasihan padaku. Mungkin yang pertama adalah sayang. Dia melihat saya merobek Viking. Dan dia tidak ingin mendorongku ke bulldog lagi.
Dia diam. Tapi saya tahu bahwa dalam menyediakan kekurangan pekerja yang liar:
- Saya, Kamerad Jenderal, bekerja besok dalam menyediakan. Biarkan aku pergi
- Lanjutkan. - Dan tiba-tiba dia tersenyum. "Kau tahu, setiap awan memiliki garis perak."
"Aku, Kamerad Jenderal, selalu sakit tanpa kebaikan."
"Dan ini dia." Anda dilarang bertemu dengannya, itu buruk. Tetapi untuk harta pengalaman kami, kami menambahkan biji-bijian lain.

Kesimpulan:


  • kode metode JDK dalam beberapa kasus tidak terkait dengan eksekusi aktual, karena alih-alih tubuh metode intrinsik dapat dieksekusi, yang tersembunyi di dalam perut VM.
  • metode seperti itu dapat dikenali, khususnya, label @HotSpotIntrinsicCandidate menunjuk kepada mereka, meskipun beberapa metode @HotSpotIntrinsicCandidate tanpa petunjuk apa pun, misalnya String::equals (dan banyak, banyak lainnya ).
  • Kesimpulan yang datang dari dua yang pertama adalah bahwa diskusi kita tentang cara kerja kode JDK mungkin bertentangan dengan kenyataan. C'est la vie

PS
Kemungkinan pengganti lain:


 StringBuilder sb = new StringBuilder(); sb.append(str, 0, endIndex); // --> StringBuilder sb = new StringBuilder(str.substring(o, endIndex)); 

PPS
Pengembang Oracle dengan tepat menunjukkan hal itu


Menurut saya agak aneh dan mengejutkan untuk memperkenalkan jalur kode ke
sb.append (cs, int, int) yang mengalokasikan memori untuk mendapatkan intrinsik itu
hanya terkadang membuat segalanya berjalan lebih cepat. Seperti yang Anda amati, kinerja
pengorbanan tidak jelas.

Sebaliknya, jika kita ingin mengoptimalkan sb.append (cs, int, int) mungkin kita harus pergi
maju dan lakukan itu, mungkin dengan menambah atau mengatur ulang intrinsik.

Solusi yang diajukan adalah intrinsifikasi StringBuilder.append(CharSequence, int, int) .


Tugas
Diskusi


PPS
Menariknya, saat ini, ketika menulis sesuatu seperti


 StringBuilder sb = new StringBuilder(); sb.append(str.substring(0, endIndex)); 

"Ide" menyarankan penyederhanaan kode


 StringBuilder sb = new StringBuilder(); sb.append(s, 0, endIndex); 

Jika kinerja di tempat ini tidak terlalu penting bagi Anda, mungkin lebih tepat menggunakan versi kedua yang disederhanakan. Namun, sebagian besar kode yang kita tulis adalah untuk kawan-kawan kita, bukan untuk mesin.

Source: https://habr.com/ru/post/id436746/


All Articles