Neden StringBuilder#(int) Java 8'de daha hızlı Java 7 Ekle?
little debate w için araştırma yaparken.r.t. "" n
Integer.toString(int)
bir tamsayı JMH bu microbenchmark yazdığım bir dize için ilkel dönüştürmek için kullanma
@Fork(1)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class IntStr {
protected int counter;
@GenerateMicroBenchmark
public String integerToString() {
return Integer.toString(this.counter );
}
@GenerateMicroBenchmark
public String stringBuilder0() {
return new StringBuilder().append(this.counter ).toString();
}
@GenerateMicroBenchmark
public String stringBuilder1() {
return new StringBuilder().append("").append(this.counter ).toString();
}
@GenerateMicroBenchmark
public String stringBuilder2() {
return new StringBuilder().append("").append(Integer.toString(this.counter )).toString();
}
@GenerateMicroBenchmark
public String stringFormat() {
return String.format("%d", this.counter );
}
@Setup(Level.Iteration)
public void prepareIteration() {
this.counter = 0;
}
}
Linux makinem (güncel 4 64-bit Hiç, Intel Core i7-3770 CPU, 32 GB RAM) var hem Java VM ile varsayılan JMH seçenekleri ile karşılaştım. İlk JVM bir Oracle FIRSATLAR ile birlikte oldu 8u5 64-bit:
java version "1.8.0_05"
Java(TM) SE Runtime Environment (build 1.8.0_05-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode)
Bu JVM ile beklediğim gibi buldum:
Benchmark Mode Samples Mean Mean error Units
b.IntStr.integerToString thrpt 20 32317.048 698.703 ops/ms
b.IntStr.stringBuilder0 thrpt 20 28129.499 421.520 ops/ms
b.IntStr.stringBuilder1 thrpt 20 28106.692 1117.958 ops/ms
b.IntStr.stringBuilder2 thrpt 20 20066.939 1052.937 ops/ms
b.IntStr.stringFormat thrpt 20 2346.452 37.422 ops/ms
I. e. StringBuilder
sınıfını kullanarak StringBuilder
nesne oluşturma ve boş bir dize ekleme ek yükü nedeniyle yavaş. String.format(String, ...)
kullanırken bile daha yavaş, büyüklük sırasına kadar.
Dağıtım-sağlanan derleyici, diğer taraftan, OpenJDK 1.7 dayanır:
java version "1.7.0_55"
OpenJDK Runtime Environment (mageia-2.4.7.1.mga4-x86_64 u55-b13)
OpenJDK 64-Bit Server VM (build 24.51-b03, mixed mode)
Sonuçları buradailginç:
Benchmark Mode Samples Mean Mean error Units
b.IntStr.integerToString thrpt 20 31249.306 881.125 ops/ms
b.IntStr.stringBuilder0 thrpt 20 39486.857 663.766 ops/ms
b.IntStr.stringBuilder1 thrpt 20 41072.058 484.353 ops/ms
b.IntStr.stringBuilder2 thrpt 20 20513.913 466.130 ops/ms
b.IntStr.stringFormat thrpt 20 2068.471 44.964 ops/ms
Neden StringBuilder.append(int)
çok daha hızlı bu JVM ile görünüyor mu? StringBuilder
sınıfı bakarak kaynak kodu özellikle ilginç bir şey ortaya çıkardı söz konusu yöntem Integer#toString(int)
hemen hemen aynıdır. İlginçtir ki, Integer.toString(int)
(stringBuilder2
microbenchmark) sonucu ekleme daha hızlı görünmüyor.
Bu performans farklılığı test donanımı ile ilgili bir sorun? Ya da OpenJDK benim JVM bu özel kod (anti) etkileyecek iyileştirmeler-desen içeriyor mu?
DÜZENLEME:
Düz ileri daha fazla bir karşılaştırma için, 1 ile İLGİLENİYORUZ Oracle kurdum.7u55:
java version "1.7.0_55"
Java(TM) SE Runtime Environment (build 1.7.0_55-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.55-b03, mixed mode)
Sonuçları OpenJDK benzemektedir:
Benchmark Mode Samples Mean Mean error Units
b.IntStr.integerToString thrpt 20 32502.493 501.928 ops/ms
b.IntStr.stringBuilder0 thrpt 20 39592.174 428.967 ops/ms
b.IntStr.stringBuilder1 thrpt 20 40978.633 544.236 ops/ms
Bu daha genel Java 7 vs 8 Java sorunu gibi görünüyor. Daha agresif Java 7 vardı dize iyileştirmeleri belki?
2 DÜZENLEYİN:
Bütünlüğü için, burada bu Jvm'ler de dize ile ilgili VM seçenekler:
Oracle için FIRSATLAR 8u5:
$ /usr/java/default/bin/java -XX: PrintFlagsFinal 2>/dev/null | grep String
bool OptimizeStringConcat = true {C2 product}
intx PerfMaxStringConstLength = 1024 {product}
bool PrintStringTableStatistics = false {product}
uintx StringTableSize = 60013 {product}
1.7: OpenJDK
$ java -XX: PrintFlagsFinal 2>/dev/null | grep String
bool OptimizeStringConcat = true {C2 product}
intx PerfMaxStringConstLength = 1024 {product}
bool PrintStringTableStatistics = false {product}
uintx StringTableSize = 60013 {product}
bool UseStringCache = false {product}
UseStringCache
seçeneği herhangi bir fark yaratır sanmam hiçbir değiştirme ile Java 8'de kaldırıldı. Seçeneklerin geri kalanı aynı ayarlar var görünüyor.
EDİT 3:
src.zip
dosyası, ** 37 ** 38 Integer
sınıfların kaynak kodunun yan yana bir karşılaştırma hiçbir şey noteworty ortaya koymaktadır. Kozmetik ve Dokümantasyon değişiklikleri bir sürü dışında Integer
şimdi işaretsiz tamsayılar için bazı desteği vardır ve StringBuilder
hafif StringBuffer
ile daha fazla kod paylaşmak için tekrar elden geçirildi. Bu değişikliklerin hiçbiri kod yolların bir şey kaçırmış olabilir, ancak StringBuilder#append(int)
tarafından kullanılan etkileyecek gibi görünüyor.
Derleme kod karşılaştırılması IntStr#integerToString()
IntStr#stringBuilder0()
çok daha ilginç için oluşturulan. Kodu temel düzeni IntStr#integerToString()
için oluşturulan Oracle ile İLGİLENİYORUZ 8u5 daha agresif w gibi görünüyordu rağmen hem Jvm'ler için benzer oldu.r.t. bazı satır içi uygulaması Integer#toString(int)
kod içinde çağırır. Java kaynak kodu, daha az montaj deneyimi olan biri için net bir yazışma vardı.
IntStr#stringBuilder0()
ancak için derleme kod radikal bir şekilde farklıydı. Kod Oracle ile İLGİLENİYORUZ 8u5 tarafından oluşturulan bir kez daha doğrudan Java kaynak kodu ile ilişkili olduğu kolayca aynı düzen tanırdım. Tam tersine, kod OpenJDK 7 tarafından oluşturulan ve eğitimsiz (benimki gibi) neredeyse tanınmayacak haldeydi. new StringBuilder()
çağrı StringBuilder
kurucu dizinin oluşturulması gibi görünüşte kaldırıldı. Ek olarak, çözücü eklentisi ile İLGİLENİYORUZ 8'de olduğu gibi kaynak kod olarak bir çok referans vermemeyi başardı.
Ben bu OpenJDK çok daha agresif bir optimizasyon pass 7 veya daha büyük olasılıkla StringBuilder
bazı işlemler için elle yazılmış bir düşük seviye kod ekleme sonucu olduğunu da varsayalım. Bu optimizasyon JVM benim 8 uygulama olmaz neden ya da aynı JVM iyileştirmeleri 7 Integer#toString(int)
hayata geçirilemedi neden emin değilim. Birisi TOTEM kaynak kodunun ilgili bölümleri hakkında bilgi sahibi bu sorulara cevap olurdu sanırım
CEVAP
TL;DR:append
yan etkileri görünüşe göre StringConcat iyileştirmeleri Ara.
Asıl soruya çok iyi analiz ve güncellemeleri!
Bütünlüğü için, aşağıda birkaç eksik bir adım
Hem 7u55 ve 8u5
-XX: PrintInlining
ile bakın. 7u55, sana bir şey gibi görür bu:@ 16 org.sample.IntStr::inlineSideEffect (25 bytes) force inline by CompilerOracle @ 4 java.lang.StringBuilder::<init> (7 bytes) inline (hot) @ 18 java.lang.StringBuilder::append (8 bytes) already compiled into a big method @ 21 java.lang.StringBuilder::toString (17 bytes) inline (hot)
...ve 8u5
@ 16 org.sample.IntStr::inlineSideEffect (25 bytes) force inline by CompilerOracle @ 4 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) @ 18 java.lang.StringBuilder::append (8 bytes) inline (hot) @ 2 java.lang.AbstractStringBuilder::append (62 bytes) already compiled into a big method @ 21 java.lang.StringBuilder::toString (17 bytes) inline (hot) @ 13 java.lang.String::<init> (62 bytes) inline (hot) @ 1 java.lang.Object::<init> (1 bytes) inline (hot) @ 55 java.util.Arrays::copyOfRange (63 bytes) inline (hot) @ 54 java.lang.Math::min (11 bytes) (intrinsic) @ 57 java.lang.System::arraycopy (0 bytes) (intrinsic)
7u55 sürüm sığ olduğunu fark edebilirsiniz, ve hiçbir şey
StringBuilder
yöntemlerden sonra bu dize iyileştirmeleri geçerli iyi bir göstergedir -- denir gibi görünüyor. Eğer-XX:-OptimizeStringConcat
ile 7u55 çalıştırırsanız nitekim subcalls görünecektir ve performans 8u5 düzeyleri düşer.TAMAM, 8u5 aynı optimizasyon yapmaz neden anlamaya ihtiyacımız var. Grep http://hg.openjdk.java.net/jdk9/jdk9/hotspot "" StringConcat optimizasyonu kolları anlamaya; bu
src/share/vm/opto/stringopts.cpp
. içine alacak StringBuilder içinEn son değişiklikleri orada anlamaya
hg log src/share/vm/opto/stringopts.cpp
. Adaylardan biri olabilir:changeset: 5493:90abdd727e64 user: iveresov date: Wed Oct 16 11:13:15 2013 -0700 summary: 8009303: Tiered: incorrect results in VM tests stringconcat...
OpenJDK e-posta listeleri üzerinde inceleme konuları bak () değişiklik özeti için google için yeterince kolay: http://mail.openjdk.java.net/pipermail/hotspot-compiler-dev/2013-October/012084.html
"Dize herhangi bir parametre optimizasyon optimizasyon desen çöker [...] sonucu doğrudan oluşturan bir dize tek bir ayırma ve. nokta Optimize kod olacak her şey mümkün deopts başından bu model (StringBuffer ayırma başlayarak) yeniden başlatın.Bütün desen bana yan etkisi özgür gerektiği anlamına gelir."Eureka?
Zıt kriter yazmak:
@Fork(5) @Warmup(iterations = 5) @Measurement(iterations = 5) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Benchmark) public class IntStr { private int counter; @GenerateMicroBenchmark public String inlineSideEffect() { return new StringBuilder().append(counter ).toString(); } @GenerateMicroBenchmark public String spliceSideEffect() { int cnt = counter ; return new StringBuilder().append(cnt).toString(); } }
İnlined/işlenmiş yan etkileri: aynı performansı görmek 7u55, yapışkan notlar üzerinde ölçün
Benchmark Mode Samples Mean Mean error Units o.s.IntStr.inlineSideEffect avgt 25 65.460 1.747 ns/op o.s.IntStr.spliceSideEffect avgt 25 64.414 1.323 ns/op
İnlined etkisi ile bozulması: performansını görmek 8u5, yapışkan notlar üzerinde ölçün
Benchmark Mode Samples Mean Mean error Units o.s.IntStr.inlineSideEffect avgt 25 84.953 2.274 ns/op o.s.IntStr.spliceSideEffect avgt 25 65.386 1.194 ns/op
Hata raporu (https://bugs.openjdk.java.net/browse/JDK-8043677) VM çocuklar bu davranışı görüşmek üzere gönderin. Özgün düzeltme için gerekçe kaya gibi sağlam, ilginç ancak eğer yapabilirsek/bu gibi önemsiz bazı durumlarda bu en iyi duruma getirme.
???
KAR.
Ve evet, StringBuilder
zincirinden artış taşır kıyaslama sonuçlarını, tüm zincirin önce gönderilmesi gerekiyor. Ayrıca, ortalama süre geçti, ve ns/op. Bu FIRSATLAR 7u55
Benchmark Mode Samples Mean Mean error Units o.s.IntStr.integerToString avgt 25 153.805 1.093 ns/op o.s.IntStr.stringBuilder0 avgt 25 128.284 6.797 ns/op o.s.IntStr.stringBuilder1 avgt 25 131.524 3.116 ns/op o.s.IntStr.stringBuilder2 avgt 25 254.384 9.204 ns/op o.s.IntStr.stringFormat avgt 25 2302.501 103.032 ns/op
Ve bu 8u5
Benchmark Mode Samples Mean Mean error Units o.s.IntStr.integerToString avgt 25 153.032 3.295 ns/op o.s.IntStr.stringBuilder0 avgt 25 127.796 1.158 ns/op o.s.IntStr.stringBuilder1 avgt 25 131.585 1.137 ns/op o.s.IntStr.stringBuilder2 avgt 25 250.980 2.773 ns/op o.s.IntStr.stringFormat avgt 25 2123.706 25.105 ns/op
stringFormat
aslında biraz 8u5 ve diğer testlerin hepsi aynı daha hızlı. Bu hipotezi özgün soru büyük suçlu SB zincire yan etkisi kırılması katılaşır.