C++ コンパイラは、2 倍の演算x*2
をビットシフト演算に最適化しx<<1
ますか?
そう信じたいです。
実際、VS2008 はこれを x+x に最適化します。
01391000 push ecx
int x = 0;
scanf("%d", &x);
01391001 lea eax,[esp]
01391004 push eax
01391005 push offset string "%d" (13920F4h)
0139100A mov dword ptr [esp+8],0
01391012 call dword ptr [__imp__scanf (13920A4h)]
int y = x * 2;
01391018 mov ecx,dword ptr [esp+8]
0139101C lea edx,[ecx+ecx]
x64 ビルドでは、さらに明示的であり、以下を使用します。
int y = x * 2;
000000013FB9101E mov edx,dword ptr [x]
printf("%d", y);
000000013FB91022 lea rcx,[string "%d" (13FB921B0h)]
000000013FB91029 add edx,edx
これは、「最大速度」(/O2) の最適化設定になります。
Raymond Chen のこの記事は興味深いかもしれません。
x/2 が x>>1 と異なるのはいつですか? : http://blogs.msdn.com/oldnewthing/archive/2005/05/27/422551.aspx
レイモンドの引用:
もちろん、コンパイラはこれを自由に認識して、乗算またはシフト演算を書き換えることができます。実際、x+x は乗算やシフトよりも簡単にペアリングできるため、これを行う可能性が非常に高くなります。あなたのシフトまたは 2 倍の乗算は、おそらく add eax, eax 命令に近いものとして書き直されるでしょう。
[...]
シフトが符号ビットで満たされていると仮定しても、x が負の場合、シフトと除算の結果は異なります。
(-1) / 2 ≡ 0
(-1) >> 1 ≡ -1[...]
物語の教訓は、あなたが言いたいことを書くことです。2で割りたい場合は「>>1」ではなく「/2」と書きます。
コンパイラに何をしてもらいたいかではなく、何を望んでいるのかをコンパイラに伝えることが賢明であると想定することしかできません。最適化を望み、プロファイラーを使用し、アルゴリズムの効率を調査します。
VS 2008 は鉱山を x << 1 に最適化しました。
x = x * 2;
004013E7 mov eax,dword ptr [x]
004013EA shl eax,1
004013EC mov dword ptr [x],eax
編集: これは、VS のデフォルトの「デバッグ」構成を使用して、最適化を無効にしました (/Od)。最適化スイッチ (/O1、/O2 (VS "Retail")、または /Ox) のいずれかを使用すると、Rob が投稿した追加の自己コードが生成されます。また、念のため、/Od と /Ox の両方で cl コンパイラx = x << 1
と同じように扱われることを確認しました。x = x * 2
つまり、要約すると、x86 用の cl.exe バージョン 15.00.30729.01 はまったく同じように扱われ* 2
、<< 1
他の最近のコンパイラのほぼすべてが同じことを行うと思います。
x が float の場合はそうではありません。
はい。また、いくつかのシフトの合計として書き換えることができる 2 の累乗以外の乗算など、他の同様の演算も最適化します。また、2 の累乗による除算を右シフトに最適化しますが、符号付き整数を扱う場合、2 つの演算が異なることに注意してください! コンパイラは、結果が正の数と負の数で同じであることを確認するために、追加のビット調整命令を発行する必要がありますが、除算を行うよりも高速です。同様に、係数を 2 の累乗で最適化します。
答えは「速ければ」(または小さければ)です。これは、ターゲット アーキテクチャと特定のコンパイラのレジスタ使用モデルに大きく依存します。一般に、答えは「はい、常に」です。これは、実装する非常に単純なのぞき穴の最適化であり、通常はまともな勝利です。
これは、オプティマイザーができることの始まりにすぎません。コンパイラの動作を確認するには、アセンブラ ソースを生成するスイッチを探します。Digital Mars コンパイラの場合、出力アセンブラは OBJ2ASM ツールで調べることができます。コンパイラがどのように機能するかを知りたい場合は、アセンブラの出力を見ると非常にわかりやすくなります。
それらはすべてこの種の最適化を行っていると確信していますが、それでも関連性があるかどうかは疑問です。古いプロセッサは、シフトと加算によって乗算を行っていましたが、完了するまでに数サイクルかかる場合がありました。一方、最新のプロセッサには、必要なすべてのシフトと加算を 1 クロック サイクル以下で同時に実行できる一連のバレル シフタがあります。これらの最適化が本当に役立つかどうか、実際にベンチマークした人はいますか?
はい、そうします。
言語標準で何かが指定されていない限り、そのような質問に対する保証された回答を得ることはできません。疑わしい場合は、コンパイラにアセンブル コードを吐き出させてチェックしてもらいます。それが本当のことを知る唯一の方法になるでしょう。
@フェルッチオ・バルレッタ
それは良い質問です。私は答えを見つけるためにグーグルに行きました。
Intel プロセッサに関する回答を直接見つけることはできませんでしたが、このページには時間を計ろうとした人がいます。これは、シフトが広告の 2 倍以上速く、倍増することを示しています。ビット シフトは非常に単純なので (乗算はシフトと加算である可能性があります)、これは理にかなっています。
そこで、AMD を Google で検索したところ、2002 年からの Athlon の古い最適化ガイドを見つけました。このガイドには、2 から 32 の間の定数で数値を乗算する最速の方法がリストされています。興味深いことに、それは数値によって異なります。いくつかは広告で、いくつかはシフトです。122ページにあります。
Athlon 64のガイドにも同じことが示されています (164 ページ程度)。乗算は 3 (32 ビットの場合) または 4 (64 ビットの場合) のサイクル操作であり、シフトは 1、加算は 2 であると書かれています。
最適化としてはまだ有用なようです。
ただし、サイクル カウントを無視すると、この種の方法では、(おそらく) 乗算実行ユニットを拘束できなくなるため、定数を使用するものと使用しないものがあるタイトなループで多数の乗算を実行している場合、余分なスケジューリング ルームが必要になる可能性があります。使える。
しかし、それは憶測です。
使用しているコンパイラによって異なります。たとえば、Visual C++ は最適化が苦手なことで有名です。投稿を編集して、使用しているコンパイラを指定すると、答えやすくなります。