通常、switchステートメントは、コンパイラーの最適化により、同等のif-else-ifステートメント(たとえば、この article で説明)よりも高速です。
この最適化は実際にどのように機能しますか?誰にも良い説明がありますか?
コンパイラは、必要に応じてジャンプテーブルを作成できます。たとえば、生成されたコードを見るためにリフレクターを使用すると、ストリングの巨大なスイッチに対して、コンパイラーが実際にハッシュテーブルを使用してこれらをディスパッチするコードを生成することがわかります。ハッシュテーブルは文字列をキーとして使用し、case
コードへのデリゲートを値として使用します。
これは、多くの連鎖if
テストよりも漸近的な実行時間に優れており、実際には比較的少ない文字列でも高速です。
Konrad は正しいです。整数の連続した範囲を切り替える場合(たとえば、ケース0、ケース1、ケース2 ..ケースnがある場合)、コンパイラはハッシュテーブルを構築する必要さえないため、さらに良いことを行うことができます。関数ポインタの配列を保存するだけなので、一定の時間でジャンプターゲットをロードできます。
これは、通常、if..else if ..
人が簡単にswitch文に変換できるシーケンス。コンパイラも同様です。ただ、余計な楽しみを加えるために、コンパイラは構文に制限されないため、範囲や単一のターゲットなどが混在するステートメントのような「スイッチ」を内部で生成できます。 .elseステートメント。
コンラッドの答えの拡張版であるAnyhooは、コンパイラがジャンプテーブルを生成する可能性があるということですが、それは必ずしも保証されているわけではありません(望ましくありません)。さまざまな理由により、ジャンプテーブルは最新のプロセッサの分岐予測子に悪いことをし、テーブル自体はキャッシュ動作に悪いことをします。
switch(a) { case 0: ...; break; case 1: ...; break; }
コンパイラが実際にこのためのジャンプテーブルを生成した場合、代替のif..else if..
分岐予測を無効にするジャンプテーブルのためのスタイルコード。
Konradが言ったように、コンパイラはJumpテーブルを作成できます。
C++では、スイッチの制限が原因である可能性があります。
通常、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構造を使用しますか?
マッチなしの統計は良くないかもしれません。
実際にソースをダウンロードした場合、ifとswitchの両方のケースで、一致しない値は21であることがわかります。コンパイラは、どのステートメントを常に実行する必要があるかを把握し、CPUが分岐予測を適切に行えるようにする必要があります。
より興味深いケースは、私の意見では、すべてのケースが中断するわけではない場合ですが、それは実験の範囲ではなかったかもしれません。
If overケースの唯一の利点は、最初のケースの発生頻度の顕著な増加がある場合です。
しきい値の正確な場所はわかりませんが、最初の「ほぼ常に」が最初のテストに合格しない限り、case構文を使用します。
これは、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のコードも短くなっています。