Bitişik girişlerdeki Java anahtarı neden ek vakalarla daha hızlı çalışıyor gibi görünüyor?


276

Ana program mantığımda birçok noktada çağrılan sıcak işlevlerde çalışacağı için son derece optimize edilmesi gereken bazı Java kodları üzerinde çalışıyorum. Bu kodun bir kısmı, doubledeğişkenlerin 10keyfi negatif olmayanlara yükseltilmiş olarak çarpılmasını içerirint exponent . Bir Kısa yol: çarpılan değer etmektir almak için (düzenleme ancak mümkün olan en hızlı aşağıya Güncelleme 2 bakınız) switchüzerinde exponent:

double multiplyByPowerOfTen(final double d, final int exponent) {
   switch (exponent) {
      case 0:
         return d;
      case 1:
         return d*10;
      case 2:
         return d*100;
      // ... same pattern
      case 9:
         return d*1000000000;
      case 10:
         return d*10000000000L;
      // ... same pattern with long literals
      case 18:
         return d*1000000000000000000L;
      default:
         throw new ParseException("Unhandled power of ten " + power, 0);
   }
}

Yukarıdaki yorumlanan elipsler case intsabitlerin 1 artmaya devam ettiğini gösterir , bu nedenle caseyukarıdaki kod snippet'inde gerçekten 19 sn vardır. Emin Aslında 10 tüm yetkilerini ihtiyaç olup olmayacağını olmadığı için casetablolarda 10aracılığıyla 18Ben bununla tam 10 milyon operasyonların süresini karşılaştıran bazı microbenchmarks ran switchbir karşı açıklamada switchyalnızca cases 0aracılığıyla9 ile ( exponentdaha az 9 veya sınırlı parçalanmış kırmaktan kaçının switch). Daha switchfazla caseifade ile daha uzun aslında daha hızlı koştu oldukça şaşırtıcı (benim için, en azından!) Sonuç var .

Bir toygarda, casesadece kukla değerleri döndüren daha fazla s eklemeye çalıştım ve anahtarı yaklaşık 22-27 bildirilen cases ile daha hızlı çalıştırmak için alabileceğimi buldum (kod çalışırken bu kukla durumlar aslında vurulmasa bile) ). (Yine, caseönceki casesabiti arttırarak bitişik bir şekilde s eklenmiştir 1.) Bu yürütme süresi farklılıkları çok önemli değildir: ve exponentarasındaki rastgele bir için , kukla dolgulu ifade, 1.49 saniyede 10 milyon yürütmeyi bitirmeden 1.54 saniyede bitirir. sürüm, yürütme başına toplam 5ns büyük tasarruf için. Yani, doldurma üzerine takıntı yapan bir şey değil010switchswitchoptimizasyon açısından çabaya değer ifadesi. Ama yine de meraklı ve karşı-sezgisel buluyorum, a daha fazla s eklendiğinde yürütmek için switchyavaşlamak (veya belki de en iyi sabit O (1) zamanını korumak ) olmazcase .

kıyaslama sonuçlarını değiştir

Bunlar, rasgele üretilen exponentdeğerler üzerinde çeşitli sınırlarla çalışarak elde ettiğim sonuçlardır . Sonuçları sonuna kadar dahil etmedim1 için exponentsınırın ancak eğrinin genel şekli 12-17 vaka işareti etrafında bir sırt ve 18-28 arasında bir vadi ile aynı kalır. Tüm testler, aynı test girişlerini sağlamak için rasgele değerler için paylaşılan kaplar kullanılarak JUnitBenchmarks'da gerçekleştirildi. Testleri switch, siparişle ilgili test problemleri olasılığını ortadan kaldırmak için hem en uzun ifadeden en kısa düzeye, hem de tam tersi şekilde yaptım . Herkes bu sonuçları çoğaltmak istiyorsa test kodumu bir github deposuna koydum.

Peki, burada neler oluyor? Mimarimin veya mikro-ölçüt yapımın bazı değişkenleri? Veya Java switchiçinde yürütmek için biraz daha hızlı gerçekten 18kadar 28 caseondan daha aralığına 11kadar 17?

github test repo "anahtar denemesi"

GÜNCELLEME: Kıyaslama kütüphanesini biraz temizledim ve daha geniş bir olası exponentdeğerler aralığında çıktı içeren / sonuçlara bir metin dosyası ekledim . Ben de bir atmak için değil test kodunda bir seçenek eklendi Exceptiongelen default, ancak bu sonuçlarını etkileyebilir görünmüyor.

