私は質問といくつかの回答やコメントに未検討の仮定がいくつかあるので、質問者がより差別化された答えを持っている方が役立つと思います。
結果として生じるシフトと乗算の相対的な実行時間は、Cとは何の関係もありません。私がCと言うとき、私はそのようなGCCのバージョンなどの特定の実装のインスタンスではなく、言語を意味します。このような馬鹿げた話をするつもりはありませんが、説明のために極端な例を使用します:完全に標準に準拠したCコンパイラを実装し、乗算に1時間かかり、シフトにミリ秒かかります-またはその逆です。CやC ++でのこのようなパフォーマンス制限については知りません。
あなたは議論の中でこの技術を気にかけないかもしれません。あなたの意図はおそらくシフトと乗算の相対的なパフォーマンスをテストすることであり、Cを選択しました。これは一般に低レベルのプログラミング言語として認識されているため、ソースコードが対応する命令に直接変換されることを期待する可能性があるためです このような質問は非常に一般的であり、Cでもソースコードは特定のインスタンスで考えられるほど直接命令に変換されないことを指摘すべきです。以下にいくつかの可能なコンパイル結果を示しました。
ここで、実世界のソフトウェアでこの同等性を代用することの有用性を疑問視するコメントが出てきます。EricLippertのコメントのように、あなたの質問へのコメントの一部を見ることができます。これは、このような最適化に対する一般的な経験豊富なエンジニアからの反応と一致しています。プロダクションコードでバイナリシフトを掛け算と除算の包括的な手段として使用すると、人々はおそらくあなたのコードにうんざりし、ある程度の感情的な反応(「天国のためにJavaScriptについて行われたこの無意味な主張」を聞いたことがあります)それらの反応の理由をよりよく理解しない限り、初心者プログラマーにとって意味がないかもしれません。
これらの理由は主に、相対的なパフォーマンスの比較ですでにわかっているように、そのような最適化の可読性の低下と無益さの組み合わせです。ただし、乗算の代わりにシフトを使用することがこのような最適化の唯一の例である場合、人々がそれほど強い反応を示すとは思いません。あなたのような質問は、さまざまな形でさまざまなコンテキストで頻繁に出てきます。より多くの上級エンジニアが実際に非常に強く反応するのは、少なくとも私が時々持っていると思うのは、人々がそのようなマイクロ最適化をコードベース全体で自由に採用すると、はるかに広い範囲の害の可能性があるということです。Microsoftのような会社で大規模なコードベースで働いている場合、他のエンジニアのソースコードを読んだり、特定のコードを見つけようとすることに多くの時間を費やすことになります。数年のうちに、特にポケットベルでの呼び出しを受けた後の本番システムの停止を修正する必要がある場合など、最も不適切な時期に意味をなそうとしているのは、独自のコードですらあります。金曜日の夜の義務で、友人との楽しい夜に出かけようとしています…コードを読むことに多くの時間を費やしているなら、それが可能な限り読みやすいことに感謝します。お気に入りの小説を読んでいると想像してみてください。しかし、出版社はabbrvを使用する新しいエディションをリリースすることを決定しました。すべてのovr th plc bcs thy thnk svs spc。これは、他のエンジニアがそのような最適化を振りかけた場合、あなたのコードに対する反応と似ています。他の答えが指摘したように、あなたが何を意味するかを明確に述べる方が良いです、
ただし、そのような環境であっても、これまたは他の同等性を知っていることが期待されるインタビューの質問を解くことがあります。それらを知ることは悪くなく、優れたエンジニアはバイナリシフトの算術効果を知っているでしょう。これは良いエンジニアになるとは言わなかったが、私の意見では、良いエンジニアは知っているだろうことに注意してください。特に、通常はインタビューループの終わり頃に、このスマートエンジニアリングの「トリック」をコーディングの質問で明らかにし、彼/彼女を証明する喜びを期待してあなたに広く笑うマネージャーを見つけることができます。 、また、以前は熟練したエンジニアの1人であり、「単なる」マネージャーではありません。そのような状況では、感銘を受けたように見えるようにし、啓発的なインタビューに感謝します。
Cで速度の違いが見られなかったのはなぜですか?最もありそうな答えは、どちらも同じアセンブリコードになったということです。
int shift(int i) { return i << 2; }
int multiply(int i) { return i * 2; }
両方にコンパイルできます
shift(int):
lea eax, [0+rdi*4]
ret
最適化を行わないGCC、つまり「-O0」フラグを使用すると、次のようになります:
shift(int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov eax, DWORD PTR [rbp-4]
sal eax, 2
pop rbp
ret
multiply(int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov eax, DWORD PTR [rbp-4]
add eax, eax
pop rbp
ret
ご覧のとおり、GCCに "-O0"を渡しても、生成されるコードの種類が多少賢くないという意味ではありません。特に、この場合でもコンパイラーは乗算命令の使用を避けていることに注意してください。同じ実験を、他の数によるシフトや、2のべき乗ではない数による乗算でも繰り返すことができます。プラットフォームでは、シフトと加算の組み合わせが表示されますが、乗算は表示されない可能性があります。乗算とシフトのコストが実際に同じ場合、コンパイラーがすべてのケースで乗算の使用を避けることは明らかに偶然のように思えますか?しかし、私は証明のために仮定を提供するつもりはないので、先に進みましょう。
上記のコードを使用してテストを再実行し、速度の違いに気づいたかどうかを確認できます。それでも、乗算がないことからわかるように、シフトと乗算のテストは行っていませんが、特定のインスタンスでのシフトと乗算のC操作に対してGCCが特定のフラグセットで生成したコードです。そのため、別のテストでは、アセンブリコードを手動で編集し、代わりに「乗算」メソッドのコードで「imul」命令を使用できます。
コンパイラーのこれらのスマートのいくつかを無効にしたい場合は、より一般的なシフトおよび乗算メソッドを定義でき、次のような結果になります。
int shift(int i, int j) { return i << j; }
int multiply(int i, int j) { return i * j; }
次のアセンブリコードが生成される場合があります。
shift(int, int):
mov eax, edi
mov ecx, esi
sal eax, cl
ret
multiply(int, int):
mov eax, edi
imul eax, esi
ret
ここで最終的に、GCC 4.9の最高の最適化レベルであっても、テストに最初に着手したときに予想されるアセンブリ命令の式があります。パフォーマンスの最適化において、それ自体が重要な教訓になると思います。コンパイラが適用できるスマートの観点から、コード内の具体的な定数を変数に置き換えることで生じた違いを見ることができます。shift-multiply置換のようなマイクロ最適化は、コンパイラが通常単独で簡単に実行できる非常に低レベルの最適化です。パフォーマンスにはるかに影響を与える他の最適化には、コードの意図を理解する必要があります多くの場合、コンパイラはアクセスできないか、何らかの発見的手法によってのみ推測できます。ソフトウェアエンジニアとしてあなたが入る場所であり、通常、乗算をシフトに置き換える必要はありません。これには、I / Oを生成し、プロセスをブロックする可能性のあるサービスへの冗長な呼び出しを回避するなどの要素が含まれます。ハードディスクに移動する場合、または既にメモリに保存されているデータから派生した余分なデータをリモートデータベースに移動する場合、待機時間は100万命令の実行を上回ります。今、私たちはあなたの元の質問から少し離れていると思いますが、特にコードの翻訳と実行を把握し始めたばかりの人がいる場合、これを質問者に指摘すると思います。
それで、どれがより速くなりますか?パフォーマンスの違いを実際にテストするために選択した良い方法だと思います。一般に、いくつかのコード変更の実行時パフォーマンスに驚くのは簡単です。現代のプロセッサが採用している多くの技術があり、ソフトウェア間の相互作用も複雑になる可能性があります。ある状況で特定の変更に対して有益なパフォーマンス結果が得られたとしても、このタイプの変更が常にパフォーマンス上のメリットをもたらすと結論付けるのは危険だと思います。そのようなテストを1回実行するのは危険だと思います。「わかりました。今はどちらが速いかわかりました!」そして、測定を繰り返さずに、その同じ最適化を実稼働コードに無差別に適用します。
では、シフトが乗算より速い場合はどうでしょうか?なぜそうなるのかは確かに示されています。上記でわかるように、GCCは、(最適化を行わなくても)他の命令を優先して直接乗算を行わないことをお勧めします。インテル64およびIA-32アーキテクチャー最適化リファレンス・マニュアルは、あなたのCPU命令の相対的なコストのアイデアを与えるだろう。命令のレイテンシとスループットに重点を置いた別のリソースは、http: //www.agner.org/optimize/instruction_tables.pdfです。。これらは絶対実行時間の適切な述語ではなく、相互に相対的な命令のパフォーマンスの述語であることに注意してください。タイトループでは、テストがシミュレートしているため、「スループット」のメトリックが最も重要になります。これは、特定の命令を実行するときに実行ユニットが通常拘束されるサイクル数です。
では、シフトが乗算より速くない場合はどうでしょうか?上で述べたように、現代のアーキテクチャは非常に複雑になる可能性があり、分岐予測、キャッシュ、パイプライン化、並列実行ユニットなどによって、論理的に等価な2つのコードの相対的なパフォーマンスを予測することが困難になる場合があります。私は本当にこれを強調したいと思います。なぜなら、こういう質問に対するほとんどの答えと、シフトが乗算より速いということは(もう)真実ではないと言っている人々のキャンプに満足していないからです。
いいえ、私が知っている限りでは、1970年代に、または乗算ユニットとビットシフターのコストの違いを突然無効にするときは、秘密のエンジニアリングソースを発明しませんでした。論理ゲートの点で、そして確かに論理演算の点での一般的な乗算は、多くのシナリオで、多くのアーキテクチャのバレルシフターを使用したシフトよりもさらに複雑です。これがデスクトップコンピュータの全体的なランタイムにどのように変換されるかは、少し不透明かもしれません。特定のプロセッサにどのように実装されているのかはわかりませんが、乗算の説明は次のとおりです。整数乗算は、最新のCPUでの加算と本当に同じ速度ですか
ここにバレルシフターの説明がありますが。前の段落で参照したドキュメントは、CPU命令のプロキシによる操作の相対コストに関する別の見解を示しています。Intelのエンジニアは、よく似た質問をしているようです。Intel開発者ゾーンフォーラムは、コア2デュオプロセッサでの整数の乗算と加算のクロックサイクルです。
はい、ほとんどの実際のシナリオで、そしてほぼ確実にJavaScriptで、パフォーマンスのためにこの同等性を悪用しようとすることは、おそらく無駄な仕事です。ただし、乗算命令の使用を強制し、ランタイムに違いが見られない場合でも、それは使用したコストメトリックスの性質によるものであり、正確には、コストの違いがないためではありません。エンドツーエンドランタイムは1つのメトリックであり、それが私たちが関心を持っている唯一のメトリックである場合、すべてが順調です。しかし、それは乗算とシフトの間のすべてのコストの違いが単に消えたという意味ではありません。そして、その考えを質問者に伝えることは確かに良い考えではないと思います。質問者は暗黙的または別の方法で、明らかに現代のコードの実行時間とコストに関与する要因の考えを得始めています。エンジニアリングは常にトレードオフに関係しています。ユーザーが最終的に見る実行時間を表示するために現代のプロセッサーがどのようなトレードオフを行ったかについての問い合わせと説明は、より差別化された答えをもたらす可能性があります。そして、「最適化」の性質をより一般的に理解する必要があるため、マイクロ最適化されたコードを読みやすくするより少ないエンジニアをチェックしたい場合は、「これはもはや真実ではありません」よりもより差別化された答えが保証されると思います特定のインスタンスを単に古くなったものとして参照するよりも、さまざまな多様な化身を見つけます。