レジスターが非常に高速である場合、それ以上の数を用意しないのはなぜですか?


88

32ビットでは、8つの「汎用」レジスタがありました。64ビットでは、量は2倍になりますが、64ビットの変更自体とは無関係のようです。
さて、レジスタが非常に高速である(メモリアクセスがない)場合、自然にそれらの数が増えないのはなぜですか?CPUビルダーは、CPUにできるだけ多くのレジスターを機能させるべきではありませんか?私たちが持っている量しか持っていない理由に対する論理的な制限は何ですか?


CPUとGPUは、主にそれぞれキャッシュと大規模マルチスレッドによって遅延を隠します。したがって、CPUにはレジスタが少ない(または必要な)のに対し、GPUには数万のレジスタがあります。これらすべてのトレードオフと要因について説明しているGPUレジスタファイルに関する私の調査用紙を参照してください。
user984260

回答:


119

膨大な数のレジスターしか持たない理由はたくさんあります。

  • それらはほとんどのパイプラインステージに強くリンクしています。まず第一に、あなたは彼らの寿命を追跡し、結果を前の段階に戻す必要があります。複雑さは非常に扱いにくくなり、関係するワイヤの数(文字通り)は同じ割合で増加します。それはエリアで高価です。つまり、最終的には、特定のポイント以降の電力、価格、パフォーマンスで高価になります。
  • 命令のエンコーディングスペースを占有します。16個のレジスタは、ソースとデスティネーションに4ビットを使用し、3オペランド命令(ARMなど)を使用している場合はさらに4ビットを使用します。これは、レジスターを指定するためだけに使用される、非常に多くの命令セットエンコーディングスペースです。これは最終的に、デコード、コードサイズ、そして再び複雑さに影響を与えます。
  • 同じ結果を得るにはもっと良い方法があります...

最近、私たちは本当にたくさんのレジスターを持っています-それらは明示的にプログラムされていないだけです。「登録名変更」があります。小さなセット(8〜32レジスタ)にのみアクセスしますが、実際にははるかに大きなセット(64〜256など)によってサポートされます。次に、CPUは各レジスタの可視性を追跡し、名前を変更したセットに割り当てます。たとえば、連続して何度もレジスタにロード、変更、保存し、キャッシュミスなどに応じてこれらの各操作を実際に個別に実行させることができます。ARMの場合:

ldr r0, [r4]
add r0, r0, #1
str r0, [r4]
ldr r0, [r5]
add r0, r0, #1
str r0, [r5]

Cortex A9コアはレジスタの名前変更を行うので、「r0」への最初のロードは実際には名前が変更された仮想レジスタに行きます-それを「v0」と呼びましょう。ロード、インクリメント、ストアは「v0」で行われます。一方、r0へのロード/変更/保存も再度実行しますが、これはr0を使用する完全に独立したシーケンスであるため、「v1」に名前が変更されます。キャッシュミスが原因で「r4」のポインタからのロードがストールしたとしましょう。これで問題ありません。「r0」の準備が整うまで待つ必要はありません。名前が変更されているので、次のシーケンスを「v1」(これもr0にマップされます)で実行できます-おそらくそれはキャッシュヒットであり、パフォーマンスが大幅に向上しました。

ldr v0, [v2]
add v0, v0, #1
str v0, [v2]
ldr v1, [v3]
add v1, v1, #1
str v1, [v3]

x86は最近、名前が変更されたレジスターの巨大な数に達していると思います(ballpark 256)。つまり、送信元と宛先が何であるかを言うために、すべての命令に対して8ビット×2を使用することになります。コア全体に必要なワイヤの数とサイズが大幅に増加します。したがって、ほとんどの設計者が解決した16〜32個のレジスタの周りにスイートスポットがあり、順不同のCPUデザインでは、レジスタの名前変更がそれを軽減する方法です。

編集:アウトオブオーダー実行とこれに関するレジスタの名前変更の重要性。OOOを取得したら、レジスターの数はそれほど重要ではありません。それらは単なる「一時タグ」であり、はるかに大きな仮想レジスターセットに名前が変更されるためです。小さなコードシーケンスを書くのが難しくなるので、数値が小さすぎないようにしてください。これはx86-32の問題です。制限された8個のレジスタは、多くの一時がスタックを通過することを意味し、コアはメモリへの読み取り/書き込みを転送するために追加のロジックを必要とするためです。OOOがない場合、通常は小さなコアについて話していることになります。その場合、大きなレジスタセットはコスト/パフォーマンスの面で不十分です。

そのため、ほとんどのクラスのCPUで約32の設計済みレジスタで最大になるレジスタバンクサイズには、自然なスイートスポットがあります。x86-32には8つのレジスタがあり、それは明らかに小さすぎます。ARMには16個のレジスタがあり、それは妥協案です。32レジスタは少し多すぎますが、最後の10レジスタは必要ありません。

