Java 9'da String birleştirme nasıl uygulanır?


111

JEP 280'de yazıldığı gibi : Dize Birleştirmesini Belirtin :

JDK kitaplık işlevlerine yapılan çağrıları kullanmak Stringiçin tarafından oluşturulan statik birleştirme bayt kodu sırasını değiştirin . Bu, tarafından yayılan bayt kodunda daha fazla değişiklik yapılmasına gerek kalmadan gelecekteki birleştirme optimizasyonlarını mümkün kılacaktır .javacinvokedynamicStringjavac

İşte kullanılması anlamak istiyoruz invokedynamicaramalar ve nasıl baytkodu birleştirme farklıdır invokedynamic?


11
Bunu bir süre önce yazmıştım - eğer yardımcı olursa, cevabı yoğunlaştıracağım.
Nicolai

10
Ayrıca, yeni dizi birleştirme mekanizmasının amacını
ZhekaKozlov

3
@ZhekaKozlov Keşke yorumunuza iki kez yukarı oy verebilseydim, aslında tüm bunları uygulayan insanlardan gelen bağlantılar en iyisidir.
Eugene

2
@Nicolai: Bu harika olurdu ve buradaki diğerlerinden daha iyi bir cevap olurdu (benimki dahil). Cevabımın herhangi bir bölümünü yaptığınızda dahil etmek istediğiniz herhangi bir kısım, kendinizi özgür hissedin - eğer (temelde) her şeyi daha geniş cevabın bir parçası olarak eklerseniz, sadece benimkini sileceğim. Alternatif olarak, cevabıma sadece görünür olduğu için eklemek isterseniz, onu bir topluluk wiki'si yaptım.
TJ Crowder

Yanıtlar:


95

"Eski" yöntem, bir dizi StringBuilderodaklı işlem üretir . Bu programı düşünün:

public class Example {
    public static void main(String[] args)
    {
        String result = args[0] + "-" + args[1] + "-" + args[2];
        System.out.println(result);
    }
}

Bunu JDK 8 veya öncesi ile derlersek ve sonra javap -c Examplebayt kodunu görmek için kullanırsak , şuna benzer bir şey görürüz:

public class Örneği {
  genel Örnek ();
    Kod:
       0: aload_0
       1: özel # 1 çağrısı // Yöntem java / lang / Object. "<init>" :() V
       4: dönüş

  public static void main (java.lang.String []);
    Kod:
       0: yeni # 2 // sınıf java / lang / StringBuilder
       3: çift
       4: özel # 3'ü çağırın // Yöntem java / lang / StringBuilder. "<init>" :() V
       7: aload_0
       8: iconst_0
       9: aaload
      10: invokevirtual # 4 // Yöntem java / lang / StringBuilder.append: (Ljava / lang / String;) Ljava / lang / StringBuilder;
      13: ldc # 5 // Dize -
      15: invokevirtual # 4 // Yöntem java / lang / StringBuilder.append: (Ljava / lang / String;) Ljava / lang / StringBuilder;
      18: aload_0
      19: iconst_1
      20: aaload
      21: invokevirtual # 4 // Yöntem java / lang / StringBuilder.append: (Ljava / lang / String;) Ljava / lang / StringBuilder;
      24: ldc # 5 // Dize -
      26: invokevirtual # 4 // Yöntem java / lang / StringBuilder.append: (Ljava / lang / String;) Ljava / lang / StringBuilder;
      29: aload_0
      30: iconst_2
      31: aaload
      32: invokevirtual # 4 // Yöntem java / lang / StringBuilder.append: (Ljava / lang / String;) Ljava / lang / StringBuilder;
      35: invokevirtual # 6 // Yöntem java / lang / StringBuilder.toString :() Ljava / lang / String;
      38: astore_1
      39: getstatic # 7 // Alan java / lang / System.out: Ljava / io / PrintStream;
      42: aload_1
      43: invokevirtual # 8 // Yöntem java / io / PrintStream.println: (Ljava / lang / String;) V
      46: dönüş
}