GÜNCELLEME 2: 2009'da xkcd forumunda bu konuyla ilgili oldukça iyi bir tartışma bulundu: http://forums.xkcd.com/viewtopic.php?f=11&t=33524 . OP'nin kullanımı tartışması Array.binarySearch()bana yukarıdaki üstel örüntü dizisinin basit bir dizi tabanlı uygulaması fikrini verdi. İkili aramaya gerek yok, çünkü girdilerin ne olduğunu biliyorum array. switchAçıkça sağlanan kontrol akışının bazılarının pahasına, kullanımdan yaklaşık 3 kat daha hızlı çalışıyor gibi görünüyor switch. Bu kod github deposuna da eklendi.


64
Artık her yerdeki tüm Google çalışanlarının switchaçık bir şekilde en uygun çözüm olduğu için tüm ifadelerde tam olarak 22 vaka olacak . : D (Bunu benim yoluma gösterme, lütfen.)
asteri

2
Daha basit bir SSCCE'niz var mı? Bu benim için derlenmiyor. Java performansı kadar zayıfım, buna bir şans vermek istiyorum.
Gizemli

5
Dize tabanlı durumlar hakkındaki cevabımda "JVM'deki anahtarlar" bölümünü yararlı bulabilirsiniz. Ben burada ne oluyor bir geçiş olduğunu düşünüyorum lookupswitcha tableswitch. Kodunuzun sökülmesi size javapkesin olarak gösterecektir.
erickson

2
Depodaki / lib klasörüne bağımlılık kavanozları ekledim. @Mysticial Üzgünüm, bu tavşan deliğinden aşağıya çok fazla zaman harcadım! "AbstractBenchmark'ı genişletir" test sınıflarından çıkarır ve "com.carrotsearch" ithalatlarından kurtulursanız, yalnızca JUnit bağımlılığıyla çalışabilirsiniz, ancak carrotsearch şeyler JIT'ten gelen bazı gürültüleri filtrelemek için oldukça güzel ve ısınma süreleri. Ne yazık ki bu JUnit testlerini IntelliJ dışında nasıl yapacağımı bilmiyorum.
Andrew Bissell

2
@AndrewBissell Sonuçlarınızı çok daha basit bir ölçütle kopyalamayı başardım. Küçük ve orta büyüklükteki performans için şube ve tablo biraz açık bir tahmindi. Ama 30 davanın içerisine girme konusunda herkesten daha iyi bir
görüşüm yok

Yanıtlar:


228

Diğer cevabın işaret ettiği gibi , vaka değerleri bitişik olduğundan (seyrek olarak), çeşitli testleriniz için oluşturulan bayt kodu bir anahtar tablosu kullanır (bayt kodu talimatı)tableswitch ) kullanır.

Ancak, JIT işine başladığında ve bayt kodunu derlemeye derlediğinde, tableswitchtalimat her zaman bir işaretçi dizisi ile sonuçlanmaz: bazen anahtar tablosu neye benzediğine dönüşür lookupswitch( if/ else ifyapısına benzer ).

JIT (hotspot JDK 1.7) tarafından oluşturulan montajın ayrıştırılması, 17 veya daha az vaka olduğunda, 18'den fazla (daha verimli) olduğunda bir dizi işaretçi olup olmadığını art arda kullandığını gösterir.

Bu sihirli sayının 18 kullanılmasının nedeni MinJumpTableSizeJVM bayrağının varsayılan değerine ( kodda 352 satır civarında) iniyor gibi görünüyor .

Hotspot derleyici listesindeki sorunu gündeme getirdim ve geçmiş testlerin mirası gibi görünüyor . Daha fazla karşılaştırma yapıldıktan sonra bu varsayılan değerin JDK 8'de kaldırıldığını unutmayın .

Son olarak, yöntem çok uzun hale geldiğinde (testlerimde> 25 vaka), artık varsayılan JVM ayarları ile çizgiselleştirilmez - bu, o noktada performans düşüşünün en olası sebebidir.


5 vakada, ayrıştırılmış kod şöyle görünür (cmp / je / jg / jmp talimatlarını, if / goto için montajı dikkat edin):