これは、SSEおよびその他のベクトル浮動小数点コプロセッサー用に取得する追加のレジスターには触れません。これらは整数コアとは独立して実行され、CPUの複雑さを指数関数的に増大させないため、追加のセットとして意味があります。


12
優れた答え-別の理由をミックスに投入したい-レジスタが多いほど、コンテキストの切り替え時にそれらをスタックに投入したり、スタックからプルしたりするのにより多くの時間がかかります。間違いなく大きな問題ではなく、考慮事項です。
ウィルA

7
@WillA良い点。ただし、多数のレジスタを備えたアーキテクチャには、このコストを軽減する方法があります。ABIは通常、ほとんどのレジスタの呼び出し先を保存するため、コアセットを保存するだけで済みます。コンテキストの切り替えは、通常、他のすべての従来のテープに比べて、追加の保存/復元にそれほどコストがかからないほど高価です。SPARCは実際には、レジスタバンクをメモリ領域の「ウィンドウ」にすることでこの問題を回避しているため、これにいくらか対応しています(手で振るようなもの)。
ジョンリプリー、

4
私が確かに予期していなかったそのような徹底的な答えに吹き飛ばされた私の心を考えてみてください。また、なぜ多くの名前付きレジスタが本当に必要ないのかについての説明に感謝します。これは非常に興味深いことです。私は「内部で」何が起こっているのかに完全に興味があるので、あなたの答えを読んで本当に楽しかったです。:)私はあなたが知らないので、答えを受け入れる前にもう少し待つつもりですが、私の+1は確かです。
Xeo

1
レジスターを保存する責任がどこにあるかに関係なく、それがかかる時間は管理オーバーヘッドです。OKです。コンテキストの切り替えはほとんどの場合発生しませんが、割り込みはそうです。手動でコード化されたルーチンはレジスタを節約できますが、ドライバがCで記述されている場合、割り込み宣言された関数がすべてのレジスタを保存する可能性がある場合は、isrを呼び出し、保存されたすべてのレジスタを復元します。IA-32は、RISCアーキテクチャの32以上の正規表現と比較して、15〜20の正規表現で割り込みの利点がありました。
Olof Forshell、

1
すばらしい答えですが、「名前を変更した」レジスタと「実際の」アドレス可能なレジスタを直接比較することには同意しません。x86-32では、256の内部レジスターがあっても、単一の実行ポイントでレジスターに保管された一時的な値を8つ以上使用することはできません。基本的に、レジスタの名前変更はOOEの奇妙な副産物にすぎません。
noop

12

我々ドゥもっとそれらの持っています

ほとんどすべての命令は、1、2、または3つのアーキテクチャ上可視のレジスタを選択する必要があるため、それらの数を拡張すると、各命令のコードサイズが数ビット増加し、コード密度が低下します。また、スレッド状態として保存し、関数のアクティブ化レコードに部分的に保存する必要があるコンテキストの量も増加します これらの操作は頻繁に発生します。パイプラインインターロックは、すべてのレジスターのスコアボードをチェックする必要があり、これは時間とスペースが2次で複雑になります。そしておそらく最大の理由は、すでに定義された命令セットとの互換性にあるのでしょう。

しかし、結局のところ、 レジスタの名前変更の実際には多数のレジスタを使用でき、それらを保存する必要すらありません。CPUには実際には多数のレジスタセットがあり、コードの実行時に自動的にレジスタセットを切り替えます。これは純粋に、より多くのレジスターを取得するために行われます。

例:

load  r1, a  # x = a
store r1, x
load  r1, b  # y = b
store r1, y

r0〜r7のみのアーキテクチャでは、次のコードはCPUによって次のように自動的に書き換えられる可能性があります。

load  r1, a
store r1, x
load  r10, b
store r10, y

この場合、r10は一時的にr1の代わりに使用される隠しレジスタです。CPUは、r1の値が最初のストアの後に再び使用されることはないことを認識できます。これにより、2番目のロードまたは2番目のストアの遅延を必要とせずに、最初のロードを遅延させることができます(通常、オンチップキャッシュヒットでも数サイクルかかります)。


2

これらは常にレジスタを追加しますが、多くの場合、特殊目的の命令(SIMD、SSE2など)に関連付けられているか、特定のCPUアーキテクチャにコンパイルする必要があるため、移植性が低下します。既存の命令は特定のレジスタで機能することが多く、利用可能であれば他のレジスタを利用できません。従来の命令セットとすべて。


1

ここに少し興味深い情報を追加すると、8つの同じサイズのレジスタを使用すると、オペコードが16進表記との一貫性を維持できることがわかります。たとえば、命令push axはx86のオペコード0x50であり、最後のレジスタdiで最大0x57になります。次に、命令pop axは0x58から始まり、0x5Fに達しpop diて、最初のbase-16を完了します。16進数の一貫性は、サイズごとに8つのレジスタで維持されます。


2
x86 / 64では、REX命令のプレフィックスにより、レジスタインデックスがより多くのビットで拡張されます。
Alexey Frunze
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.