Gördüğünüz gibi, bir yaratır StringBuilderve kullanır append. Bu, yerleşik arabelleğin varsayılan kapasitesi StringBuilderyalnızca 16 karakter olduğu için oldukça verimsizdir ve derleyicinin önceden daha fazla ayırmayı bilmesinin bir yolu yoktur , bu nedenle yeniden tahsis etmek zorunda kalır. Aynı zamanda bir sürü yöntem çağrısı. (JVM'nin bazen bu çağrı kalıplarını daha verimli hale getirmek için algılayıp yeniden yazabileceğini unutmayın .)

Java 9'un ne ürettiğine bakalım:

public class Örneği {
  genel Örnek ();
    Kod:
       0: aload_0
       1: özel # 1 çağrısı // Yöntem java / lang / Object. "<init>" :() V
       4: dönüş

  public static void main (java.lang.String []);
    Kod:
       0: aload_0
       1: iconst_0
       2: aaload
       3: aload_0
       4: iconst_1
       5: aaload
       6: aload_0
       7: iconst_2
       8: aaload
       9: invokedynamic # 2, 0 // InvokeDynamic # 0: makeConcatWithConstants: (Ljava / lang / String; Ljava / lang / String; Ljava / lang / String;) Ljava / lang / String;
      14: astore_1
      15: getstatic # 3 // Alan java / lang / System.out: Ljava / io / PrintStream;
      18: aload_1
      19: invokevirtual # 4 // Yöntem java / io / PrintStream.println: (Ljava / lang / String;) V
      22: dönüş
}

Aman tanrım ama bu daha kısa. :-) Bu tek bir çağrı yapar makeConcatWithConstantsdan StringConcatFactoryonun Javadoc bu diyor:

Muhtemelen tip uyarlamasından ve bağımsız değişkenlerin kısmi değerlendirmesinden sonra, bilinen türde bilinen sayıda bağımsız değişkeni verimli bir şekilde birleştirmek için kullanılabilen Dize birleştirme yöntemlerinin oluşturulmasını kolaylaştıran yöntemler. Bu yöntemler tipik olarak , Java Programlama Dilinin dizi birleştirme özelliğini desteklemek için arama siteleri için önyükleme yöntemleri olarak kullanılır .invokedynamic


41
Bu bana neredeyse 6 yıl önce o güne kadar yazdığım bir cevabı hatırlattı: stackoverflow.com/a/7586780/330057 - Birisi bir StringBuilder mı yapmaları gerektiğini yoksa +=for döngüsünde sadece eski mi kullanmaları gerektiğini sordu . Onlara duruma göre değiştiğini söyledim, ama bir ara yolda bir araya getirmenin daha iyi bir yolunu bulabileceklerini de unutmayalım. Anahtar satır gerçekten sondan bir önceki satır:So by being smart, you have caused a performance hit when Java got smarter than you.
corsiKa

3
@corsiKa: LOL! Ama vay canına, oraya ulaşmak uzun zaman aldı (altı yılı kastetmiyorum, 22 ya da öylesine ... :-))
TJ Crowder

1
@supercat: Anladığım kadarıyla birkaç neden var, en azından performans açısından kritik bir yolda bir yönteme geçmek için bir varargs dizisi oluşturmanın ideal olmaması. Ayrıca, kullanımı invokedynamic, her bir çağrıda bir yöntem çağrısı ve gönderme tablosunun ek yükü olmadan çalışma zamanında farklı birleştirme stratejilerinin seçilmesine ve ilk çağrıda bağlanmasına izin verir; daha Nicolai makalesinde burada ve JEP .
TJ Crowder

1
@supercat: Ve son sonuca dönüştürülmek yerine String'e önceden dönüştürülmeleri gerekeceğinden, String olmayanlarla iyi oynamayacağı gerçeği var; daha fazla verimsizlik. Başarabilirdi Object, ama o zaman tüm ilkelleri kutuya koymanız gerekir ... (Nicolai bu mükemmel makalesinde anlatıyor, btw.)
TJ Crowder

2
@supercat String.concat(String)Uygulaması, sonuçta ortaya çıkan dizenin yerinde dizisini oluşturan zaten var olan yönteme başvuruyordum . toString()Keyfi nesnelere başvurmamız gerektiğinde avantaj tartışılmaz hale gelir . Benzer şekilde, bir diziyi kabul eden bir yöntemi çağırırken, arayan kişi diziyi oluşturmak ve doldurmak zorundadır, bu da genel faydayı azaltır. Ama şimdi, yeni çözüm temelde düşündüğünüz şey olduğu için, bunun dışında kutulama ek yükü olmaması, dizi oluşturmaya ihtiyaç duymaması ve arka uç belirli senaryolar için optimize edilmiş işleyiciler oluşturabilir.
Holger

20

invokedynamicDize birleştirme optimizasyonu için kullanılan uygulamanın ayrıntılarına girmeden önce , bence neyin invokedynamic ve onu nasıl kullanacağım hakkında biraz bilgi edinmesi gerekiyor?