[Verified Entry Point]
  # {method} 'multiplyByPowerOfTen' '(DI)D' in 'javaapplication4/Test1'
  # parm0:    xmm0:xmm0   = double
  # parm1:    rdx       = int
  #           [sp+0x20]  (sp of caller)
  0x00000000024f0160: mov    DWORD PTR [rsp-0x6000],eax
                                                ;   {no_reloc}
  0x00000000024f0167: push   rbp
  0x00000000024f0168: sub    rsp,0x10           ;*synchronization entry
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@-1 (line 56)
  0x00000000024f016c: cmp    edx,0x3
  0x00000000024f016f: je     0x00000000024f01c3
  0x00000000024f0171: cmp    edx,0x3
  0x00000000024f0174: jg     0x00000000024f01a5
  0x00000000024f0176: cmp    edx,0x1
  0x00000000024f0179: je     0x00000000024f019b
  0x00000000024f017b: cmp    edx,0x1
  0x00000000024f017e: jg     0x00000000024f0191
  0x00000000024f0180: test   edx,edx
  0x00000000024f0182: je     0x00000000024f01cb
  0x00000000024f0184: mov    ebp,edx
  0x00000000024f0186: mov    edx,0x17
  0x00000000024f018b: call   0x00000000024c90a0  ; OopMap{off=48}
                                                ;*new  ; - javaapplication4.Test1::multiplyByPowerOfTen@72 (line 83)
                                                ;   {runtime_call}
  0x00000000024f0190: int3                      ;*new  ; - javaapplication4.Test1::multiplyByPowerOfTen@72 (line 83)
  0x00000000024f0191: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffffa7]        # 0x00000000024f0140
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@52 (line 62)
                                                ;   {section_word}
  0x00000000024f0199: jmp    0x00000000024f01cb
  0x00000000024f019b: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffff8d]        # 0x00000000024f0130
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@46 (line 60)
                                                ;   {section_word}
  0x00000000024f01a3: jmp    0x00000000024f01cb
  0x00000000024f01a5: cmp    edx,0x5
  0x00000000024f01a8: je     0x00000000024f01b9
  0x00000000024f01aa: cmp    edx,0x5
  0x00000000024f01ad: jg     0x00000000024f0184  ;*tableswitch
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
  0x00000000024f01af: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffff81]        # 0x00000000024f0138
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@64 (line 66)
                                                ;   {section_word}
  0x00000000024f01b7: jmp    0x00000000024f01cb
  0x00000000024f01b9: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffff67]        # 0x00000000024f0128
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@70 (line 68)
                                                ;   {section_word}
  0x00000000024f01c1: jmp    0x00000000024f01cb
  0x00000000024f01c3: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffff55]        # 0x00000000024f0120
                                                ;*tableswitch
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
                                                ;   {section_word}
  0x00000000024f01cb: add    rsp,0x10
  0x00000000024f01cf: pop    rbp
  0x00000000024f01d0: test   DWORD PTR [rip+0xfffffffffdf3fe2a],eax        # 0x0000000000430000
                                                ;   {poll_return}
  0x00000000024f01d6: ret    

18 vakada, montaj şu şekilde görünür (kullanılan işaretçi dizisine dikkat edin ve tüm karşılaştırmalara olan ihtiyacı bastırır: jmp QWORD PTR [r8+r10*1]doğrudan doğru çarpmaya atlar) - performans iyileştirmesinin olası nedeni budur:

