C++ 長い switch ステートメントまたはマップで検索しますか?
-
20-09-2019 - |
質問
私の C++ アプリケーションには、他の値を表すコードとして機能する値がいくつかあります。コードを変換するために、switch ステートメントを使用するか stl マップを使用するかについて議論しています。スイッチは次のようになります。
int code;
int value;
switch(code)
{
case 1:
value = 10;
break;
case 2:
value = 15;
break;
}
地図は stl::map<int, int>
変換は、キー値として使用されるコードを使用した単純な検索になります。
どちらが優れています/より効率的/クリーン/受け入れられていますか?なぜ?
解決
個人的に、私は、マップを使用します - 通常はスイッチを使用すると、プログラムの動作の違いを示しています。さらに、データのマッピングを変更すると、スイッチよりも地図容易である。
パフォーマンスが本当の問題である場合は、、プロファイリングは、使用可能な答えを得るための唯一の方法です。分岐予測ミスが頻繁に十分に起こる場合、スイッチは速くないかもしれません。
それはコードと値の範囲が静的である場合は特に、データ構造にコードと関連付けられた値を組み合わせるために多くの意味がありません場合は、このことについて考えるために別のアプローチがあります:
struct Code { int code; int value; };
Code c = ...
std::cout << "Code " << c.code << ", value " << c.value << std::end;
他のヒント
あなたは昔ながらの単純な配列、
のようなものとのより良いだろうint lookup[] = {-1, 10, 15, -1 222};
その後、switchステートメントは、単に
などのように書き換えることができます値=参照[コード];
他のすべてのオプションはある程度の追加コストを紹介します。
これはかなりのコードがあり、どのように多く存在しているかに依存します。良いコンパイラは、彼らがストレートのif / then文を採用していますいくつかは、彼らがswitch文を最適化するために使用するさまざまなトリックを、持っています。ほとんどはケース0、1、例えば2またはケース3、6、9ための単純な数学または使用ルックアップ/ジャンプテーブルを行うのに十分明るい。
もちろん、いくつかはそうではない、と多くは簡単に値の異常なまたは不規則なセットによってくじかれています。いくつかの例を処理するためのコードは非常に似ています場合にも、カット&ペーストは、メンテナンスの問題につながることができます。あなたは多くのコードを持っていますが、彼らはグループにアルゴリズム分けることができた場合、あなたは、ネストされた/複数のスイッチ文を検討するかもしれないではなく、例えばます:
switch (code) {
case 0x0001: ...
case 0x0002: ...
...
case 0x8001: ...
case 0x8002: ...
...
}
あなたが使用する可能性があります:
if (code & 0x8000) {
code &= ~0x8000;
switch (code) {
case 0x0001: ... // actually 0x8001
case 0x0002: ... // actually 0x8002
...
}
}
else {
switch (code) {
case 0x0001: ...
case 0x0002: ...
...
}
}
多くの言語の通訳は、シングルバイトのオペコードは、追加情報が様々なビットに詰めていて、そして、すべての可能な組み合わせとそのハンドラを転写することで繰り返しの多い脆弱なことであろうから、このようオペコードをデコードします。一方、過度のビットマングリングは、コンパイラによって任意の最適化を倒し、反生産性を高めることができます。
あなたは、これが本当のパフォーマンス上のボトルネックになって確認していない限り、私は時期尚早の最適化を避けるだろう:それを行う合理的に堅牢で、実装が迅速としてあなたを打つ方方法。アプリケーションがあまりにもゆっくりと実行されているとおりとあれば、それをプロファイルし、それに応じて最適化します。
switch文は速くなりますが、これは、アプリケーションのパフォーマンスのボトルネックではない場合、あなたは本当にそれを気にしてはいけません。
長期的に維持するためにあなたのコードをより簡単にするもののために行く。
あなたのサンプルは、その点で意味のある呼び出しを行うには短すぎる。
私は、テーブルを自分で検索する部分です。私はまた、テーブルは将来の変化やメンテナンスに、より良い自分自身を貸すと思います。
すべてのIMHO、もちろんます。
私はペアの静的、定数、テーブルを使用することをお勧め。これは、ルックアップテーブルの別の形式です。
struct Table_Entry
{
int code;
int value;
};
static const Table_Entry lookup_table[] =
{
{1, 10},
{2, 15},
{3, 13},
};
static const unsigned int NUM_TABLE_ENTRIES =
sizeof(lookup_table) / sizeof(lookup_table[0]);
これの利点は、テーブルが実行時に初期化されなければならstd::map
とは異なり、コンパイル時に生成されていることです。量が大きい場合は、エントリを見つけるためにstd::lower_bound
を使用することができ、テーブルを注文されました。
もう一つの利点は、この技術は、データ駆動されています。データは、検索エンジンへの変更なしに変更することができます。コードまたはプロセスへの変更は、重大な回帰テストを必要とするかもしれないが、データの変更はないかもしれません。 YMMVます。
これは、コンパイラが生成するかもしれないものに似ています。
あなたが最も検索一定の時間を作る必要がある値を(int型ハッシングはあまりにも本当に速いことができる)、ハッシュするunordered_map使用することができTR1を使用できます。
あなたは、これはあなたのプログラムのボトルネックであることを示すデータをプロファイリングしていない限り、しかし、設計の観点から、最も理にかなったアプローチでそれをコーディングします。
私は、あなたがやっているすべての値を割り当てている場合マップと言います。私の理由は、それがあなたのswitch文ではないコードを変更せずに拡張可能であるということです。
ところで、どの程度列挙する?
私は、その場合には、私はSTL ::マップがより適切であると考え、「コード」の量が多くなると、スイッチケース構造の生成されたコードは、非常に大きくなることができると思います。
通常、私は(あなたはもちろん、速度やコードサイズではなく、読みやすくするために最適化していると仮定して)マップまたは参照配列または多分いくつかのハイブリッドルックアップモンスターをお勧めしたいが、それは、新しいバージョンがあることは注目に値しますGCCは、テーブルをルックアップするために、このようなスイッチ/ケースの割り当てを交換するのに十分なスマートです。これがない完全スパースキーのとても大きいですが、あなたが鍵をしているような塊である場合、それは次のようになります。 0、1、2、3、4、5、11、12、13、14、15、193、194、195、196、197、198 ...
もちろん、あなたはおそらく好き一部arithmaticがさらに良く(通常)、変換を行うには可能性があります。このような何かをやったときにエンディアン、またはそれ以上の伝統的な数学を交換し、シフト見落とすことはありません。
unordered_mapは、おそらくここに良く適していることでしょう。
- 整数を配列/ベクトルに読み込みます
- 配列/ベクトルをソートする
- 基礎となる配列で bsearch を使用する