เมื่อเร็ว ๆ นี้ฉันพบปัญหาเกี่ยวกับการต่อข้อมูลสตริง เกณฑ์มาตรฐานนี้สรุป:
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class BrokenConcatenationBenchmark {
@Benchmark
public String slow(Data data) {
final Class<? extends Data> clazz = data.clazz;
return "class " + clazz.getName();
}
@Benchmark
public String fast(Data data) {
final Class<? extends Data> clazz = data.clazz;
final String clazzName = clazz.getName();
return "class " + clazzName;
}
@State(Scope.Thread)
public static class Data {
final Class<? extends Data> clazz = getClass();
@Setup
public void setup() {
//explicitly load name via native method Class.getName0()
clazz.getName();
}
}
}
บน JDK 1.8.0_222 (OpenJDK 64- บิตเซิร์ฟเวอร์ VM, 25.222-b10) ฉันได้รับผลลัพธ์ดังต่อไปนี้:
Benchmark Mode Cnt Score Error Units
BrokenConcatenationBenchmark.fast avgt 25 22,253 ± 0,962 ns/op
BrokenConcatenationBenchmark.fast:·gc.alloc.rate avgt 25 9824,603 ± 400,088 MB/sec
BrokenConcatenationBenchmark.fast:·gc.alloc.rate.norm avgt 25 240,000 ± 0,001 B/op
BrokenConcatenationBenchmark.fast:·gc.churn.PS_Eden_Space avgt 25 9824,162 ± 397,745 MB/sec
BrokenConcatenationBenchmark.fast:·gc.churn.PS_Eden_Space.norm avgt 25 239,994 ± 0,522 B/op
BrokenConcatenationBenchmark.fast:·gc.churn.PS_Survivor_Space avgt 25 0,040 ± 0,011 MB/sec
BrokenConcatenationBenchmark.fast:·gc.churn.PS_Survivor_Space.norm avgt 25 0,001 ± 0,001 B/op
BrokenConcatenationBenchmark.fast:·gc.count avgt 25 3798,000 counts
BrokenConcatenationBenchmark.fast:·gc.time avgt 25 2241,000 ms
BrokenConcatenationBenchmark.slow avgt 25 54,316 ± 1,340 ns/op
BrokenConcatenationBenchmark.slow:·gc.alloc.rate avgt 25 8435,703 ± 198,587 MB/sec
BrokenConcatenationBenchmark.slow:·gc.alloc.rate.norm avgt 25 504,000 ± 0,001 B/op
BrokenConcatenationBenchmark.slow:·gc.churn.PS_Eden_Space avgt 25 8434,983 ± 198,966 MB/sec
BrokenConcatenationBenchmark.slow:·gc.churn.PS_Eden_Space.norm avgt 25 503,958 ± 1,000 B/op
BrokenConcatenationBenchmark.slow:·gc.churn.PS_Survivor_Space avgt 25 0,127 ± 0,011 MB/sec
BrokenConcatenationBenchmark.slow:·gc.churn.PS_Survivor_Space.norm avgt 25 0,008 ± 0,001 B/op
BrokenConcatenationBenchmark.slow:·gc.count avgt 25 3789,000 counts
BrokenConcatenationBenchmark.slow:·gc.time avgt 25 2245,000 ms
ดูเหมือนว่าปัญหาที่คล้ายกับJDK-8043677ซึ่งการแสดงออกที่มีผลข้างเคียงทำให้การเพิ่มประสิทธิภาพของStringBuilder.append().append().toString()
โซ่ใหม่ลดลง แต่โค้ดของClass.getName()
ตัวเองดูเหมือนจะไม่มีผลข้างเคียงใด ๆ :
private transient String name;
public String getName() {
String name = this.name;
if (name == null) {
this.name = name = this.getName0();
}
return name;
}
private native String getName0();
สิ่งที่น่าสงสัยเพียงอย่างเดียวที่นี่คือการเรียกใช้วิธีการเนทีฟซึ่งเกิดขึ้นในความเป็นจริงเพียงครั้งเดียวและผลลัพธ์ของมันจะถูกแคชไว้ในฟิลด์ของชั้นเรียน ในเกณฑ์มาตรฐานของฉันฉันได้แคชไว้อย่างชัดเจนในวิธีการตั้งค่า
ฉันคาดว่าเครื่องมือทำนายสาขาจะคิดออกว่าในการเรียกใช้เบนช์มาร์กแต่ละครั้งมูลค่าจริงของชื่อนี้จะไม่เป็นโมฆะและทำให้การแสดงออกทั้งหมดดีที่สุด
อย่างไรก็ตามในขณะที่BrokenConcatenationBenchmark.fast()
ฉันมีสิ่งนี้:
@ 19 tsypanov.strings.benchmark.concatenation.BrokenConcatenationBenchmark::fast (30 bytes) force inline by CompileCommand
@ 6 java.lang.Class::getName (18 bytes) inline (hot)
@ 14 java.lang.Class::initClassName (0 bytes) native method
@ 14 java.lang.StringBuilder::<init> (7 bytes) inline (hot)
@ 19 java.lang.StringBuilder::append (8 bytes) inline (hot)
@ 23 java.lang.StringBuilder::append (8 bytes) inline (hot)
@ 26 java.lang.StringBuilder::toString (35 bytes) inline (hot)
เช่นคอมไพเลอร์สามารถอินไลน์ทุกอย่างเพราะBrokenConcatenationBenchmark.slow()
มันแตกต่าง:
@ 19 tsypanov.strings.benchmark.concatenation.BrokenConcatenationBenchmark::slow (28 bytes) force inline by CompilerOracle
@ 9 java.lang.StringBuilder::<init> (7 bytes) inline (hot)
@ 3 java.lang.AbstractStringBuilder::<init> (12 bytes) inline (hot)
@ 1 java.lang.Object::<init> (1 bytes) inline (hot)
@ 14 java.lang.StringBuilder::append (8 bytes) inline (hot)
@ 2 java.lang.AbstractStringBuilder::append (50 bytes) inline (hot)
@ 10 java.lang.String::length (6 bytes) inline (hot)
@ 21 java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes) inline (hot)
@ 17 java.lang.AbstractStringBuilder::newCapacity (39 bytes) inline (hot)
@ 20 java.util.Arrays::copyOf (19 bytes) inline (hot)
@ 11 java.lang.Math::min (11 bytes) (intrinsic)
@ 14 java.lang.System::arraycopy (0 bytes) (intrinsic)
@ 35 java.lang.String::getChars (62 bytes) inline (hot)
@ 58 java.lang.System::arraycopy (0 bytes) (intrinsic)
@ 18 java.lang.Class::getName (21 bytes) inline (hot)
@ 11 java.lang.Class::getName0 (0 bytes) native method
@ 21 java.lang.StringBuilder::append (8 bytes) inline (hot)
@ 2 java.lang.AbstractStringBuilder::append (50 bytes) inline (hot)
@ 10 java.lang.String::length (6 bytes) inline (hot)
@ 21 java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes) inline (hot)
@ 17 java.lang.AbstractStringBuilder::newCapacity (39 bytes) inline (hot)
@ 20 java.util.Arrays::copyOf (19 bytes) inline (hot)
@ 11 java.lang.Math::min (11 bytes) (intrinsic)
@ 14 java.lang.System::arraycopy (0 bytes) (intrinsic)
@ 35 java.lang.String::getChars (62 bytes) inline (hot)
@ 58 java.lang.System::arraycopy (0 bytes) (intrinsic)
@ 24 java.lang.StringBuilder::toString (17 bytes) inline (hot)
ดังนั้นคำถามคือว่านี่เป็นพฤติกรรมที่เหมาะสมของ JVM หรือคอมไพเลอร์บั๊กหรือไม่?
ฉันถามคำถามเพราะบางโครงการยังคงใช้ Java 8 อยู่และถ้ามันไม่ได้รับการแก้ไขในการอัพเดทใด ๆ กับฉันมันสมเหตุสมผลที่จะยกการโทรClass.getName()
ด้วยตนเองจากฮอตสปอต
PS บน JDK ล่าสุด (11, 13, 14-eap) ปัญหาไม่ได้ถูกทำซ้ำ
Class.getName()
และในsetUp()
วิธีการไม่ได้อยู่ในร่างของเกณฑ์มาตรฐาน
this.name
การกำหนดไป