[Verified Entry Point]
  # {method} 'multiplyByPowerOfTen' '(DI)D' in 'javaapplication4/Test1'
  # parm0:    xmm0:xmm0   = double
  # parm1:    rdx       = int
  #           [sp+0x20]  (sp of caller)
  0x000000000287fe20: mov    DWORD PTR [rsp-0x6000],eax
                                                ;   {no_reloc}
  0x000000000287fe27: push   rbp
  0x000000000287fe28: sub    rsp,0x10           ;*synchronization entry
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@-1 (line 56)
  0x000000000287fe2c: cmp    edx,0x13
  0x000000000287fe2f: jae    0x000000000287fe46
  0x000000000287fe31: movsxd r10,edx
  0x000000000287fe34: shl    r10,0x3
  0x000000000287fe38: movabs r8,0x287fd70       ;   {section_word}
  0x000000000287fe42: jmp    QWORD PTR [r8+r10*1]  ;*tableswitch
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
  0x000000000287fe46: mov    ebp,edx
  0x000000000287fe48: mov    edx,0x31
  0x000000000287fe4d: xchg   ax,ax
  0x000000000287fe4f: call   0x00000000028590a0  ; OopMap{off=52}
                                                ;*new  ; - javaapplication4.Test1::multiplyByPowerOfTen@202 (line 96)
                                                ;   {runtime_call}
  0x000000000287fe54: int3                      ;*new  ; - javaapplication4.Test1::multiplyByPowerOfTen@202 (line 96)
  0x000000000287fe55: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe8b]        # 0x000000000287fce8
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@194 (line 92)
                                                ;   {section_word}
  0x000000000287fe5d: jmp    0x000000000287ff16
  0x000000000287fe62: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe86]        # 0x000000000287fcf0
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@188 (line 90)
                                                ;   {section_word}
  0x000000000287fe6a: jmp    0x000000000287ff16
  0x000000000287fe6f: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe81]        # 0x000000000287fcf8
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@182 (line 88)
                                                ;   {section_word}
  0x000000000287fe77: jmp    0x000000000287ff16
  0x000000000287fe7c: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe7c]        # 0x000000000287fd00
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@176 (line 86)
                                                ;   {section_word}
  0x000000000287fe84: jmp    0x000000000287ff16
  0x000000000287fe89: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe77]        # 0x000000000287fd08
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@170 (line 84)
                                                ;   {section_word}
  0x000000000287fe91: jmp    0x000000000287ff16
  0x000000000287fe96: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe72]        # 0x000000000287fd10
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@164 (line 82)
                                                ;   {section_word}
  0x000000000287fe9e: jmp    0x000000000287ff16
  0x000000000287fea0: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe70]        # 0x000000000287fd18
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@158 (line 80)
                                                ;   {section_word}
  0x000000000287fea8: jmp    0x000000000287ff16
  0x000000000287feaa: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe6e]        # 0x000000000287fd20
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@152 (line 78)
                                                ;   {section_word}
  0x000000000287feb2: jmp    0x000000000287ff16
  0x000000000287feb4: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe24]        # 0x000000000287fce0
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@146 (line 76)
                                                ;   {section_word}
  0x000000000287febc: jmp    0x000000000287ff16
  0x000000000287febe: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe6a]        # 0x000000000287fd30
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@140 (line 74)
                                                ;   {section_word}
  0x000000000287fec6: jmp    0x000000000287ff16
  0x000000000287fec8: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe68]        # 0x000000000287fd38
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@134 (line 72)
                                                ;   {section_word}
  0x000000000287fed0: jmp    0x000000000287ff16
  0x000000000287fed2: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe66]        # 0x000000000287fd40
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@128 (line 70)
                                                ;   {section_word}
  0x000000000287feda: jmp    0x000000000287ff16
  0x000000000287fedc: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe64]        # 0x000000000287fd48
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@122 (line 68)
                                                ;   {section_word}
  0x000000000287fee4: jmp    0x000000000287ff16
  0x000000000287fee6: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe62]        # 0x000000000287fd50
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@116 (line 66)
                                                ;   {section_word}
  0x000000000287feee: jmp    0x000000000287ff16
  0x000000000287fef0: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe60]        # 0x000000000287fd58
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@110 (line 64)
                                                ;   {section_word}
  0x000000000287fef8: jmp    0x000000000287ff16
  0x000000000287fefa: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe5e]        # 0x000000000287fd60
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@104 (line 62)
                                                ;   {section_word}
  0x000000000287ff02: jmp    0x000000000287ff16
  0x000000000287ff04: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe5c]        # 0x000000000287fd68
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@98 (line 60)
                                                ;   {section_word}
  0x000000000287ff0c: jmp    0x000000000287ff16
  0x000000000287ff0e: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe12]        # 0x000000000287fd28
                                                ;*tableswitch
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
                                                ;   {section_word}
  0x000000000287ff16: add    rsp,0x10
  0x000000000287ff1a: pop    rbp
  0x000000000287ff1b: test   DWORD PTR [rip+0xfffffffffd9b00df],eax        # 0x0000000000230000
                                                ;   {poll_return}
  0x000000000287ff21: ret    

Ve son olarak 30 vakaya sahip (aşağıda) movapd xmm0,xmm1, @cHao tarafından tespit edildiği gibi , kodun ortasına doğru görünen ek hariç, 18 vakaya benziyor - ancak performans düşüşünün en olası nedeni, yöntemin de varsayılan JVM ayarlarıyla satır içi uzun:

