Wir wollten das Beste, aber es stellte sich wie immer heraus.
Victor Chernomyrdin,
Russischer Staatsmann
Es gibt Zeiten im Leben, in denen Sie scheinbar alles richtig machen, aber etwas schief geht.
Diese Geschichte handelt von einem solchen Fall.
Einmal habe ich mir diesen Code angesehen und darüber nachgedacht, ihn zu beschleunigen:
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(); }
Zuerst wollte ich die Gesamtlänge der Zeichenfolge mithilfe der Variablen beginIndex
und endIndex
(sowie die Tatsache, dass zusätzlich zur abgeschnittenen Zeichenfolge 2 weitere Zeichen zu StringBuilder
hinzugefügt werden) und diesen Wert an den StringBuilder
Konstruktor übergeben, um sofort das Array mit der erforderlichen Größe auszuwählen . Dieser Gedanke schien mir zu offensichtlich, also beschloss ich, etwas anderes auszuprobieren. Die Tatsache, dass dieser Code nicht durch die "Idee" hervorgehoben wurde, veranlasste mich zum richtigen Gedanken, obwohl dieses kluge Mädchen normalerweise vorschlägt, die kurze Zeichenfolge aus StringBuilder::append
durch die Hinzufügung von Zeichenfolgen zu ersetzen, die kürzer und leichter zu lesen sind.
Ein Hindernis für diese Vereinfachung ist die Verwendung der Methode StringBuilder.append(CharSequence, int, int)
. Da das Feld data.str
eine Zeichenfolge ist, können Sie mit String.substring(beginIndex, endIndex)
eine String.substring(beginIndex, endIndex)
auswählen und an StringBuilder.append(String)
.
Code nach der Konvertierung:
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(); }
Und jetzt bietet die Idee eine Vereinfachung:
public String appendBounds(Data data) { int beginIndex = data.beginIndex; int endIndex = data.endIndex; return 'L' + data.str.substring(beginIndex, endIndex) + ';'; }
Unser Ziel in diesem Fall ist jedoch weniger die Lesbarkeit als vielmehr die Produktivität. Vergleichen Sie beide Methoden:
@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(); } } }
Der Benchmark ist so einfach wie zwei Cent: Dem StringBuilder
wird eine zufällige Zeichenfolge hinzugefügt, deren Größe durch das length
bestimmt wird. Da der Yard 2019 ist, müssen Sie ihn als Zeichenfolge überprüfen, die nur die Zeichen des lateinischen Hauptalphabets enthält (die sogenannte komprimierte Zeile, in der jedes Zeichen enthalten ist) entspricht 1 Byte) und einer Zeichenfolge mit nicht lateinischen Zeichen (jedes Zeichen wird durch 2 Byte dargestellt).
Bei einer flüchtigen Prüfung appendSubString
uns die appendSubString
Methode langsamer zu sein, da die zu klebende Datenmenge mit der der appendBounds
Methode übereinstimmt. Bei der appendBounds
Methode wird jedoch auch explizit eine Teilzeichenfolge erstellt, d. H. Speicher für ein neues Objekt data.latinStr
und der Inhalt aus data.latinStr
kopiert / data.nonLatinStr
.
Die überraschenderen (aber nur auf den ersten Blick) Ergebnisse der Messung, die ich mit JDK11 auf einem Heimcomputer (Intel Core i5-4690, 3,50 GHz) durchgeführt habe, scheinen zu sein:
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
Unsere Annahme zu widerlegen, appendSubString
die appendSubString
Methode in den allermeisten Fällen (auch immer für nicht-lateinische Zeichenfolgen) als schneller und weniger gefräßig (obwohl String::substring
ein neues Objekt zurückgibt). Wie ist es passiert?
Ich schaue in das Buch, ich sehe eine Feige
Das Studium des StringBuilder
Quellcodes hilft dabei StringBuilder
Schleier der Geheimhaltung zu lüften. Beide verwendeten Methoden übergeben den Aufruf an dieselben Methoden von 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; } }
Gehen Sie zu 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); }
Was ist hier interessant? Die AbstractStringBuilder::inflate
, wie der Name schon sagt, das AbstractStringBuilder.value
Array, wenn unterschiedliche Zeichenfolgen kombiniert werden. Die Daten werden in der String::getBytes
:
void getBytes(byte[] dst, int dstBegin, byte coder) { if (coder() == coder) { System.arraycopy(value, 0, dst, dstBegin << coder, value.length); } else {
Was ist wichtig? Wenn die Zeichenfolgen homogen sind, werden die Daten mit der intrinsischen System::arraycopy
verschoben, andernfalls mit StringLatin1::inflate
, was uns durch Delegierung zur StringUTF16::inflate
:
Wenn die Zeilen homogen sind, wird die Plattform mit der plattformabhängigen Methode System::arraycopy
verschoben, andernfalls wird eine Schleife (auch intrinsisch) verwendet. Dies bedeutet, dass beim Kleben von zwei Linien, bei denen alle Zeichen im Satz des lateinischen Hauptalphabets enthalten sind (dh in 1 Byte passen), die Leistung viel besser sein sollte als beim Kleben heterogener Linien. Der Benchmark bestätigt dies (siehe Ausgabe für nonLatin = false
).
Nun die AbstractStringBuilder.append(CharSequence, int, int)
Methode 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; }
Hier ähnelt der Ansatz dem im vorherigen Beispiel: Für homogene Zeichenfolgen wird ein einfacherer Mechanismus verwendet (hier das Kopieren von Zeichen in einer Schleife). Für heterogene Zeichenfolgen verwenden wir StringUTF16
. Beachten StringUTF16
jedoch, dass die aufgerufene StringUTF16::putCharsSB
nicht 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)); } }
Die interne Struktur beider Methoden und der Grund für ihre unterschiedliche Leistung sind uns also mehr oder weniger klar. Es stellt sich natürlich die Frage, was mit dem als nächstes gewonnenen Wissen zu tun ist. Es gibt mehrere Optionen gleichzeitig:
1) Denken Sie daran und wenn es einen verdächtigen Code entdeckt, ändern Sie ihn mit Ihren Händen
2) Gehen Sie zu Tagir und bitten Sie ihn, einen Scheck einzureichen, der die Arbeit anstelle von uns erledigt
3) Nehmen Sie Änderungen am JDK vor, damit sich der Code überhaupt nicht ändert.
Natürlich beginnen wir mit dem dritten. Bereit, ein Risiko einzugehen?
Abgrund
Wir werden trainieren auf Katzen Den Quellcode des elften Java können Sie hier herunterladen.
Die einfachste und naheliegendste Möglichkeit zur Verbesserung besteht darin, einen Teilstring auszuwählen, der sich bereits in der AbstractStringBuilder.append(CharSequence, int, int)
Methode AbstractStringBuilder.append(CharSequence, int, int)
:
Jetzt müssen Sie das JDK erstellen, die Tests ausführen und den StringBuilderAppendBenchmark::appendBounds
Benchmark darauf StringBuilderAppendBenchmark::appendBounds
, dessen Ergebnisse mit den Ergebnissen desselben Benchmarks im ursprünglichen JDK verglichen werden müssen:
# 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
Was heißt plötzlich! Verbesserungen traten nicht nur nicht auf, sondern es trat auch eine Verschlechterung auf. Verdammt, aber wie?
Tatsache ist, dass ich ganz am Anfang in der Beschreibung der StringBuilder::append
Methode eine kleine, aber kritisch wichtige Auslassung gemacht habe. Die Methode wurde folgendermaßen beschrieben:
public final class StringBuilder { @Override public StringBuilder append(String str) { super.append(str); return this; } }
Und hier ist die vollständige Ansicht:
public final class StringBuilder { @Override @HotSpotIntrinsicCandidate public StringBuilder append(String str) { super.append(str); return this; } }
Der oben untersuchte Java-Code, der auf C2-Ebene erwärmt und kompiliert wird, spielt keine Rolle, da er nicht ausgeführt wird, sondern der eigentliche Code. Dies lässt sich leicht beweisen, indem Sie das Profil mit dem Async-Profiler entfernen. Im Folgenden wird das Profil für length = 1000
und nicht 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
Der Code von StringBuilder
(und AbstractStringBuilder
) riecht hier nicht einmal, fast 3/4 des Profils ist intrinsisch. Ich möchte ungefähr das gleiche Bild im Profil unseres "verbesserten" StringBuilder.append(CharSequence, int, int)
.
In der Tat haben wir dies:
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
Sie werden sagen: "Hier sind sie ganz oben!" In der Tat sind nur diese nicht die gleichen Eigenheiten (inkl. Vergleiche den Namen der zweiten von oben). Rückruf:
public final class StringBuilder { @Override @HotSpotIntrinsicCandidate public StringBuilder append(String str) { super.append(str); return this; } }
Hier ersetzt der Intrinsic den Aufruf von StringBuilder.append(String)
, aber in unserem Patch ist dieser Aufruf nicht! Wird als AbstractStringBuilder.append(String)
. Der jbyte_disjoint_arraycopy
Aufruf von jbyte_disjoint_arraycopy
ist der intrinsische Aufruf von StringLatin1::inflate
, der von AbstractStringBuider::putStringAt
über String::getBytes
. Das heißt, im Gegensatz zu StringBuilder::append
verarbeitet es nicht nur plattformspezifischen, sondern auch Java-Code.
Verstehen Sie die Fehlerursache und versuchen Sie, anderweitig erfolgreich zu sein. Es ist leicht zu erraten, dass wir uns irgendwie auf StringBuilder::append
beziehen müssen. Sie können dies tun, indem Sie den vorherigen Patch abreißen und Änderungen an StringBuilder
selbst vornehmen:
public final class StringBuilder {
Jetzt ist alles mit Bedacht erledigt: Der intrinsisierte StringBuilder :: append wird aufgerufen.
Neu erstellen, ausführen, vergleichen:
# 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
Ich bin wirklich sehr traurig, aber es wurde nicht besser. Jetzt ein neues Profil:
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
Es hat sich wenig geändert. Für mich bleibt unklar, warum das Intrinsic beim Zugriff auf StringBuilder.append(String)
aus StringBuilder
nicht funktioniert hat. Es besteht der Verdacht, dass das Einfügen (Inlining) des Hauptteils der StringBuilder.append(String)
-Methode in den Hauptteil von StringBuilder.append(CharSequence, int, int)
etwas bei der Verarbeitung von VM-Methodenaufrufen ändert.
Wie auch immer, das ist ein Fiasko, Bruder. Es war nicht möglich, das JDK zu patchen, aber wir können den Austausch trotzdem manuell durchführen, wo es sinnvoll ist.
Failure Literary RetreatDie Antwortverschlüsselung erfolgte in zwei Tagen. Der Navigator möchte sich nicht von Oto Velara trennen, einer Firma, die überraschend schnelle und mächtige Kriegsschiffe baut. Der Navigator möchte mir die Verschlüsselung nicht vorlesen. Er wiederholt einfach die Antwort vom Kommandoposten: "Nein." Die Verschlüsselung erklärt nicht, warum "nein". "Nein" bedeutet auf jeden Fall, dass er eine Person ist, die einem großen Computer bekannt ist. Wenn nichts über ihn bekannt wäre, wäre die Antwort ja: probieren Sie es aus. Schade. Es ist schade, eine so interessante Person zu verlieren. Und der Kommandant muss mir leid tun. Vielleicht ist das erste Mal schade. Er sieht mich in die Wikinger reißen. Und er will mich nicht wieder in die Windhunde schieben.
Er schweigt. Aber ich weiß, dass bei einem wilden Mangel an Arbeitskräften:
- Ich, Generalgenosse, arbeite morgen an der Versorgung. Lass mich gehen?
- Weiter. - Und plötzlich lächelt sie. "Weißt du, jede Wolke hat einen Silberstreifen."
"Ich, Genosse General, bin immer krank ohne Gutes."
"Und hier ist es." Es war dir verboten, ihn zu treffen, das ist schlecht. Aber zu den Schätzen unserer Erfahrung haben wir ein weiteres Korn hinzugefügt.
Schlussfolgerungen:
- Der Code von JDK-Methoden hängt in einigen Fällen nicht mit der tatsächlichen Ausführung zusammen, da anstelle des Methodenkörpers ein intrinsischer Code ausgeführt werden kann, der im Darm der VM verborgen ist.
- Solche Methoden können erkannt werden, insbesondere die Bezeichnung
@HotSpotIntrinsicCandidate
zeigt auf sie, obwohl einige Methoden ohne Hinweis @HotSpotIntrinsicCandidate
sind, z. B. String::equals
(und viele, viele andere ). - Die Schlussfolgerung aus den ersten beiden ist, dass unsere Diskussion darüber, wie der JDK-Code funktioniert, möglicherweise der Realität widerspricht. C'est la vie
PS
Ein weiterer möglicher Ersatz:
StringBuilder sb = new StringBuilder(); sb.append(str, 0, endIndex);
PPS
Oracle-Entwickler weisen zu Recht darauf hin
Es erscheint mir ziemlich seltsam und überraschend, einen Codepfad einzuführen
sb.append (cs, int, int), das Speicher zuweist, um zu einem intrinsischen Wert zu gelangen
nur manchmal laufen die Dinge schneller. Wie Sie beobachtet haben, ist die Leistung
Kompromisse sind nicht offensichtlich.
Wenn wir stattdessen sb.append (cs, int, int) optimieren möchten, sollten wir vielleicht einfach gehen
voraus und tun Sie dies, möglicherweise durch Hinzufügen oder Neuanordnen der Eigenheiten.
Die vorgeschlagene Lösung ist die Intrinsifikation von StringBuilder.append(CharSequence, int, int)
.
→ Aufgabe
→ Diskussion
PPS
Interessanterweise im Moment, wenn man so etwas schreibt
StringBuilder sb = new StringBuilder(); sb.append(str.substring(0, endIndex));
"Idee" schlägt vor, den Code zu vereinfachen
StringBuilder sb = new StringBuilder(); sb.append(s, 0, endIndex);
Wenn die Leistung an diesem Ort für Sie nicht sehr wichtig ist, ist es wahrscheinlich korrekter, die zweite, vereinfachte Version zu verwenden. Der größte Teil des Codes, den wir schreiben, ist jedoch für unsere Kameraden bestimmt, nicht für Maschinen.