أردنا الأفضل ، لكن اتضح كما هو الحال دائمًا.
فيكتور تشيرنوميردين ،
رجل دولة روسي
هناك أوقات في الحياة يبدو أنك تفعل فيها كل شيء بشكل صحيح ، ولكن هناك خطأ ما.
هذه القصة عن حالة واحدة من هذا القبيل.
بمجرد أن نظرت إلى هذا الرمز وفكرت في تسريع الأمر:
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(); }
أولاً ، أردت حساب الطول الإجمالي للسلسلة باستخدام المتغيرات beginIndex
و endIndex
(بالإضافة إلى حقيقة أنه بالإضافة إلى السلسلة المقتطعة ، ستتم إضافة حرفين آخرين إلى StringBuilder
) وتمرير هذه القيمة إلى مُنشئ StringBuilder
لتحديد صفيف الحجم المطلوب على الفور . بدا هذا الفكر واضحًا جدًا بالنسبة لي ، لذلك قررت تجربة شيء آخر. دفعتني حقيقة أن هذا الرمز لم يتم تسليط الضوء عليه بواسطة "الفكرة" إلى التفكير الصحيح ، على الرغم من أن هذه الفتاة الذكية تقترح عادةً استبدال السلسلة القصيرة من StringBuilder::append
بإضافة سلاسل ، وهي أقصر وأسهل في القراءة.
عقبة أمام هذا التبسيط هي استخدام أسلوب StringBuilder.append(CharSequence, int, int)
. نظرًا لأن حقل data.str
عبارة عن سلسلة ، باستخدام String.substring(beginIndex, endIndex)
يمكنك تحديد سلسلة فرعية منه وتمريرها إلى StringBuilder.append(String)
.
الرمز بعد التحويل:
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(); }
والآن تقدم Idea تبسيطًا:
public String appendBounds(Data data) { int beginIndex = data.beginIndex; int endIndex = data.endIndex; return 'L' + data.str.substring(beginIndex, endIndex) + ';'; }
ومع ذلك ، فإن هدفنا في هذه الحالة ليس قابلية القراءة بقدر ما الإنتاجية. قارن بين الطريقتين:
@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(); } } }
المعيار بسيط مثل بنسات اثنين: يتم إضافة سلسلة عشوائية إلى StringBuilder
، يتم تحديد حجمها حسب حقل length
، وبما أن الفناء هو 2019 ، تحتاج إلى التحقق من ذلك كسلسلة تحتوي فقط على أحرف الأبجدية اللاتينية الرئيسية (ما يسمى الخط المضغوط ، والذي يُسمى فيه كل حرف يتوافق مع 1 بايت) ، وسلسلة مع الأحرف غير اللاتينية (يتم تمثيل كل حرف بواسطة 2 بايت).
في الفحص السريع ، appendSubString
أن طريقة appendSubString
لدينا أبطأ ، لأن كمية البيانات التي يجب لصقها تتوافق مع طريقة appendBounds
، ومع ذلك ، في طريقة appendSubString
أيضًا إنشاء واضح appendSubString
فرعية ، أي تخصيص الذاكرة لكائن جديد ونسخ المحتويات من data.latinStr
/ data.nonLatinStr
.
يبدو أن نتائج القياس التي أجريتها باستخدام JDK11 على جهاز منزلي (Intel Core i5-4690، 3.50 GHz) تبدو أكثر إثارة للدهشة (ولكن للوهلة الأولى).
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
appendSubString
افتراضنا ، appendSubString
طريقة appendSubString
في الغالبية العظمى من الحالات (بما في ذلك دائمًا السلاسل غير اللاتينية) كانت أسرع وأقل شرهًا (على الرغم من أن String::substring
تُرجع كائنًا جديدًا). كيف حدث ذلك؟
أنظر إلى الكتاب ، أرى التين
دراسة الكود المصدري لـ StringBuilder
ستساعد على StringBuilder
حجاب السرية. تمرير كل الأساليب المستخدمة الاستدعاء إلى نفس الأساليب من 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; } }
انتقل إلى 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); }
ما هو مثير للاهتمام هنا؟ تقوم AbstractStringBuilder::inflate
، كما يوحي الاسم ، بتوسيع صفيف AbstractStringBuilder.value
عند الجمع بين السلاسل المختلفة. يتم String::getBytes
البيانات في String::getBytes
:
void getBytes(byte[] dst, int dstBegin, byte coder) { if (coder() == coder) { System.arraycopy(value, 0, dst, dstBegin << coder, value.length); } else {
ما هو المهم؟ إذا كانت السلاسل متجانسة ، فسيتم استخدام System::arraycopy
لنقل البيانات ، وإلا StringLatin1::inflate
، والذي يقودنا من خلال التفويض إلى StringUTF16::inflate
:
وبالتالي ، إذا كانت الصفوف متجانسة ، فسيتم استخدام الطريقة التي تعتمد على System::arraycopy
لنقل البيانات ، وإلا يتم استخدام حلقة ( System::arraycopy
أيضًا). هذا يعني أنه عند الإلتصاق بسطرين ، حيث تكون جميع الأحرف في مجموعة الأبجدية اللاتينية الرئيسية (بمعنى آخر ، تناسب البايت الواحد) ، يجب أن يكون الأداء أفضل بكثير من عند الإلتصاق بالخطوط غير المتجانسة. يؤكد هذا المعيار (انظر الإخراج لغير nonLatin = false
).
الآن طريقة 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; }
هنا ، يشبه النهج المتبع في المثال السابق: بالنسبة للسلاسل المتجانسة ، يتم استخدام آلية أكثر بساطة (هنا ، نسخ النسخ في حلقة) ، للسلاسل غير المتجانسة التي نستخدمها StringUTF16
، ومع ذلك ، لاحظ أن 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)); } }
لذلك ، فإن البنية الداخلية لكلتا الطريقتين والسبب وراء أدائهما المختلف أكثر وضوحًا بالنسبة لنا. السؤال الذي يطرح نفسه بطبيعة الحال - ماذا تفعل مع المعرفة المكتسبة بعد ذلك؟ هناك العديد من الخيارات في وقت واحد:
1) ضع ذلك في الاعتبار وعندما يكتشف رمزًا مشبوهًا ، قم بتغييره بيديك
2) اذهب إلى Tagir واطلب منه تقديم الشيك الذي سيقوم بالعمل بدلاً منا
3) قم بإجراء تغييرات على JDK بحيث لا يتغير الرمز على الإطلاق.
بالطبع ، نبدأ مع الثالث. على استعداد لاتخاذ فرصة؟
الهاوية
سوف ندرب على القطط على الكود المصدري لـ Java الحادي عشر ، يمكنك تنزيله من هنا .
تتمثل أبسط الطرق وأكثرها وضوحًا في تحسينها في تحديد سلسلة فرعية موجودة بالفعل داخل طريقة AbstractStringBuilder.append(CharSequence, int, int)
:
أنت الآن بحاجة إلى إنشاء JDK ، وإجراء الاختبارات ، وتشغيل معيار StringBuilderAppendBenchmark::appendBounds
، والذي يجب مقارنة نتائجه بنتائج نفس المؤشر على JDK الأصلي:
# 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
ما يسمى ، فجأة! لم تحدث التحسينات فحسب ، بل حدث تدهور. اللعنة ، ولكن كيف؟
الحقيقة هي أنه في البداية ، في وصف أسلوب StringBuilder::append
قمت بإهمال واحد صغير ولكنه مهم. تم وصف الطريقة مثل هذا:
public final class StringBuilder { @Override public StringBuilder append(String str) { super.append(str); return this; } }
وهنا وجهة نظرها كاملة:
public final class StringBuilder { @Override @HotSpotIntrinsicCandidate public StringBuilder append(String str) { super.append(str); return this; } }
لا يُعد رمز Java الذي درسناه أعلاه ، والذي يتم تسخينه وتجميعه على مستوى C2 ، أمرًا مهمًا ، لأنه لا يتم تنفيذه ، ولكنه جوهري. من السهل إثبات ذلك عن طريق إزالة ملف التعريف باستخدام ملف التعريف غير المتزامن . فيما يلي ، تتم إزالة ملف التعريف length = 1000
و 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
كود StringBuilder
(و AbstractStringBuilder
) لا يشم حتى هنا ، ما يقرب من 3/4 من الملف الشخصي يشغلها مضمن. أرغب في ملاحظة نفس الصورة في ملف تعريف StringBuilder.append(CharSequence, int, int)
"المحسن" الخاص بنا StringBuilder.append(CharSequence, int, int)
.
في الواقع ، لدينا هذا:
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
ستقول: "ها هم ، في جوهرهم ، في القمة!" في الواقع ، هذه فقط ليست هي نفسها في جوهرها (بما في ذلك مقارنة اسم الثانية من أعلاه). أذكر:
public final class StringBuilder { @Override @HotSpotIntrinsicCandidate public StringBuilder append(String str) { super.append(str); return this; } }
هنا يحل محل جوهري الدعوة إلى StringBuilder.append(String)
، ولكن في التصحيح لدينا هذه الدعوة ليست كذلك! دعا AbstractStringBuilder.append(String)
. استدعاء jbyte_disjoint_arraycopy
التي jbyte_disjoint_arraycopy
هي جوهرية ل StringLatin1::inflate
، ودعا من AbstractStringBuider::putStringAt
عبر String::getBytes
. أي أنه ، على عكس StringBuilder::append
لا يعالج فقط النظام الأساسي بل وأيضًا كود جافا ،
فهم سبب الفشل ، حاول أن تنجح بطريقة أخرى. من السهل تخمين أننا بحاجة إلى الرجوع بطريقة ما إلى StringBuilder::append
. يمكنك القيام بذلك عن طريق تمزيق التصحيح السابق وإجراء تغييرات على StringBuilder
نفسه:
public final class StringBuilder {
الآن كل شيء يتم بحكمة: يسمى StringBuilder :: إلحاقي.
إعادة بناء ، تشغيل ، مقارنة:
# 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
أشعر بحزن شديد حقًا ، لكنها لم تتحسن. الآن ملف تعريف جديد:
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
لقد تغير القليل. بالنسبة لي ، يبقى من غير الواضح سبب عدم عمل الجوهر عند الوصول إلى StringBuilder.append(String)
من داخل StringBuilder
. هناك شك في أن لصق (مضمّن) نص أسلوب StringBuilder.append(String)
في نص StringBuilder.append(CharSequence, int, int)
يغير شيئًا ما في معالجة استدعاءات أسلوب VM.
على أي حال ، هذا هو الفشل ، وإخوانه. لم يكن من الممكن تصحيح JDK ، لكن لا يزال بإمكاننا القيام بالبدائل يدويًا حيث يكون ذلك منطقيًا.
فشل التراجع الأدبيجاء تشفير الاستجابة خلال يومين. لا يرغب المستكشف في الانفصال عن Oto Velara ، مع شركة تبني سفن حربية سريعة وقوية بشكل مدهش. لا يريد المستكشف قراءة التشفير لي. إنه ببساطة يكرر الرد من مركز القيادة: "لا". التشفير لا يفسر لماذا "لا". "لا" في أي حال يعني أنه شخص معروف لجهاز كمبيوتر كبير. إذا لم يكن هناك شيء معروف عنه ، فكان الجواب نعم: جربه. سيء للغاية إنه لأمر مؤسف أن تفقد مثل هذا الشخص المثير للاهتمام. والقائد يجب أن يكون آسف بالنسبة لي. ربما المرة الأولى أمر مؤسف. رآني يمزق الفايكنج. وهو لا يريد أن يدفعني إلى الكلاب السلوقية مرة أخرى.
إنه صامت. لكنني أعلم أنه في توفير نقص حاد في العمال:
- أنا ، الرفيق العام ، أعمل في الغد. دعني اذهب
- استمر. - وفجأة تبتسم. "أنت تعرف ، كل سحابة لها بطانة فضية."
"أنا ، الرفيق العام ، أعاني دائمًا من مرض جيد."
"ومن هنا" منعت من مقابلته ، هذا أمر سيء. لكن إلى كنوز تجربتنا ، أضفنا حبة أخرى.
الاستنتاجات:
- لا يرتبط رمز أساليب JDK في بعض الحالات بالتنفيذ الفعلي ، لأنه بدلاً من نص الطريقة ، يمكن تنفيذ جوهري ، والذي يكون مخفيًا في أحشاء جهاز VM.
- يمكن التعرف على مثل هذه الأساليب ، على وجه الخصوص ،
@HotSpotIntrinsicCandidate
علامات @HotSpotIntrinsicCandidate
إليها ، على الرغم من أن بعض الطرق مضمّنة دون أي تلميح ، على سبيل المثال String::equals
( والعديد غيرها ). - الاستنتاج الذي يأتي من الأولين هو أن مناقشتنا لكيفية عمل كود JDK قد تتعارض مع الواقع. C'est la vie
PS
بديل محتمل آخر:
StringBuilder sb = new StringBuilder(); sb.append(str, 0, endIndex);
PPS
يشير مطورو أوراكل بحق إلى ذلك
يبدو لي غريبًا إلى حد ما ومن المستغرب إدخال مسار رمز إلى
sb.append (cs، int، int) الذي يخصص الذاكرة من أجل الحصول على ذلك
فقط في بعض الأحيان يجعل الأمور تعمل بشكل أسرع. كما لاحظت ، والأداء
المفاضلات ليست واضحة.
بدلاً من ذلك ، إذا كنا نريد تحسين sb.append (cs ، int ، int) ربما يجب أن نذهب
إلى الأمام والقيام بذلك ، ربما عن طريق إضافة أو إعادة ترتيب العناصر الداخلية.
الحل المقترح هو StringBuilder.append(CharSequence, int, int)
.
→ المهمة
→ مناقشة
PPS
ومن المثير للاهتمام ، في هذه اللحظة ، عند كتابة شيء من هذا القبيل
StringBuilder sb = new StringBuilder(); sb.append(str.substring(0, endIndex));
"الفكرة" تقترح تبسيط الرمز إلى
StringBuilder sb = new StringBuilder(); sb.append(s, 0, endIndex);
إذا لم يكن الأداء في هذا المكان مهمًا للغاية بالنسبة لك ، فمن المحتمل أن يكون استخدام الإصدار الثاني المبسط أكثر صحة. ومع ذلك ، فإن معظم الشفرة التي نكتبها مخصصة لرفاقنا ، وليس للآلات.