[Verified Entry Point]
  # {method} 'multiplyByPowerOfTen' '(DI)D' in 'javaapplication4/Test1'
  # parm0:    xmm0:xmm0   = double
  # parm1:    rdx       = int
  #           [sp+0x20]  (sp of caller)
  0x0000000002524560: mov    DWORD PTR [rsp-0x6000],eax
                                                ;   {no_reloc}
  0x0000000002524567: push   rbp
  0x0000000002524568: sub    rsp,0x10           ;*synchronization entry
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@-1 (line 56)
  0x000000000252456c: movapd xmm1,xmm0
  0x0000000002524570: cmp    edx,0x1f
  0x0000000002524573: jae    0x0000000002524592  ;*tableswitch
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
  0x0000000002524575: movsxd r10,edx
  0x0000000002524578: shl    r10,0x3
  0x000000000252457c: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe3c]        # 0x00000000025243c0
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@364 (line 118)
                                                ;   {section_word}
  0x0000000002524584: movabs r8,0x2524450       ;   {section_word}
  0x000000000252458e: jmp    QWORD PTR [r8+r10*1]  ;*tableswitch
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
  0x0000000002524592: mov    ebp,edx
  0x0000000002524594: mov    edx,0x31
  0x0000000002524599: xchg   ax,ax
  0x000000000252459b: call   0x00000000024f90a0  ; OopMap{off=64}
                                                ;*new  ; - javaapplication4.Test1::multiplyByPowerOfTen@370 (line 120)
                                                ;   {runtime_call}
  0x00000000025245a0: int3                      ;*new  ; - javaapplication4.Test1::multiplyByPowerOfTen@370 (line 120)
  0x00000000025245a1: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe27]        # 0x00000000025243d0
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@358 (line 116)
                                                ;   {section_word}
  0x00000000025245a9: jmp    0x0000000002524744
  0x00000000025245ae: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe22]        # 0x00000000025243d8
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@348 (line 114)
                                                ;   {section_word}
  0x00000000025245b6: jmp    0x0000000002524744
  0x00000000025245bb: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe1d]        # 0x00000000025243e0
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@338 (line 112)
                                                ;   {section_word}
  0x00000000025245c3: jmp    0x0000000002524744
  0x00000000025245c8: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe18]        # 0x00000000025243e8
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@328 (line 110)
                                                ;   {section_word}
  0x00000000025245d0: jmp    0x0000000002524744
  0x00000000025245d5: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe13]        # 0x00000000025243f0
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@318 (line 108)
                                                ;   {section_word}
  0x00000000025245dd: jmp    0x0000000002524744
  0x00000000025245e2: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe0e]        # 0x00000000025243f8
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@308 (line 106)
                                                ;   {section_word}
  0x00000000025245ea: jmp    0x0000000002524744
  0x00000000025245ef: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe09]        # 0x0000000002524400
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@298 (line 104)
                                                ;   {section_word}
  0x00000000025245f7: jmp    0x0000000002524744
  0x00000000025245fc: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe04]        # 0x0000000002524408
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@288 (line 102)
                                                ;   {section_word}
  0x0000000002524604: jmp    0x0000000002524744
  0x0000000002524609: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffdff]        # 0x0000000002524410
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@278 (line 100)
                                                ;   {section_word}
  0x0000000002524611: jmp    0x0000000002524744
  0x0000000002524616: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffdfa]        # 0x0000000002524418
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@268 (line 98)
                                                ;   {section_word}
  0x000000000252461e: jmp    0x0000000002524744
  0x0000000002524623: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffd9d]        # 0x00000000025243c8
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@258 (line 96)
                                                ;   {section_word}
  0x000000000252462b: jmp    0x0000000002524744
  0x0000000002524630: movapd xmm0,xmm1
  0x0000000002524634: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe0c]        # 0x0000000002524448
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@242 (line 92)
                                                ;   {section_word}
  0x000000000252463c: jmp    0x0000000002524744
  0x0000000002524641: movapd xmm0,xmm1
  0x0000000002524645: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffddb]        # 0x0000000002524428
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@236 (line 90)
                                                ;   {section_word}
  0x000000000252464d: jmp    0x0000000002524744
  0x0000000002524652: movapd xmm0,xmm1
  0x0000000002524656: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffdd2]        # 0x0000000002524430
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@230 (line 88)
                                                ;   {section_word}
  0x000000000252465e: jmp    0x0000000002524744
  0x0000000002524663: movapd xmm0,xmm1
  0x0000000002524667: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffdc9]        # 0x0000000002524438
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@224 (line 86)
                                                ;   {section_word}

[etc.]

  0x0000000002524744: add    rsp,0x10
  0x0000000002524748: pop    rbp
  0x0000000002524749: test   DWORD PTR [rip+0xfffffffffde1b8b1],eax        # 0x0000000000340000
                                                ;   {poll_return}
  0x000000000252474f: ret    

