SORU
20 Mayıs 2014, Salı


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
21 Mayıs 2014, ÇARŞAMBA


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çin

  • En 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.

Bunu Paylaş:
  • Google+
  • E-Posta
Etiketler:

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • ehowhome

    ehowhome

    22 NİSAN 2009
  • eyes4beautee

    eyes4beautee

    17 HAZİRAN 2011
  • metal571

    metal571

    30 Mayıs 2006

İLGİLİ SORU / CEVAPLAR