隣接するintのJavaスイッチが、ケースを追加するとより速く実行されるように見えるのはなぜですか?


276

メインプログラムロジックの多くのポイントで呼び出されるホット関数で実行されるため、高度に最適化する必要があるいくつかのJavaコードに取り組んでいます。このコードの一部には、double変数10を任意の非負int exponentのsに乗じることによって乗算することが含まれます。(編集:なく最速、下記アップデート2参照)を一つの早道乗算値を取得するには、にあるswitchexponent

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);
   }
}

上記のコメント付きの省略記号は、case int定数が1ずつ増加し続けることを示しているためcase、上記のコードスニペットには実際には19 秒あります。caseステートメント10スルーで10のすべてのパワーが実際に必要かどうかわからなかったので18、このswitchステートメントで1000万回の操作を完了する時間と、s スルーswitchのみcaseの場合(9以下に制限)を比較するいくつかのマイクロベンチマークを実行しました簡略化された)を壊さないでください)。私はむしろ驚くべきことだ(少なくとも、私には!)長いという結果以上との声明が実際に速く走ったし。09exponentswitchswitchcase

On a lark, I tried adding even more cases which just returned dummy values, and found that I could get the switch to run even faster with around 22-27 declared cases (even though those dummy cases are never actually hit while the code is running). (Again, cases were added in a contiguous fashion by incrementing the prior case constant by 1.) These execution time differences are not very significant: for a random exponent between 0 and 10, the dummy padded switch statement finishes 10 million executions in 1.49 secs versus 1.54 secs for the unpadded version, for a grand total savings of 5ns per execution. So, not the kind of thing that makes obsessing over padding out a switch最適化の観点から努力する価値のあるステートメント。しかし、さらにsが追加されても、aのswitch実行が遅くなることはない(または、せいぜい一定のO(1)時間を維持するだけであることが、好奇心が強く、直観に反していることがわかりますcase

スイッチのベンチマーク結果

これらは、ランダムに生成されたexponent値にさまざまな制限を付けて実行した結果です。私はすべての方法までの結果含まれていなかった1ためexponent限界が、しかし、曲線の一般的な形状は、12-17ケースマークの周りの尾根、及び18-28の間の谷で、同じまま。すべてのテストは、ランダムな値の共有コンテナーを使用してJUnitBenchmarksで実行され、テスト入力が同一であることを確認しました。switchまた、順序付けに関連するテストの問題の可能性を排除するために、テストを最長のステートメントから最短の順序、およびその逆の順序で実行しました。誰かがこれらの結果を再現したい場合は、テストコードをgithubリポジトリに配置しました。

それで、ここで何が起こっているのですか?私のアーキテクチャまたはマイクロベンチマーク構築のいくつかの気まぐれ?またはJavaはあるswitchに実行するために少し速く本当に1828 caseそれがあるからより範囲11まで17

github test repo "switch-experiment"

更新:ベンチマークライブラリをかなりクリーンアップし、/ resultsにテキストファイルを追加して、可能なexponent値の広い範囲にわたる出力をいくつか追加しました。Exceptionfrom をスローしないオプションをテストコードに追加しましたdefaultが、これは結果に影響しないようです。

更新2:この問題に関するかなり良い議論が2009年のxkcdフォーラム(http://forums.xkcd.com/viewtopic.php?f=11&t=33524)で見つかりました。OPの使用に関する議論Array.binarySearch()から、上記のべき乗パターンの単純な配列ベースの実装のアイデアが得られました。のエントリが何であるかを知っているので、バイナリ検索の必要はありませんarray。を使用するよりも約3倍速く実行されているように見えますがswitch、明らかに、switch提供される制御フローの一部が犠牲になります。そのコードはgithubリポジトリにも追加されています。


64
現在、あらゆる場所のすべてのGoogle社員がswitch、最も最適なソリューションであるため、すべてのステートメントで正確に22のケースがあります。:D(これを私のリードに見せないでください。)
アステリ

2
より簡単なSSCCEはありますか?これは私のためにコンパイルされません。Javaパフォーマンスと同じくらい弱いので、これを試してみたいと思います。
Mysticial 2013年

5
文字列ベースのケースに関する私の回答の「JVMのスイッチ」セクションが役立つかもしれません。ここで起こっていることは、あなたがa lookupswitchからに切り替えているということtableswitchです。でコードを分解するjavapと、間違いなく表示されます。
エリクソン2013年

2
依存関係jarをリポジトリの/ libフォルダーに追加しました。@Mysticial申し訳ありませんが、私はすでにこのウサギの穴を下りるのにあまりにも多くの時間を費やしました!テストクラスから「AbstractBenchmarkの拡張」を取り除き、「com.carrotsearch」のインポートを削除すると、JUnit依存関係のみで実行できますが、carrotsearchのものは、JITからのノイズの一部を取り除くのに非常に適しています。ウォームアップ期間。残念ながら、IntelliJの外部でこれらのJUnitテストを実行する方法はわかりません。
Andrew Bissell 2013年

2
@AndrewBissellより単純なベンチマークで結果を再現しました。中小規模のパフォーマンスに対するブランチとテーブルの比較は、いくぶん明白な推測でした。しかし、私は他の誰よりも30のケースに入るディップについてより良い洞察を持っています...
Mysticial

回答:


228

他の回答で指摘されているように、ケース値は(スパースではなく)連続しているため、さまざまなテスト用に生成されたバイトコードはスイッチテーブル(バイトコード命令tableswitch)を使用します。

ただし、JITがそのジョブを開始し、バイトコードをアセンブリにコンパイルすると、tableswitch命令は常にポインターの配列になるわけではありません。スイッチテーブルがlookupswitchif/ else if構造に似た)のように変換される場合があります。

JIT(hotspot JDK 1.7)によって生成されたアセンブリを逆コンパイルすると、17ケース以下の場合は一連のif / else、18を超える場合(より効率的)にはポインターの配列が使用されます。

このマジックナンバー18が使用される理由は、MinJumpTableSizeJVMフラグのデフォルト値(コードの352行目付近)に達しているようです。

ホットスポットコンパイラリストで問題を提起しましたが、これは過去のテストの遺産のようです。このデフォルト値さらにベンチマークが実行された、JDK 8で削除されていることに注意してください。

最後に、メソッドが長くなりすぎると(私のテストでは25ケースを超える)、デフォルトのJVM設定でインライン化されなくなりました。これが、その時点でパフォーマンスが低下する最も可能性の高い原因です。


5つのケースの場合、逆コンパイルされたコードは次のようになります(cmp / je / jg / jmp命令、if / gotoのアセンブリに注意してください)。

[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ケースの場合、アセンブリは次のようになります(使用され、すべての比較の必要性を抑制するポインターの配列に注意してください:jmp QWORD PTR [r8+r10*1]正しい乗算に直接ジャンプします)。これがパフォーマンス向上の理由である可能性があります。

[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    

最後に、30ケース(下)のアセンブリは18ケースに似てmovapd xmm0,xmm1いますが、@ cHaoによって検出されたコードの中央に向かって追加される点が異なりますが、パフォーマンスが低下する最も可能性の高い理由は、メソッドが多すぎることです。デフォルトのJVM設定でインライン化するには長い:

[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正直なところ、細かいところもわかりません;-)
assylias 2013年

4
すばらしい回答を得るには+1してください。パフォーマンスがOPのチャートの「落ち込み」から抜け出すときを比較するために、30以上のケースで何かを分解できますか?
アステリ2013年


2
@AndrewBissell私の推測では、異なる動作は、(i)ケースの数が18より大きい場合にのみポインタの配列が効率的であることが示されているクロスアーキテクチャパフォーマンステスト、または(ii)コードがそれが実行され、プロファイラーが実行時にどちらのアプローチが適切かを判断します。答えが見つかりません。
アサイリア2013年

3
30ケースの分解と18ケースの分解はほとんど同じように見えます。違いは主に、11番目のケースの後の追加のレジスタシャッフルの追加ビットに限定されているようです。JITterがそれを行う理由は言えません。それは不要に見えます。
cHao

46

スイッチ-ケース値が狭い範囲に配置されている場合、ケースはより高速です。

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

なぜなら、この場合、コンパイラーは、switchステートメント内のすべてのケースレッグに対して比較を実行することを回避できるからです。コンパイラは、さまざまなレッグで実行されるアクションのアドレスを含むジャンプテーブルを作成します。切り替えが実行されている値は、それをのインデックスに変換するために操作されjump tableます。この実装では、switchステートメントにかかる時間は、同等のif-else-ifステートメントカスケードにかかる時間よりもはるかに短くなります。また、switchステートメントにかかる時間は、switchステートメントのケースレッグの数とは無関係です。

ウィキペディアでコンパイルセクションのswitchステートメントについて述べたように。

入力値の範囲が識別可能に「小さく」、わずかなギャップしかない場合、オプティマイザを組み込んだ一部のコンパイラは、switchステートメントを実際には、長い一連の条件付き命令ではなく、ブランチテーブルまたはインデックス付き関数ポインタの配列として実装する場合があります。これにより、switchステートメントは、比較のリストを調べなくても、実行する分岐を即座に決定できます。


4
不正解です。ケース値の範囲が狭いか広いかに関係なく、より高速になります。それはO(1)です-ケース値がどれほど離れているかは問題ではありません。
Aniket Inge 2013

6
@Aniket:ウィキペディアのこの記事を読んでください。en.wikipedia.org/wiki/Branch_table
Vishal K

14
@Aniket:範囲が広く疎である場合、O(1)ではありません。スイッチには2種類あり、範囲が広すぎる場合、Javaはそれを「テーブルスイッチ」ではなく「ルックアップスイッチ」にコンパイルします。前者はブランチが見つかるまでブランチごとに比較する必要がありますが、後者は必要ありません。
cHao

4
ウィキペディアは参考文献を見つけるのにまともな場所ですが、信頼できる情報源と考えるべきではありません。そこで読むものはせいぜい中古情報です。
cHao

6
@Aniket:公平に言うと、逆アセンブリは特定のプラットフォーム上の特定のJVMに固有です。他の人はそれを異なって翻訳するかもしれません。実際には、ルックアップスイッチにハッシュテーブルを使用する場合もあります。それでもテーブルスイッチほどのパフォーマンスは得られませんが、少なくとも閉じることはできます。JITに時間がかかるだけで、入力にハッシュアルゴリズムを適用する必要があります。したがって、結果のアセンブリコードは啓発的なものになる可能性がありますが、Windows x86_64でのHotspot v1.7.whateverについて具体的に話しているのでない限り、信頼できるものではありません。
cHao

30

答えはバイトコードにあります:

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");
        }
    }       
}