7
@ syb0rg Dürüst olmak gerekirse, ben de ince ayrıntıları anlamıyorum ;-)
assylias

4
Harika cevap için +1! Performansın OP grafiğindeki "dip" ten ne zaman çıkacağını karşılaştırmak için 30'dan fazla vaka içeren bir şeyi sökebilir misiniz?
asteri


2
@AndrewBissell Tahminim, farklı davranışın (i) işaretçi dizisinin yalnızca vaka sayısının 18'den büyük olduğu veya (ii) kodun çalıştırılır ve profiler çalışma zamanında hangi yaklaşımın daha iyi olduğunu belirler. Cevabı bulamıyorum.
Mart'ta Assylias

3
30-kasalı sökme ve 18-kasalı sökme çoğunlukla aynı görünür. Farklılıklar çoğunlukla yaklaşık 11. JITter'ın bunu neden yaptığını söyleyemem; gereksiz görünüyor.
cHao

46

Durum değerleri dar bir aralığa yerleştirilirse durum daha hızlıdır.

case 1:
case 2:
case 3:
..
..
case n:

Çünkü bu durumda derleyici, switch deyimindeki her vaka ayağı için bir karşılaştırma yapmaktan kaçınabilir. Derleyici, farklı ayaklarda yapılacak eylemlerin adreslerini içeren bir atlama tablosu oluşturur. Anahtarın gerçekleştirildiği değer, anahtarı bir dizine dönüştürmek için değiştirilir.jump table . Bu uygulamada, switch deyiminde geçen süre, eşdeğer if-else-if deyimi basamaklamasında geçen süreden çok daha azdır. Ayrıca, switch deyiminde geçen süre, switch deyimindeki vaka bacaklarının sayısından bağımsızdır.

Derleme bölümündeki switch deyimi hakkında wikipedia'da belirtildiği gibi .

Giriş değerleri aralığı tanımlanabilir şekilde 'küçük' ise ve yalnızca birkaç boşluğu varsa, bir optimizer içeren bazı derleyiciler, anahtar deyimini uzun bir dizi koşullu talimat yerine bir şube tablosu veya dizinlenmiş işlev işaretçileri dizisi olarak uygulayabilir. Bu, switch ifadesinin, karşılaştırma listesinden geçmek zorunda kalmadan hangi şubenin yürütüleceğini anında belirlemesine olanak tanır.


4
bu doğru değil. Vaka değerlerinin dar veya geniş aralıkta olmasına bakılmaksızın daha hızlı olacaktır. O (1) - vaka değerlerinin ne kadar birbirinden ayrı olduğu önemli değil.
Aniket Inge

6
@Aniket: Bu wikipedia makalesini okuyun. en.wikipedia.org/wiki/Branch_table
Vishal K

14
@Aniket: Aralık geniş ve seyrek ise O (1) değildir. İki tür anahtar vardır ve aralık çok yayılmışsa, Java bunu bir "tableswitch" yerine "arama anahtarına" derleyecektir. Birincisi, kişi bulunana kadar dal başına bir karşılaştırma gerektirir, ikincisi ise bulunmaz.
cHao

4
Wikipedia referans bulmak için iyi bir yerdir, ancak yetkili bir kaynak olarak görülmemelidir. Orada okuduğunuz her şey en iyi ikinci el bilgilerdir.
cHao

6
@Aniket: Tüm adalette, demontaj, belirli bir platformda belirli bir JVM'ye özgüdür. Diğerleri farklı çevirebilir. Bazıları aslında arama anahtarı için bir karma tablo kullanabilir. Hala bir tablewitch kadar iyi performans göstermez, ancak en azından yakın olabilir. Sadece JIT için daha uzun sürer ve girişe bir karma algoritma uygulamayı içerir. Bu nedenle, sonuçta ortaya çıkan montaj kodu aydınlatıcı olsa da, özellikle Windows x86_64'te Hotspot v1.7'den bahsetmediğiniz sürece, yetkili değildir.
cHao

30

Cevap bayt kodunda yatıyor:

SwitchTest10.java

public class SwitchTest10 {

    public static void main(String[] args) {
        int n = 0;

        switcher(n);
    }

