通常のフローまたはfor
、if
などの条件付きステートメントでビット演算を使用すると、全体的なパフォーマンスが向上しますか?例えば:
if(i++ & 1) {
}
vs.
if(i % 2) {
}
古代のコンパイラを使用していない限り、このレベルの変換を独自に処理できます。つまり、最新のコンパイラはi % 2
ビット単位のAND
命令を使用します(ターゲットCPUで実行するのが理にかなっている場合に限ります(公平に言えば、通常はそうなります)。
言い換えれば、少なくとも適度に適格なオプティマイザーを備えた適度に最新のコンパイラーでは、これらのパフォーマンスにanyの違いが現れることを期待しないでください。この場合、reasonably
の定義もかなり広範です。数十年前のごく少数のコンパイラーでさえ、この種のマイクロ最適化をまったく問題なく処理できます。
TL; DR最初にセマンティクスを記述し、次に測定されたホットスポットを最適化します
CPUレベルでは、整数モジュラスと除算が最も遅い操作の1つです。しかし、CPUレベルで記述しているのではなく、C++で記述しています。コンパイラはそれを中間表現に変換し、最終的にコンパイル対象のCPUモデルに従ってAssemblyに変換します。
このプロセスでは、コンパイラは Peephole Optimizations を適用しますが、その中には Strength Reduction Optimizations のような(- Wikipediaの礼儀 ):
Original Calculation Replacement Calculation y = x / 8 y = x >> 3 y = x * 64 y = x << 6 y = x * 2 y = x << 1 y = x * 15 y = (x << 4) - x
最後の例は、おそらく最も興味深いものです。 2の累乗による乗算または除算は(手動で)ビットシフト演算に簡単に変換されますが、コンパイラーは一般に、おそらく自分で考えてさらに簡単に認識されない(非常に少なくとも、_(x << 4) - x
手段 x * 15
)。
これは明らかにCPUに依存しますが、ビット単位の操作が完了するまでにCPUサイクルが長くなることはなく、通常はCPUサイクルが少なくなると予想できます。一般に、整数/
および%
は、CPU命令が進むので有名です。とはいえ、特定の命令が以前に完了した最新のCPUパイプラインでは、プログラムが必ずしも高速で実行されるわけではありません。
ベストプラクティスは、理解可能で保守しやすく、実装するロジックを表現するコードを記述することです。この種のマイクロ最適化が明確な違いを生むことは非常にまれなので、プロファイリングが重大なボトルネックを示し、これが大きな違いをもたらすことが証明されている場合にのみ使用する必要があります。さらに、特定のプラットフォームで大きな違いが生じた場合、コンパイラーオプティマイザーが同等であると判断したときに、既にビット単位の演算を置き換えている可能性があります。
デフォルトでは、読み取り可能なコードに最適化する必要があるため、意図した意味を最もよく表す操作を使用する必要があります。 (今日、ほとんどの場合、最も不足しているリソースは人間のプログラマーです。)
したがって、ビットを抽出する場合は&
を使用し、分割可能性をテストする場合、つまり値が偶数か奇数かをテストする場合は%
を使用します。
符号なしの値の場合、両方の演算の効果はまったく同じであり、コンパイラーは除算を対応するビット演算で置き換えるのに十分スマートでなければなりません。心配な場合は、生成されるアセンブリコードを確認できます。
残念ながら、整数除算は符号付きの値ではわずかに不規則です。ゼロに丸められ、%の結果は第1オペランドに応じて符号が変わります。一方、ビット演算は常に切り捨てられます。そのため、コンパイラーは単純なビット操作で除算を置き換えることはできません。代わりに、整数除算のためにルーチンを呼び出すか、不規則性を処理するための追加のロジックでビット操作に置き換えます。これは、最適化レベルと、どのオペランドが定数であるかに依存します。
ゼロでのこの不規則性は、非線形性であるため、悪いことですらあります。たとえば、最近、ADCからの符号付き値の除算を使用したケースがありました。これはARM Cortex M0で非常に高速である必要がありました。この場合は、パフォーマンスと非線形性を取り除くための右シフト。
C演算子は、「パフォーマンス」のサームで有意義に比較することはできません。言語レベルでは「高速」または「低速」の演算子はありません。結果としてコンパイルされたマシンコードのみがパフォーマンスを分析できます。特定の例では、結果のマシンコードは通常まったく同じになります(最初の条件に何らかの理由で後置インクリメントが含まれるという事実を無視した場合)。つまり、パフォーマンスにまったく違いはありません。
両方のオプション用に最適化された-O3コードを生成したコンパイラ(GCC 4.6)は次のとおりです。
int i = 34567;
int opt1 = i++ & 1;
int opt2 = i % 2;
Opt1の生成コード:
l %r1,520(%r11)
nilf %r1,1
st %r1,516(%r11)
asi 520(%r11),1
Opt2の生成コード:
l %r1,520(%r11)
nilf %r1,2147483649
ltr %r1,%r1
jhe .L14
ahi %r1,-1
oilf %r1,4294967294
ahi %r1,1
.L14: st %r1,512(%r11)
したがって、4つの追加の指示...これはprod環境には何もありません。これは時期尚早な最適化であり、複雑さを招くだけです