対応するバイトコード。示されている関連部品のみ:

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");
        }
    }       
}

対応するバイトコード。ここでも、関連する部分のみを示しています。

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 }

最初のケースでは、範囲が狭いため、コンパイルされたバイトコードはを使用しtableswitchます。2番目のケースでは、コンパイルされたバイトコードはを使用しlookupswitchます。

ではtableswitch、スタックの最上部にある整数値を使用してテーブルにインデックスを付け、ブランチ/ジャンプターゲットを見つけます。このジャンプ/分岐は、すぐに実行されます。したがって、これはO(1)操作です。

A lookupswitchはより複雑です。この場合、正しいキーが見つかるまで、整数値をテーブル内のすべてのキーと比較する必要があります。キーが見つかると、(このキーがマップされている)分岐/ジャンプターゲットがジャンプに使用されます。で使用されるテーブルlookupswitchはソートされ、バイナリ検索アルゴリズムを使用して正しいキーを見つけることができます。二分探索のパフォーマンスはO(log n)でありO(log n)、ジャンプがまだあるため、プロセス全体も同様O(1)です。したがって、スパース範囲の場合にパフォーマンスが低下するのは、テーブルに直接インデックスを付けることができないため、最初に正しいキーを検索する必要があるためです。

スパース値がありtableswitch、使用する必要があった場合、テーブルには基本的にdefaultオプションを指すダミーエントリが含まれます。たとえば、最後のエントリがでSwitchTest10.java21なくだったとすると10、次のようになります。

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 }