    public static void switcher(int n) {
        switch(n) {
            case 0: System.out.println(0);
                    break;

            case 1: System.out.println(1);
                    break;

            case 2: System.out.println(2);
                    break;

            case 3: System.out.println(3);
                    break;

            case 4: System.out.println(4);
                    break;

            case 5: System.out.println(5);
                    break;

            case 6: System.out.println(6);
                    break;

            case 7: System.out.println(7);
                    break;

            case 8: System.out.println(8);
                    break;

            case 9: System.out.println(9);
                    break;

            case 10: System.out.println(10);
                    break;

            default: System.out.println("test");
        }
    }       
}

Karşılık gelen bayt kodu; sadece gösterilen ilgili parçalar:

public static void switcher(int);
  Code:
   0:   iload_0
   1:   tableswitch{ //0 to 10
        0: 60;
        1: 70;
        2: 80;
        3: 90;
        4: 100;
        5: 110;
        6: 120;
        7: 131;
        8: 142;
        9: 153;
        10: 164;
        default: 175 }

SwitchTest22.java:

public class SwitchTest22 {

    public static void main(String[] args) {
        int n = 0;

        switcher(n);
    }

    public static void switcher(int n) {
        switch(n) {
            case 0: System.out.println(0);
                    break;

            case 1: System.out.println(1);
                    break;

            case 2: System.out.println(2);
                    break;

            case 3: System.out.println(3);
                    break;

            case 4: System.out.println(4);
                    break;

            case 5: System.out.println(5);
                    break;

            case 6: System.out.println(6);
                    break;

            case 7: System.out.println(7);
                    break;

            case 8: System.out.println(8);
                    break;

            case 9: System.out.println(9);
                    break;

            case 100: System.out.println(10);
                    break;

            case 110: System.out.println(10);
                    break;
            case 120: System.out.println(10);
                    break;
            case 130: System.out.println(10);
                    break;
            case 140: System.out.println(10);
                    break;
            case 150: System.out.println(10);
                    break;
            case 160: System.out.println(10);
                    break;
            case 170: System.out.println(10);
                    break;
            case 180: System.out.println(10);
                    break;
            case 190: System.out.println(10);
                    break;
            case 200: System.out.println(10);
                    break;
            case 210: System.out.println(10);
                    break;

            case 220: System.out.println(10);
                    break;

            default: System.out.println("test");
        }
    }       
}

Karşılık gelen bayt kodu; yine, sadece ilgili parçalar gösterilmiştir:

public static void switcher(int);
  Code:
   0:   iload_0
   1:   lookupswitch{ //23
        0: 196;
        1: 206;
        2: 216;
        3: 226;
        4: 236;
        5: 246;
        6: 256;
        7: 267;
        8: 278;
        9: 289;
        100: 300;
        110: 311;
        120: 322;
        130: 333;
        140: 344;
        150: 355;
        160: 366;
        170: 377;
        180: 388;
        190: 399;
        200: 410;
        210: 421;
        220: 432;
        default: 443 }

İlk durumda, dar aralıklar ile, derlenmiş bayt kodu a kullanır tableswitch. İkinci durumda, derlenen bayt kodu bir lookupswitch.

İçinde tableswitch, yığının üstündeki tamsayı değeri tabloya dizin oluşturmak, dal / atlama hedefini bulmak için kullanılır. Bu atlama / dal daha sonra hemen gerçekleştirilir. Dolayısıyla bu bir O(1)işlemdir.

A lookupswitchdaha karmaşıktır. Bu durumda, doğru anahtar bulunana kadar tam sayı değerinin tablodaki tüm anahtarlarla karşılaştırılması gerekir. Anahtar bulunduktan sonra, atlama için dal / atlama hedefi (bu anahtarın eşlendiği) kullanılır. Kullanılan tablo lookupswitchsıralanır ve doğru anahtarı bulmak için bir ikili arama algoritması kullanılabilir. Bir ikili arama için performans O(log n)ve tüm süreç de O(log n), çünkü atlama hala O(1). Bu nedenle, seyrek aralıklarda performansın düşük olmasının nedeni, doğrudan doğru anahtarın aranması gerektiğidir, çünkü doğrudan tabloya indeksleyemezsiniz.

Seyrek değerler varsa ve yalnızca tableswitchkullanmanız gereken bir tablo varsa, tablo temelde defaultseçeneğe işaret eden kukla girişler içerir . Örneğin, son giriş içinde olduğunu varsayarak SwitchTest10.javaoldu 21yerine 10elde edersiniz:

