ifとスイッチ速度


112

コンパイラの最適化により、switchステートメントは通常、同等のif-else-ifステートメントよりも高速です(この記事で説明されているように)。

この最適化は実際にはどのように機能しますか?誰か良い説明はありますか?



考えられる良い答え:dotnetperls.com/if-switch-performance
Babak

回答:


185

コンパイラは、必要に応じてジャンプテーブルを作成できます。たとえば、リフレクターを使用して生成されたコードを確認すると、文字列の巨大なスイッチの場合、コンパイラーはハッシュテーブルを使用してこれらをディスパッチするコードを実際に生成します。ハッシュテーブルは文字列をキーとして使用し、caseコードへのデリゲートを値として使用します。

これは多くの連鎖ifテストよりも漸近的に優れた実行時間を持ち、実際には比較的少ない文字列でも高速です。


6
良い答えです。ハッシュテーブルについて興味深いです。
BobbyShaftoe 2009年

4
また、場合によってはツリー比較に変換されます。推論はやや複雑ですが、基本的にはテーブルの間接化に要約され、最新のCPUジャンプターゲットバッファを無効にするため、分岐予測子を消去します。スイッチのcodegenに関するGCC会議での論文を漠然と思い出します。
olliej 2009年

つまり、スイッチ(a)ケース "x":ケース "y":ケース "z"://何かが壊れる; }は次の場合より高速です:if(a == "x" || a == "b" || a == "c")//何か正しいですか?
yazanpro 2012年

ここでは、ネストされていない場合はORのみです。
yazanpro 2012年

@yazanpro古いコンパイラでは可能性があります(ただし、ケースの数が非常に少ないため、違いが生じない場合があります!)。しかし、最近のコンパイラーはより多くのコード分析を行います。結果として、これらの2つのコードスニペットが同等であることを理解し、同じ最適化を適用する場合があります。しかし、これは私の側の純粋な推測であり、コンパイラが実際にそれを行うかどうかはわかりません。
Konrad Rudolph、

15

これは通常if..else if ..、人が簡単にスイッチステートメントに変換できるシーケンスに遭遇する現代のコンパイラと同様に、少し簡略化されています。コンパイラも同様です。ただし、さらに面白くするために、コンパイラは構文に制限されないため、範囲や単一のターゲットなどが混在する「スイッチ」のようなステートメントを内部で生成できます。また、スイッチとifの両方でこれを実行できます(実行します)。 .elseステートメント。

Anyhoo、Konradの答えの拡張は、コンパイラがジャンプテーブルを生成する可能性があることですが、これは必ずしも保証されていません(望ましくありません)。さまざまな理由で、ジャンプテーブルは最新のプロセッサの分岐予測子に悪いことをし、テーブル自体はキャッシュの動作に悪いことをします。

switch(a) { case 0: ...; break; case 1: ...; break; }

コンパイラが実際にこのためのジャンプテーブルを生成した場合、if..else if..ジャンプテーブルが分岐予測を無効にするため、代替スタイルコードよりも遅くなる可能性があります。


4

不一致の統計は適切でない場合があります。

実際にソースをダウンロードすると、ifとswitchの両方のケースで、一致しない値は21であることがわかります。コンパイラーは、どのステートメントを常に実行する必要があるかを認識して抽象化し、CPUが適切に分岐予測できる必要があります。

私の意見では、より興味深いケースは、すべてのケースがうまくいくとは限らない場合ですが、それは実験の範囲ではなかったかもしれません。


4

通常、switch / caseステートメントは1レベルの深さで高速になりますが、2つ以上になると、入れ子になったif / elseステートメントの2〜3倍の時間がかかります。

この記事では、速度の比較を行って、そのようなステートメントがネストされている場合の速度の違いを強調しています。

たとえば、彼らのテストによると、次のようなサンプルコード:

if (x % 3 == 0)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else if (x % 3 == 1)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else if (x % 3 == 2)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;

同等のswitch / caseステートメントの実行にかかる時間の半分で終了しました。

switch (x % 3)
    {
        case 0:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
        case 1:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
    case 2:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
    default:
        switch (y % 3)
        {
            case 0: total += 3;
                break;
            case 1: total += 2;
                break;
            case 2: total += 1;
                break;
            default: total += 0;
                break;
        }
        break;
    }

ええ、それは初歩的な例ですが、それは要点を示しています。

したがって、結論は、1レベルだけの深さの単純な型にはスイッチ/ケースを使用することですが、より複雑な比較や複数のネストされたレベルには、従来のif / else構成を使用しますか?


-1:1.記事は分岐予測を完全に無視しました。2。アルゴリズムはまったく同じではありません(リンク上の単一のif-elseがすでにより最適化されてコーディングされています)。適切でクリーンなコードの使用(スイッチと同じif-elseコンストラクト間の10.000.000コールで約4 ns)
Trojaner

この例は、switchブロックの数が少ないため最適化されません。通常は5〜6個の要素の後にジャンプテーブルが生成されます。
antiduh

0

if overケースの唯一の利点は、最初のケースの発生頻度の顕著な増加がある場合です。

しきい値がどこにあるかは正確にはわかりませんが、最初の「ほぼ常に」最初のテストに合格しない限り、case構文を使用します。

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