したがって、コンパイラーは基本的に、ギャップ間のダミーエントリーを含むこの巨大なテーブルを作成し、default命令の分岐ターゲットをポイントします。がない場合でも、switchブロックのdefault命令を指すエントリが含まれます。基本的なテストをいくつか行ったところ、最後のインデックスと前のインデックス()のギャップがより大きい場合、の代わりにを使用していることがわかりました。935lookupswitchtableswitch

switchステートメントの動作は、Java仮想マシン仕様(§3.10)で定義されています

スイッチのケースがまばらである場合、tableswitch命令のテーブル表現はスペースの点で非効率になります。代わりにlookupswitch命令を使用できます。lookupswitch命令は、intキー(ケースラベルの値)をテーブル内のターゲットオフセットとペアにします。lookupswitch命令が実行されると、スイッチの式の値がテーブル内のキーと比較されます。キーの1つが式の値と一致する場合、実行は関連するターゲットオフセットで続行されます。一致するキーがない場合、実行はデフォルトのターゲットで続行されます。[...]


1
質問は数字が常に連続しているが、範囲は多かれ少なかれ長いことを理解しました。つまり、ある例では0から5までのケースですが、別の例では0から30まであり、スパース値を使用する例はありません。
アサイリア2013年

@assyliasうーん、面白い。私はその質問を誤解したと思います。もう少し実験してみましょう。つまり、0〜30の範囲で隣接している場合でも、コンパイラーはlookupswitch
Vivin Paliath 2013年

