最新のプロセッサではビット単位の操作が非常に高速であることがわかっています。32ビットまたは64ビットを並列で操作できるため、ビット単位の操作には1クロックサイクルしかかかりません。ただし、加算は、少なくとも1つ、場合によっては最大12個のビット単位の操作で構成される複雑な操作であるため、当然、3〜4倍遅くなると考えました。単純なベンチマークの後、ビット単位の演算(XOR、OR、ANDなど)のどれとでも加算が正確に速いことを見て驚いた。誰もこれに光を当てることができますか?
最新のプロセッサではビット単位の操作が非常に高速であることがわかっています。32ビットまたは64ビットを並列で操作できるため、ビット単位の操作には1クロックサイクルしかかかりません。ただし、加算は、少なくとも1つ、場合によっては最大12個のビット単位の操作で構成される複雑な操作であるため、当然、3〜4倍遅くなると考えました。単純なベンチマークの後、ビット単位の演算(XOR、OR、ANDなど)のどれとでも加算が正確に速いことを見て驚いた。誰もこれに光を当てることができますか?
回答:
CPU設計者が高速化に必要な回路を追加したため、追加は高速です。ビット単位の操作よりもかなり多くのゲートが必要ですが、CPU設計者がそれを価値があると判断するのに十分な頻度です。https://en.wikipedia.org/wiki/Adder_(electronics)を参照してください。
どちらも、単一のCPUサイクル内で実行するのに十分な速さにすることができます。これらは同等に高速ではありません-追加にはビット単位の操作よりも多くのゲートと遅延が必要ですが、プロセッサが1クロックサイクルで実行できるほど十分に高速です。命令のデコードおよび制御ロジックには命令ごとのレイテンシオーバーヘッドがあり、そのレイテンシはビット単位の操作を行うレイテンシよりも大幅に大きいため、2つの間の差はそのオーバーヘッドによって圧倒されます。 AProgrammerの答えとPaul92の答えは、これらの効果をよく説明しています。
いくつかの側面があります。
ビット演算と加算の相対的なコスト。単純な加算器は、単語の幅に線形に依存するゲート深さを持ちます。ゲートの観点からよりコストのかかる代替アプローチがあり、深さを減らします(IIRCの深さは、単語の幅の対数に依存します)。他の人はそのような技術の参考文献を与えていますが、遅延を追加する制御ロジックが必要なため、操作のコストを考えると、違いもそれほど重要ではないことを指摘します。
次に、プロセッサには通常クロックが使用されているという事実があります(一部の研究や特別な目的の非クロック設計は知っていますが、市販されているものもありません)。これは、操作の速度が何であれ、クロックサイクルの整数倍でかかることを意味します。
最後に、マイクロアーキテクチャーに関する考慮事項があります。必要なものを測定しますか?現在、プロセッサは、アウトオブオーダー実行など、パイプライン化されたマルチスカラーである傾向があります。これは、完了のさまざまな段階で、複数の命令を同時に実行できることを意味します。ある操作が他の操作よりも時間がかかることを測定値で示したい場合は、その違いを隠すことが目標であるため、これらの側面を考慮する必要があります。独立したデータを使用する場合、加算演算とビット演算のスループットが同じになる可能性が非常に高くなりますが、レイテンシの測定値または演算間の依存関係の導入はそうでない場合があります。また、メジャーのボトルネックは、たとえばメモリアクセスではなく実行にあることを確認する必要があります。
paddw
クロックごとに2でvector-int加算(たとえば)しか実行できませんが、pand
クロックごとに3でブール値(など)を実行できます。(Skylakeは、3つのベクトル実行ポートすべてにベクトル加算器を配置します。)
CPUはサイクルで動作します。各サイクルで、何かが起こります。通常、1つの命令を実行するにはより多くのサイクルが必要ですが、複数の命令が異なる状態で同時に実行されます。
たとえば、単純なプロセッサでは、命令ごとにフェッチ、実行、保存の3つのステップがあります。常に3つの命令が処理されています。1つはフェッチされ、1つは実行され、1つはその結果を保存します。これはパイプラインと呼ばれ、この例では3つのステージがあります。最新のプロセッサには、15以上のステージを持つパイプラインがあります。ただし、加算とほとんどの算術演算は通常1段階で実行されます(命令自体ではなく、ALUによる2つの数値の加算演算について話します-プロセッサアーキテクチャによっては、命令に必要な場合がありますメモリから引数を取得し、条件を実行し、結果をメモリに保存するためのサイクルが増えます)。
サイクルの期間は、最も長いクリティカルパスによって決まります。基本的に、ピップラインのある段階が完了するのに必要な最長時間です。CPUをより高速にしたい場合は、クリティカルパスを最適化する必要があります。クリティカルパス自体を減らすことができない場合、パイプラインの2ステージに分割でき、CPUをほぼ2倍の周波数でクロックできるようになります(これを防ぐことができるクリティカルパスが他にない場合) )。ただし、これにはオーバーヘッドが伴います。パイプラインのステージ間にレジスタを挿入する必要があります。つまり、実際には2倍の速度は得られず(レジスターはデータを保存するのに時間が必要です)、設計全体が複雑になります。
加算を実行するための非常に効率的な方法(キャリールックアヘッド加算器など)がすでにあり、加算はプロセッサ速度のクリティカルパスではないため、複数のサイクルに分割する意味はありません。
また、複雑に思えるかもしれませんが、ハードウェアでは並行して非常に高速に処理できることに注意してください。
プロセッサにはクロックが供給されているため、一部の命令が他の命令よりも明らかに高速に実行できる場合でも、同じサイクル数を消費する可能性があります。
レジスタと実行ユニット間でデータを転送するために必要な回路は、加算器よりもはるかに複雑であることがわかるでしょう。
単純なMOV(レジスタからレジスタ)命令はビット単位のロジックよりも計算量が少なくなりますが、MOVとADDは通常1サイクルかかります。MOVを2倍の速度で作成できる場合、CPUは2倍の速度でクロックされ、ADDは2サイクルになります。
加算は、キャリービットが64ビットアキュムレータをリップルするのを待たないように十分に重要です。その用語はキャリールックアヘッド加算器であり、基本的には8ビットCPU(およびそのALU)以上の一部です。実際、最新のプロセッサーは完全な乗算にもそれほど多くの実行時間を必要としない傾向があります。キャリールックアヘッドは、実際にはプロセッサー設計者のツールボックスにある非常に古い(そして比較的手頃な)ツールです。
lea
命令を使用する場合)。
ビット単位演算よりも多くのサイクルを必要とする追加機能を備えたプロセッサーを見つけるのは難しいと思います。ほとんどのプロセッサは、プログラムカウンタをインクリメントするために、命令サイクルごとに少なくとも1回の加算を実行する必要があるためです。単なるビット単位の操作はそれほど便利ではありません。
(クロックサイクルではなく命令サイクル-たとえば、6502はパイプライン化されておらず命令キャッシュがないため、命令ごとに最低2クロックサイクルかかります)
欠けている本当の概念は、クリティカルパスの概念です。チップ内では、1サイクル内で実行できる最長の操作が、ハードウェアレベルで、チップのクロック速度を決定します。
これの例外は(まれに使用され、ほとんど商業化されていない)非同期ロジックです。これは、ロジック伝搬時間、デバイス温度などに応じて異なる速度で実際に実行されます。
ゲートレベルでは、追加するのにより多くの作業が必要であるため、時間がかかります。ただし、そのコストは問題にならないほど十分に些細なものです。
最新のプロセッサにはクロックが供給されています。このクロックレートの倍数以外では命令を実行できません。クロックレートを高くした場合、ビット単位操作の速度を最大化するには、追加に少なくとも2サイクルを費やす必要があります。2サイクル分の時間を実際に必要としないため、この時間の多くは待機することに費やされます。1.1(またはそのような数字)だけが必要でした。チップの追加は、市場の他の誰よりも遅くなりました。
さらに悪いことに、ビット単位の演算を追加または実行するという単なる行為は、サイクル中に起こっていることのほんの一部にすぎません。サイクル内で命令をフェッチ/デコードできる必要があります。サイクル内でキャッシュ操作を実行できる必要があります。単純な加算またはビット演算と同じタイムスケールで、他の多くのことが行われています。
もちろん、解決策は、非常に深いパイプラインを開発し、これらのタスクをビット単位の操作で定義される小さなサイクル時間に収まる小さな部分に分割することです。Pentium 4は、これらの深いパイプライン用語で思考の限界を有名に示しました。あらゆる種類の問題が発生します。特に、分岐が必要になるのは、データを取得したらパイプラインをフラッシュする必要があるため、分岐が難しくなることで有名です。
最新のプロセッサにはクロックが供給されています。すべての操作には、整数倍のクロックサイクルが必要です。プロセッサの設計者は、クロックサイクルの長さを決定します。ここには2つの考慮事項があります。1つは、ハードウェアの速度です。たとえば、単一のNANDゲートの遅延として測定されます。これは、使用されるテクノロジー、および速度と電力使用量のようなトレードオフに依存します。プロセッサの設計とは無関係です。2つ目に、設計者は、クロックサイクルの長さが単一のNANDゲートのn遅延に等しいと決定します。nは10、30、またはその他の値です。
この選択nは、1サイクルで処理できる複雑な操作の方法を制限します。15のNAND遅延では実行できないが、16で実行できる操作があります。したがって、n = 16を選択すると、そのような操作をサイクルで実行できることを意味し、n = 15を選択すると、実行できないことを意味します。
設計者はnを選択して、多くの重要な操作を1サイクル、あるいは2サイクルまたは3サイクルで実行できるようにします。nはローカルに最適に選択されます。nをn-1に置き換えた場合、ほとんどの操作は少し速くなりますが、一部(実際にn個のNAND遅延を完全に必要とする操作)は遅くなります。全体のプログラム実行が平均的に速くなるように、操作が遅くなることが少ない場合は、n-1を選択します。n + 1を選択することもできます。これにより、ほとんどの操作が少し遅くなりますが、n遅延内で実行できないがn + 1遅延内で実行できる多くの操作がある場合、プロセッサ全体が高速になります。
ここであなたの質問:加算と減算は非常に一般的な操作なので、1サイクルで実行できるようにしたいです。その結果、AND、ORなどがより高速に実行できることは重要ではありません。それらはまだその1サイクルを必要とします。もちろん、AND、ORなどを「計算」するユニットは、親指をいじるのに多くの時間がありますが、それは仕方がありません。
操作がn個のNAND遅延内で実行できるかどうかだけではないことに注意してください:たとえば、少し賢いことで追加を高速化でき、非常に賢いことでさらに高速にできます。 、最後に、プロセッサは非常に高速で非常に高価な回路と少し遅くて安価な回路を混在させることができるため、より多くのお金を費やすことで1つの操作を十分に速くすることができます。
今、あなたは可能性がクロック速度がそれほど高く作る/これだけの単純なビット演算を1サイクルで実行することを短く、二つ以上で他のすべてのサイクル。それはおそらくプロセッサの速度を低下させるでしょう。2サイクルかかる操作では、通常、不完全な命令をあるサイクルから次のサイクルに移動するためのオーバーヘッドがあるため、2サイクルでは実行に2倍の時間がかかることを意味しません。したがって、2サイクルで加算を行うには、クロック速度を2倍にすることはできません。
既存の回答で明示的に言及されていないいくつかのことを修正しましょう。
最新のプロセッサではビット単位の演算が非常に高速であることを知っています。32ビットまたは64ビットで並列に演算できるためです。
これは本当です。CPUを「XX」ビットとしてラベル付けすることは、通常(常にではない)、その一般的な構造(レジスタ幅、アドレス指定可能なRAMなど)のほとんどがXXビットのサイズ(多くの場合「+/- 1」など)であることを意味します。しかし、質問に関しては、32ビットまたは64ビットのCPUが32ビットまたは64ビットで基本的なビット操作を一定時間で行うと安全に仮定できます。
したがって、ビット単位の操作には1クロックサイクルしかかかりません。
この結論は必ずしも当てはまりません。特に、豊富な命令セット(google CISCとRISC)を備えたCPUは、単純なコマンドでも簡単に1サイクル以上かかることがあります。インターリーブを使用すると、単純なコマンドでも3クロックのfetch-exec-storeに分割される場合があります(例)。
ただし、追加は複雑な操作です
いいえ、整数の加算は簡単な操作です。同様に減算。加算器を完全なハードウェアに実装するのは非常に簡単であり、基本的なビット操作と同じように瞬時に処理を行います。
これは、少なくとも1つ、場合によっては最大12個のビット単位の操作で構成されているため、当然3〜4倍遅くなると思いました。
トランジスタを3〜4倍使用しますが、無視できる全体像と比較してください。
単純なベンチマークの後、ビット単位の演算(XOR、OR、ANDなど)とまったく同じように加算が高速であることに驚きました。誰もこれに光を当てることができますか?
はい:整数の加算はビット単位の演算です(他のビットよりも数ビット多いですが、それでも)。段階的に何もする必要はありません。複雑なアルゴリズム、クロック、その他のことは必要ありません。
CPUアーキテクチャよりも多くのビットを追加する場合は、段階的に実行する必要があるというペナルティが発生します。ただし、これは別のレベルの複雑さです(アセンブリ/マシンコードレベルではなく、プログラミング言語レベル)。これは、過去(または今日の小さな組み込みCPUでの)共通の問題でした。PCなどの場合、32ビットまたは64ビットで、最も一般的なデータ型でこれが問題点になり始めるのに十分です。
imul rax, rcx
レイテンシー3c、Intel Sandybridgeファミリー、AMD Ryzenでのスループット1cに1つ)。64ビットの完全乗算(rdx:raxで128ビットの結果を生成する)でも、待ち時間とスループットは同じですが、2つのuop(異なるポートで並列に実行)として実装されます。(手順表と優れたマイクロアーチガイドについては、agner.org / optimizeを参照してください)。
uint32_t
値の追加。これは現在でも32ビットターゲットのint64_tに関連していますAVRは8ビットRISCマイクロコントローラーなので、32ビット整数には4つの命令が必要です:godbolt.org/g/wre0fM