ケースを処理する必要がある場合、どれくらいの頻度で心配しますか?
-
05-07-2019 - |
質問
次のものがある場合:
$var = 3; // we'll say it's set to 3 for this example
if ($var == 4) {
// do something
} else if ($var == 5) {
// do something
} else if ($var == 2) {
// do something
} else if ($var == 3) {
// do something
} else {
// do something
}
たとえば、 $ var
の80%が3の場合、真のケースを見つける前に4つのifケースが発生するという事実を心配しますか?
小さなサイトでは大したことではないと思っていますが、ステートメントが1秒間に数千回実行される場合はどうなりますか?
私はPHPで作業していますが、言語は重要ではないと考えています。
解決
これは、レーダーシステム用のソフトウェアを作成するときに使用した方法です。 (レーダーでは速度が重要です。「リアルタイム」が「高速」ではなく「リアルタイム」を意味する数少ない場所の1つです。)
[Pythonシンタックスに切り替えます。これは私にとって簡単であり、あなたはそれを解釈できると確信しています。]
if var <= 3:
if var == 2:
# do something
elif var == 3:
# do something
else:
raise Exception
else:
if var == 4:
# do something
elif var == 5:
# do something
else:
raise Exception
ifステートメントは、フラットリストではなくツリーを形成します。このリストに条件を追加すると、ツリーの中心を揺らします。フラットな n 比較シーケンスは、平均して n / 2ステップかかります。ツリーは、log( n )比較を取る一連の比較につながります。
他のヒント
まあ、ほとんど常に 、たとえば数値的に順序付けられた値を持っていることの読みやすさは、比較命令の数を減らすことで得られるどんな小さな利点よりも優先されると思います。
すべての最適化と同様に、それを言った:
- 機能させる
- 測定
- 十分に高速な場合は、そのままにしておきます
- 遅すぎる場合は、最適化します
ああ、私はおそらくget-goのスイッチ/ケースを使用するでしょう! ;-)
これが発生する典型的なケース(投稿のように文字通り5つのオプションがある)は、ffmpegのdecode_cabac_residual関数で発生しました。これはかなり重要でした。プロファイリング(非常に重要-プロファイリングの前に最適化しないでください!)は、H.264ビデオのデコードに費やされた時間の10〜15%以上をカウントしたことを示しています。 ifステートメントは、デコードされるさまざまなタイプの残差に対して異なる方法で計算された一連のステートメントを制御しました。残念ながら、5つのタイプのそれぞれに対して関数が5回複製された場合、コードサイズのために速度が大幅に低下しました残留。そのため、代わりにifチェーンを使用する必要がありました。
プロファイリングは、多くの一般的なテストストリームで行われ、尤度の観点から順序付けされています。上が最も一般的で、下が最も少なかった。これにより、速度が少し向上しました。
今、PHPでは、上記の例のように、Cで得られる低レベルのスタイル速度の向上がはるかに少ないと思われます。
switch / caseステートメントを使用することは、間違いなくここに行く方法です。
これにより、コンパイラー(インタープリター)は、N個の比較を行うことなく、ジャンプテーブルを使用して正しいブランチに到達することができます。 0、1、2、..としてインデックス付けされたアドレスの配列を作成することを考えてください。その後、1回の操作で配列内の正しい配列を探すことができます。
さらに、caseステートメントの構文オーバーヘッドが少ないため、読みやすくなります。
更新:比較がswitchステートメントに適している場合、これはプロファイルに基づく最適化が役立つ領域です。現実的なテスト負荷でPGOビルドを実行することにより、システムはブランチの使用状況情報を生成し、これを使用してパスの最適化を行うことができます。
PHPの質問に答えるのではなく、もう少し一般的に答えます。何らかの解釈が行われるため、PHPには直接適用されません。
多くのコンパイラは、必要に応じてif-elif-elif -...ブロックとの間で変換してブロックを切り替えることができ、elif-partsのテストは十分に単純です(残りのセマンティクスは互換性があります)。 3〜4回のテストの場合、ジャンプテーブルを使用して得られるものは必ずしもありません。
理由は、CPUの分岐予測が実際に何が起こるかを予測するのが得意だからです。実際には、発生する唯一のことは、命令フェッチの圧力が少し高いことですが、それは世界を破壊することはほとんどありません。
ただし、ほとんどのコンパイラは、$ varが定数3であることを認識してから、if..elif ..ブロックの$ varを3に置き換えます。これにより、式は定数になり、trueまたはfalseのいずれかに折り畳まれます。すべての偽のブランチはデッドコード除去によって殺され、真のテストも同様に排除されます。残っているのは$ var == 3の場合です。しかし、PHPがそれほど賢いことに頼ることはできません。一般に、$ varの伝播はできませんが、一部の呼び出しサイトから可能になる場合があります。
呼び出すコードブロックの配列を試すことができます。すべてのコードブロックのオーバーヘッドは同じです。
Perl 6:
our @code_blocks = (
{ 'Code Block 0' },
{ 'Code Block 1' },
{ 'Code Block 2' },
{ 'Code Block 3' },
{ 'Code Block 4' },
{ 'Code Block 5' },
);
if( 0 <= $var < @code_blocks.length ){
@code_blocks[$var]->();
}
コードで追加のテストを実行する必要がある場合、コードの実行は確実に遅くなります。コードのこのセクションでパフォーマンスが重要な場合は、最も一般的なケースを最初に置く必要があります。
通常、「測定してから最適化する」ことに同意します。パフォーマンスが十分に速いかどうかわからないが、コードをできるだけ速く実行する必要があり、修正がテストの再配置と同じくらい簡単な場合は、コードを高速にして測定を行うライブに行った後、仮定(たとえば、3回は80%の確率で発生する)が実際に正しいことを確認します。
純粋に等価解析であるコードでは、スイッチ/ケースに移動します。パフォーマンスが向上するためです。
$var = 3; // we'll say it's set to 3 for this example
switch($var)
{
case 4:
//do something
break;
case 5:
//do something
break;
case:
//do something when none of the provided cases match (same as using an else{ after the elseif{
}
より複雑な比較を行う場合は、スイッチにネストするか、elseifを使用します。
オブジェクト指向言語では、オプションが大量のifを提供する場合、値を含むオブジェクトに動作(たとえば、 // do do
ブロック)を移動するだけです。 / p>
順序を最適化するか、実際にそれを再配置してバイナリツリーにするかのパフォーマンスの違いが大きな違いを生むかどうかを判断できるのはあなただけです。しかし、PHPで(さらに他の言語でも)気にかけるためには、毎秒数千回ではなく、数百万回必要になると思います。
時間を計ってください。上記のif / else if / elseステートメントを、アクションが実行されず、$ varが選択肢の1つではない状態で1秒間に何回実行できるかを確認します。