web-dev-qa-db-ja.com

If対スイッチ速度

通常、switchステートメントは、コンパイラーの最適化により、同等のif-else-ifステートメント(たとえば、この article で説明)よりも高速です。

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

108
Dirk Vollmar

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

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

177
Konrad Rudolph

Konrad は正しいです。整数の連続した範囲を切り替える場合(たとえば、ケース0、ケース1、ケース2 ..ケースnがある場合)、コンパイラはハッシュテーブルを構築する必要さえないため、さらに良いことを行うことができます。関数ポインタの配列を保存するだけなので、一定の時間でジャンプターゲットをロードできます。

29
Crashworks

これは、通常、if..else if ..人が簡単にswitch文に変換できるシーケンス。コンパイラも同様です。ただ、余計な楽しみを加えるために、コンパイラは構文に制限されないため、範囲や単一のターゲットなどが混在するステートメントのような「スイッチ」を内部で生成できます。 .elseステートメント。

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

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

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

14
olliej

Konradが言ったように、コンパイラはJumpテーブルを作成できます。

C++では、スイッチの制限が原因である可能性があります。

5
J.J.

通常、switch/caseステートメントは1レベルの深さで高速ですが、2つ以上に入ると、switch/caseステートメントはネストされた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ステートメントの実行にかかった時間halfで終了しました。

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構造を使用しますか?

4
user3810913

マッチなしの統計は良くないかもしれません。

実際にソースをダウンロードした場合、ifとswitchの両方のケースで、一致しない値は21であることがわかります。コンパイラは、どのステートメントを常に実行する必要があるかを把握し、CPUが分岐予測を適切に行えるようにする必要があります。

より興味深いケースは、私の意見では、すべてのケースが中断するわけではない場合ですが、それは実験の範囲ではなかったかもしれません。

4
Calyth

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

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

0
Ralph

これは、C言語のPIC18マイクロコントローラーのコードです。

void main() {
int s1='0';
int d0;
int d1;
//if (s1 == '0') {d1 = '0'; d0 = '0';}
//else if (s1 == '1') {d1 = '0';d0 = '1';}
//else if (s1 == '2') {d1 = '1';d0 = '0';}
//else if (s1 == '3') {d1 = '1';d0 = '1';}
switch (s1) {
      case  '0': {d1 = '0';d0 = '0';} break;
      case  '1': {d1 = '0';d0 = '1';} break;
      case  '2': {d1 = '1';d0 = '0';} break;
      case  '3': {d1 = '1';d0 = '1';} break;
    }
}

ifsを使用

s1='0' - 14 cycles
s1='1' - 21 cycles
s1='2' - 28 cycles
s1='3' - 33 cycles
s1='4' - 34 cycles

ケース付き

s1='0' - 17 cycles
s2='1' - 23 cycles
s3='2' - 29 cycles
s4='3' - 35 cycles
s5='4' - 32 cycles

だから、非常に低いレベルではifsの方が速いと思います。 ROMのコードも短くなっています。

0
Denys Titarenko