整数を2で除算するのに最適なオプションは次のうちどれですか?その理由は何ですか?
テクニック1:
x = x >> 1;
テクニック2:
x = x / 2;
ここでx
は整数です。
実行しようとしていることを最もよく説明する操作を使用します。
それらは完全に同等ではないことに注意してください。負の整数に対して異なる結果が得られる場合があります。例えば:
-5 / 2 = -2
-5 >> 1 = -3
最初のものは分割のように見えますか?いいえ。分割したい場合は、x / 2
を使用します。コンパイラーは、可能であればビットシフトを使用するように最適化することができます(強度削減と呼ばれます)。
積み重ねるには、x = x / 2;
の使用を支持する多くの理由があります。
それはあなたの意図をより明確に表現します(レジスタビットなどをいじるビットを扱っていないと仮定します)
とにかくコンパイラはこれをシフト操作に減らします
コンパイラーがそれを削減せず、シフトよりも遅い操作を選択したとしても、これが測定可能な方法でプログラムのパフォーマンスに影響を与える可能性は、それ自体無視できるほど小さいです(そして、それが測定可能に影響する場合、実際のシフトを使用する理由)
除算がより大きな式の一部になる場合、除算演算子を使用すると優先順位が高くなります。
x = x / 2 + 5;
x = x >> 1 + 5; // not the same as above
符号付き演算は、上記の優先順位の問題よりもさらに物事を複雑にする可能性があります
繰り返しになりますが、とにかくコンパイラーは既にこれを行います。実際、定数による除算を、2の累乗だけでなく、あらゆる種類の数値の一連のシフト、加算、および乗算に変換します。これに関する詳細情報へのリンクについては、 この質問 を参照してください。
つまり、バグを導入する可能性が高まる場合を除いて、実際に乗算または除算を行う場合は、シフトをコーディングしても何も購入しません。コンパイラーは、適切な場合にこの種のことをシフトに最適化するほど賢くなかったので、それは一生でした。
どれが最良のオプションであり、整数値を2で割るのはなぜですか?
bestの意味に依存します。
同僚に嫌われたり、コードを読みにくくしたい場合は、間違いなく最初のオプションを選択します。
数値を2で除算する場合は、2番目の数値に進みます。
2つは同等ではありません。数値が負の場合、または大きな式の内部では同じ動作をしません。ビットシフトの優先順位は+
または-
より低く、除算の優先順位は高くなります。
コードの意図を表現するコードを書く必要があります。パフォーマンスが懸念される場合は、心配しないでください。オプティマイザーは、この種のマイクロ最適化で優れた仕事をします。
単純に除算(/
)を使用してください。コンパイラはそれに応じて最適化します。
x / 2
の方が意図が明確であり、コンパイラーが最適化する必要があるため、x / 2
を優先すべきだという他の回答に同意します。
ただし、x >> 1
よりも>>
を好むもう1つの理由は、x
が符号付きint
で負の場合、E1 >> E2
の動作は実装依存であるためです。
セクション6.5.7から、ISO C99標準の箇条書き5:
E1
の結果は、E2
を右シフトしたE1
ビット位置です。E1
に符号なしの型がある場合、またはE1
に符号付きの型と非負の値がある場合、結果の値はE2
/2の商の整数部です。__コードスニペット__。E1
の型が符号付きで負の値である場合、結果の値は実装定義です。
x / 2
はより明確で、x >> 1
はそれほど高速ではありません(マイクロベンチマークによると、Java JVMの場合は約30%高速です)。他の人が指摘したように、負の数の場合、丸めはわずかに異なるため、負の数を処理する場合はこれを考慮する必要があります。一部のコンパイラーは、数値が負にならないことがわかっている場合(これを検証できなかったと思っても)、x / 2
をx >> 1
に自動的に変換する場合があります。
一部のショートカットが可能 であるため、x / 2
でも(遅い)除算CPU命令を使用できない場合がありますが、それでもx >> 1
よりも低速です。
(これはC/C++の質問です。他のプログラミング言語にはより多くの演算子があります。Javaには、符号なし右シフトx >>> 1
もあります。これもまた異なります。平均(平均)値を正しく計算できますa
とb
の非常に大きな値であっても(a + b) >>> 1
が平均値を返すように、これは、たとえば配列インデックスが非常に大きくなる可能性がある場合のバイナリ検索に必要です。 多くのバージョンのバグバイナリ検索の 、平均を計算するのに(a + b) / 2
を使用したため。これは正しく動作しません。正しい解決策は代わりに(a + b) >>> 1
を使用することです)
クヌースは言った:
早すぎる最適化はすべての悪の根源です
したがって、x /= 2;
を使用することをお勧めします
この方法でコードを理解するのは簡単です。また、その形式でのこの操作の最適化は、プロセッサにとって大きな違いを意味しないと思います。
コンパイラの出力を見て、決定に役立ててください。私はこのテストをx86-64で実行しました
gcc(GCC)4.2.1 20070719 [FreeBSD]
godboltでのコンパイラ出力のオンライン も参照してください。
コンパイラは、両方のケースでsarl
(算術右シフト)命令を使用するため、2つの式の類似性を認識します。除算を使用する場合、コンパイラーは負の数に合わせて調整する必要もあります。そのために、符号ビットを最下位ビットにシフトダウンし、それを結果に追加します。これにより、負の数をシフトする際に、除算が行うことと比較して、オフバイワンの問題が修正されます。
除算の場合は2シフトするのに対し、明示的なシフトの場合は1シフトしかないため、ここで他の回答で測定されたパフォーマンスの違いの一部を説明できます。
アセンブリ出力を含むCコード:
除算の場合、入力は次のようになります
int div2signed(int a) {
return a / 2;
}
これは次のようにコンパイルされます
movl %edi, %eax
shrl $31, %eax
addl %edi, %eax
sarl %eax
ret
シフトについても同様
int shr2signed(int a) {
return a >> 1;
}
出力付き:
sarl %edi
movl %edi, %eax
ret
追加されたメモ-
一部のVMベースの言語では、x * = 0.5の方が高速になることがよくあります。特に、actionscriptは、変数の0による除算をチェックする必要がないためです。
x = x / 2;
OR x /= 2;
を使用してください。将来、新しいプログラマが作業する可能性があるためです。そのため、彼はコードの行で何が起こっているのかを見つけるのが簡単になります。誰もがそのような最適化に気付いていないかもしれません。
私はプログラミング競技会の目的のために言っています。一般に、2による除算が何度も行われる非常に大きな入力があり、入力が正または負であることがわかっています。
x >> 1はx/2よりも優れています。私はideone.comで、2つの操作で10 ^ 10を超える除算が行われるプログラムを実行してチェックしました。同じプログラムでx/2は5.5秒近くかかったが、x >> 1は2.6秒近くかかった。
考慮すべきことがいくつかあります。
ビットをシフトするために特別な計算は実際には必要ないため、ビットシフトはより高速になりますが、指摘されているように、負の数には潜在的な問題があります。あなたが正の数を持っていることが保証され、速度を探しているなら、私はビットシフトをお勧めします。
除算演算子は、人間にとって非常に読みやすいものです。したがって、コードを読みやすくする必要がある場合は、これを使用できます。コンパイラの最適化の分野は長い道のりを歩んでいるので、コードを読みやすく理解しやすくすることは良い習慣です。
純粋なパフォーマンスを求めているなら、何百万回も操作できるテストを作成することをお勧めします。実行を数回サンプリング(サンプルサイズ)して、OS /ハードウェア/コンパイラ/コードで統計的に最適な実行を決定します。
CPUに関する限り、ビットシフト操作は除算操作よりも高速です。ただし、コンパイラーはこれを認識しており、可能な範囲で適切に最適化するため、コードが効率的に実行されていることを認識し、最も理にかなって安静な方法でコーディングできます。ただし、unsigned int
は、以前に指摘した理由により、int
よりも(場合によっては)最適化できることに注意してください。符号付き算術が必要ない場合は、符号ビットを含めないでください。
x = x/2;使用するのに適したコードです。しかし、操作は、出力をどのように生成したいかについて、独自のプログラムに依存します。
意図をより明確にします。たとえば、分割したい場合は、x/2を使用し、コンパイラーがそれを最適化して演算子(またはその他)をシフトします。
現在のプロセッサでは、これらの最適化がプログラムのパフォーマンスに影響を与えることはありません。
これに対する答えは、あなたが働いている環境によって異なります。
x /= 2
をx >>= 1
に変換しますが、除算記号の存在はその中にさらに眉をひそめますシフトを使用して除算を行うよりも環境.x >>= 1
は、目的を明確にするためにおそらく最適です。x /= 2
を使用してコードを読みやすくします。コードを見ている次のプログラマーを、シフト操作の10秒のダブルテイクを節約する方が、シフトがコンパイラー最適化よりも効率的であることを不必要に証明するよりも優れています。これらはすべて、符号なし整数を想定しています。単純なシフトは、おそらく署名に必要なものではありません。また、DanielHは、ActionScriptのような特定の言語でx *= 0.5
を使用することについて良い点を挙げています。
mod 2、= 1のテスト。cの構文を知らない。しかし、これは最速かもしれません。
一般的に右シフトは分割します:
q = i >> n; is the same as: q = i / 2**n;
これは、明確さを犠牲にしてプログラムを高速化するために時々使用されます。あなたがやるべきではないと思う。コンパイラは、高速化を自動的に実行するのに十分スマートです。これは、シフトに入れても、明確さを犠牲にして何も得られないことを意味します。
これをご覧ください Practical C++ Programmingのページ
明らかに、次のコードを読む人のためにコードを書いている場合は、「x/2」を明確にしてください。
ただし、速度を目標とする場合は、両方の方法を試して結果を確認してください。数か月前、整数の配列をステップ実行し、各要素を2で除算するビットマップ畳み込みルーチンに取り組みました。 「x/2」を「x >> 1」に置き換えるという古いトリックを含め、あらゆる種類の最適化を行いました。
両方の方法で実際に時間を計ったところ、驚いたことにx/2はx >> 1よりも高速でした。
これは、デフォルトの最適化をオンにしてMicrosoft VS2008 C++を使用していました。
パフォーマンスの面で。 CPUのシフト操作は、オペコードを分割するよりも大幅に高速です。したがって、2で除算したり、2で乗算したりなどはすべて、シフト操作の恩恵を受けます。
ルックアンドフィールについて。エンジニアとして、私たちは化粧品にとても執着して、美しい女性でさえも使用しませんでした! :)
X/Yは正しいものです...そして ">>"シフト演算子..整数を2で除算する場合は、(/)被除数演算子を使用できます。シフト演算子は、ビットをシフトするために使用されます。
x = x/2; x/= 2;このように使用できます。
X >> 1はx/2よりも高速ですが、負の値を扱う場合の>>の適切な使用はもう少し複雑です。次のようなものが必要です。
// Extension Method
public static class Global {
public static int ShiftDivBy2(this int x) {
return (x < 0 ? x + 1 : x) >> 1;
}
}