@VivinPaliath:はい、私のテストではケース定数は常に連続しているので、基本的には[0、1]、[0、1、2]、[0、1、2、3]のスイッチをテストしています...など
Andrew Bissell 2013年

@VivinPaliathいいえ、バイトコードは常にテーブルスイッチを使用しますが、JITコンパイラはテーブルスイッチをコンパイルして、含まれるアイテムの数によっては同じ方法でアセンブリしないようです。
アッシリアス2013年

6
@VivinPaliath私は確かに質問をより明確に表現できただろう。この低レベルのバイトコードとアセンブリに関するものを含む答えを評価することになると、私はちょっと深遠です。ここでもtableswitch / lookupswitchの区別が実際に重要であるように思われますが、これまでのところこれらの用語を使用しているのはあなたの回答のみです(他の人はおそらく同じ概念を異なる用語で説明しています)。さらに、JVMスペックリンクも好きです。
Andrew Bissell 2013年

19

質問はすでに(多かれ少なかれ)回答されているので、ここにいくつかのヒントがあります。使用する

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;
}

そのコードは使用するIC(命令キャッシュ)が大幅に少なく、常にインライン化されます。コードがホットな場合、アレイはL1データキャッシュに配置されます。ほとんどの場合、ルックアップテーブルは有利です。(特にマイクロベンチマーク:D)

編集:メソッドをホットインライン化したい場合は、高速ではないパスthrow new ParseException()を最小限に抑えるか、それらを別の静的メソッドに移動する(したがって、それらを最小限にする)ことを検討してください。これはthrow new ParseException("Unhandled power of ten " + power, 0);弱い考えですb / cそれは単に解釈することができるコードのインライン予算の多くを消費します-文字列連結はバイトコードではかなり冗長です。詳細と実際のケース(ArrayList付き)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.