12

私はそれが「非常に」速いと信じて、の速度に興味がありました、しかし私はそれを期待したとき(確かな理由はありません)switch、単一のスイッチが約4つのテストとほぼ同じくらい速いことを示すように見えるテストケースを持っていますif1つのテストと同じくらい速くなります。これが私が比較するために書いた2つの方法switchですif

public static int useSwitch(int i) {
    switch (i) {
        case 1: return 2;
        case 2: return 3;
        case 3: return 4;
        case 4: return 5;
        case 5: return 6;
        case 6: return 7;
        default: return 0;
    }
}

public static int useIf(int i) {
    if (i == 1) return 2;
    if (i == 2) return 3;
    if (i == 3) return 4;
    if (i == 4) return 5;
    if (i == 5) return 6;
    if (i == 6) return 7;
    return 0;
}

これが私のテストコードです:

long x = 0;
for (int i = 0; i < 999999; i++)
    x += useIf(i % 7); // I use "x" so calls won't get optimized out

そして別の同じループuseSwitch()

私のマシンでは、これらのループが完了するまでにほぼ同じ時間がかかります。これは驚きでした。
入力範囲を指定した場合の平均であるため、ifの数は「4」になりました(私は思います)。
ロジックオプションの数を減らすと、ifバージョンが著しく速くなります。


私の質問は:

それはswitch実際にはそれほど速くないのですか、それともこれは何らかの形で「不公平な」テストですか?

4

4 に答える 4

15

これはある程度不公平な比較です。CPU時間のほとんどは、モジュロ演算の処理に費やされますi % 7。最新の最高のCPUでもModuloは非常に遅く、ベンチマークしようとしているif()またはswitch()実装よりも実行に20倍長くかかる可能性があります。

さらに、switchステートメントを最適化する方法は2つあります。1つはルックアップテーブルによるもので、これはあなたが提示したようなシーケンシャルなケースで使用されます。もう1つの方法は、必要に応じて検索パーティションツリーを使用することです。これは、次の例のように、ケースがスパースの場合に発生します。

switch (someInt) {
    case 0:  ... break;
    case 10:  ... break;
    case 102:  ... break;
    case 6543:  ... break;
    case 19303:  ... break;
    case 19305:  ... break;
    // and so forth...
}

ほとんどのコンパイラは、展開されたパーティションツリーを使用して正しいケースを見つけます。これは、長いスイッチでは、正しいものをヒットするために必要な非常に優れた平均ジャンプと最悪の場合のジャンプを提供します。結果の擬似コードは次のようになります。

if (someInt >= 6543) {
    if (someInt >= 19303) {
        // continue tree search, etc.
    }
    else if (someInt==6543) {}
}
else if (someInt >= 0) {
    if (someInt >= 10) {
        // continue tree search, etc.
    }
    else if (someInt == 0) {} 
}
else {
    // default case handler...
}

ここに示すように、6〜8のケースではあまり役に立ちませんが、おそらく50以上のケースのスイッチがある場合は、非常に役立ちます。線形検索ではO(n)(50条件が最悪、25平均)になりますが、パーティションツリーのバージョンはsqrt(n)に近くなります(コンパイラの選択に応じて、8〜9条件が最悪、5〜7平均)。

于 2012-12-30T07:30:12.567 に答える
5

スイッチはより高速であるはずです、それはバイトコードを見るのに十分です

   TABLESWITCH
      1: L1
      2: L2
      3: L3
      4: L4
      5: L5
      6: L6

それが特別な操作であることを確認します。実際には、JVMの最適化のために違いはないかもしれません。しかし、-Xint(interepret only mode)を使用してコードを実行すると、違いは明らかです。私のPCでは、スイッチを優先して63〜93(ms)です。

于 2012-12-30T07:02:52.320 に答える
2

ここで興味深い質問は、コンパイラがスイッチをどのように変換するかです(ハッシュテーブルのようになるか、結果がif-else構造に似ている可能性があります)。また、入力に基づいてあらゆる種類の最適化を実行できます(既知の場合)。ただし、これはコンパイラに依存し、私が知っている仕様では定義されていません。

ですから、答えを出すことはできませんが、テストをさらに洗練させたい場合は、いつでも7の入力を与えることができます。これにより、答えが返されるまですべてのテストが実行されます。 、およびスイッチが最適化されている場合は、より良い結果が得られます。if-elseの場合と同様に、同様の結果が得られます。コンパイラが事前に7を認識しないように、7をハードコーディングしないでください。文字列から取得するには、parseintなどを使用してください。

于 2012-12-30T06:49:38.097 に答える
1

JITのスマートさに基づいて削除される場合とされない場合があるコードがあるためif、スイッチよりもステートメントを削除する方が簡単であると判断した可能性があります。アサーションやデバッグチェックなど、ステートメントが削除される可能性が高い場合、これは驚くべきことではありません。

于 2012-12-30T11:49:20.950 に答える