invokedynamic Potansiyel olarak talimat basitleştirir ve JVM üzerinde dinamik diller için derleyici ve çalışma zamanı sistemlerinin uygulamalarını geliştirir . Bunu invokedynamic, aşağıdaki adımları içeren talimatla dil uygulayıcısının özel bağlantı davranışını tanımlamasına izin vererek yapar .


Muhtemelen String birleştirme optimizasyonunun uygulanması için getirilen değişikliklerle sizi bunlardan geçirmeye çalışırdım.

  • Önyükleme Yönteminin Tanımlanması : - Java9 ile, öncelikle dize birleştirmeyi desteklemek için invokedynamicarama siteleri için önyükleme yöntemleri ve uygulamayla birlikte tanıtıldı .makeConcatmakeConcatWithConstantsStringConcatFactory

    İnvokedynamic kullanımı, çalışma zamanına kadar bir çeviri stratejisi seçmek için bir alternatif sağlar. Kullanılan çeviri stratejisi , önceki java sürümünde sunulanla StringConcatFactorybenzerdir LambdaMetafactory. Ek olarak, soruda bahsedilen JEP'in amaçlarından biri de bu stratejileri daha da genişletmektir.

  • Sabit Havuz Girişlerinin Belirtilmesi : - Bunlar, invokedynamictalimat MethodHandles.Lookupbağlamında yöntem tutamaçları oluşturmak için bir fabrika olan (1) nesne invokedynamic, (2) bir Stringnesne, dinamik çağrıda belirtilen yöntem adı dışındaki talimatın ek statik argümanlarıdır. site ve (3) MethodTypenesne, dinamik çağrı sitesinin çözümlenmiş tip imzası.

    Kodun bağlanması sırasında zaten bağlanmış. Çalışma zamanında, önyükleme yöntemi çalışır ve birleştirmeyi yapan gerçek kodda bağlanır. invokedynamicAramayı uygun bir arama ile yeniden yazar invokestatic. Bu, sabit dizeyi sabit havuzdan yükler, bu ve diğer sabitleri doğrudan önyükleme yöntemi çağrısına geçirmek için önyükleme yöntemi statik bağımsız değişkenlerinden yararlanılır.

  • Çağrılan dinamik talimatı kullanma : - Bu, ilk çağrı sırasında çağrı hedefini bir kez önyükleme araçları sağlayarak tembel bağlantı için olanaklar sunar. Buradaki optimizasyon için somut fikir, tüm StringBuilder.appenddansı , birleştirme ihtiyacındaki değerleri kabul edecek basit bir invokedynamicçağrı ile değiştirmektir java.lang.invoke.StringConcatFactory.

Indify Concatenation bir örnekle öneri durumları tarafından paylaşılan benzer bir yöntem Java9 ile uygulama kıyaslama @TJ Crowder'ın derlenir ve bayt arasındaki fark olarak değiştirilmesi, uygulama arasında oldukça görünür.


17

Buraya biraz ayrıntı ekleyeceğim. Alınması gereken ana kısım, dize birleştirmenin nasıl yapıldığının , artık bir derleme zamanı değil, çalışma zamanı kararı olduğudur . Böylece değişebilir, yani kodunuzu java-9'a karşı bir kez derlemişsinizdir ve yeniden derlemeye gerek kalmadan temeldeki uygulamayı istediği gibi değiştirebilir.

İkinci nokta ise şu anda şunlar var 6 possible strategies for concatenation of String:

 private enum Strategy {
    /**
     * Bytecode generator, calling into {@link java.lang.StringBuilder}.
     */
    BC_SB,

    /**
     * Bytecode generator, calling into {@link java.lang.StringBuilder};
     * but trying to estimate the required storage.
     */
    BC_SB_SIZED,

    /**
     * Bytecode generator, calling into {@link java.lang.StringBuilder};
     * but computing the required storage exactly.
     */
    BC_SB_SIZED_EXACT,

    /**
     * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
     * This strategy also tries to estimate the required storage.
     */
    MH_SB_SIZED,

    /**
     * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
     * This strategy also estimate the required storage exactly.
     */
    MH_SB_SIZED_EXACT,

    /**
     * MethodHandle-based generator, that constructs its own byte[] array from
     * the arguments. It computes the required storage exactly.
     */
    MH_INLINE_SIZED_EXACT
}

Bir parametresi aracılığıyla bunlardan herhangi birini seçebilirsiniz: -Djava.lang.invoke.stringConcat. Bunun StringBuilderhala bir seçenek olduğuna dikkat edin .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.