public static void switcher(int);
  Code:
   0:   iload_0
   1:   tableswitch{ //0 to 21
        0: 104;
        1: 114;
        2: 124;
        3: 134;
        4: 144;
        5: 154;
        6: 164;
        7: 175;
        8: 186;
        9: 197;
        10: 219;
        11: 219;
        12: 219;
        13: 219;
        14: 219;
        15: 219;
        16: 219;
        17: 219;
        18: 219;
        19: 219;
        20: 219;
        21: 208;
        default: 219 }

Böylece derleyici temelde, boşluklar arasında kukla girişler içeren ve defaultkomutun dal hedefini işaret eden bu büyük tabloyu oluşturur . A olmasa bile , anahtar bloğundan sonradefault talimatı gösteren girişler içerecektir . Bazı temel testler yaptım ve son dizin ile önceki dizin ( ) arasındaki boşluk daha büyükse , yerine a kullanır .935lookupswitchtableswitch

Davranışı switchaçıklamada tanımlanan Java Virtual Machine Şartname (§3.10) :

Anahtar durumları seyrek olduğunda, tablo anahtarı komutunun tablo gösterimi alan açısından verimsiz hale gelir. Bunun yerine arama anahtarı komutu kullanılabilir. Lookupswitch komutu, int anahtarlarını (vaka etiketlerinin değerleri) bir tablodaki hedef ofsetleriyle eşleştirir. Arama anahtarı komutu yürütüldüğünde, anahtarın ifadesinin değeri tablodaki tuşlarla karşılaştırılır. Anahtarlardan biri ifadenin değeriyle eşleşirse, yürütme ilişkili hedef ofsetinde devam eder. Hiçbir anahtar eşleşmezse, yürütme varsayılan hedefte devam eder. [...]


1
Sorudan, sayıların her zaman bitişik olduğunu ancak aralığın az ya da çok uzun olduğunu anladım - yani bir örnekte vakalar 0'dan 5'e, diğer bir örnekte 0'dan 30'a giderler - ve örneklerin hiçbiri seyrek değerler kullanmaz
Mart'ta Assylias

@assylias Hmm, ilginç. Sanırım soruyu yanlış anladım. Biraz daha deney yapmama izin verin. Diyorsunuz ki 0-30 arası bitişik bir aralıkta bile , derleyici bir lookupswitch?
Vivin Paliath

@VivinPaliath: Evet, testlerimde durum sabitleri her zaman bitişik, bu yüzden temelde [0, 1], [0, 1, 2], [0, 1, 2, 3] ... vb.
Andrew Bissell

@VivinPaliath Hayır, bayt kodu her zaman bir tablewitch kullanır - ancak JIT derleyicisi, içerdiği kaç öğeye bağlı olarak tableswitch'i aynı şekilde derlemeye derlemez.
Mart'ta Assylias

6
@VivinPaliath Soruyu daha açık bir şekilde ifade edebilirdim. Bu düşük seviyeli bayt kodu ve montaj şeylerini içeren cevapları değerlendirmek için biraz derinimde değilim. Bana hala tableswitch / lookupswitch ayrımı gerçekten önemli gibi görünüyor ve şu ana kadar bu terimleri kullanan tek cevap sizindir (diğerleri muhtemelen aynı kavramı farklı terminolojiyle ortaya koyuyor olsa da). Artı ben de JVM Spec bağlantı sahip gibi.
Andrew Bissell

19

Soru zaten cevaplandığından (aşağı yukarı), işte bir ipucu. kullanım

private static final double[] mul={1d, 10d...};
static double multiplyByPowerOfTen(final double d, final int exponent) {
      if (exponent<0 || exponent>=mul.length) throw new ParseException();//or just leave the IOOBE be
      return mul[exponent]*d;
}

Bu kod çok daha az IC (komut önbelleği) kullanır ve her zaman satır içine alınır. Kod sıcaksa dizi L1 veri önbelleğinde olacaktır. Arama tablosu neredeyse her zaman bir kazançtır. (özellikle mikrobenzer işaretlerde: D)

Düzenleme: yöntemin sıcak satır içi olmasını istiyorsanız, hızlı olmayan yolların throw new ParseException()en az kısa olmasını düşünün veya bunları ayrı bir statik yönteme taşıyın (bu nedenle bunları en kısa olarak kısaltın). Bu throw new ParseException("Unhandled power of ten " + power, 0);zayıf bir fikir b / c sadece yorumlanabilir kod için satır içi bütçenin çok yiyor - dize birleştirme bytecode oldukça ayrıntılı. ArrayList ile daha fazla bilgi ve gerçek bir vaka

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.