32ビットでは、8つの「汎用」レジスタがありました。64ビットでは、量は2倍になりますが、64ビットの変更自体とは無関係のようです。
さて、レジスタが非常に高速である(メモリアクセスがない)場合、自然にそれらの数が増えないのはなぜですか?CPUビルダーは、CPUにできるだけ多くのレジスターを機能させるべきではありませんか?私たちが持っている量しか持っていない理由に対する論理的な制限は何ですか?
32ビットでは、8つの「汎用」レジスタがありました。64ビットでは、量は2倍になりますが、64ビットの変更自体とは無関係のようです。
さて、レジスタが非常に高速である(メモリアクセスがない)場合、自然にそれらの数が増えないのはなぜですか?CPUビルダーは、CPUにできるだけ多くのレジスターを機能させるべきではありませんか?私たちが持っている量しか持っていない理由に対する論理的な制限は何ですか?
回答:
膨大な数のレジスターしか持たない理由はたくさんあります。
最近、私たちは本当にたくさんのレジスターを持っています-それらは明示的にプログラムされていないだけです。「登録名変更」があります。小さなセット(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の複雑さを指数関数的に増大させないため、追加のセットとして意味があります。
ほとんどすべての命令は、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番目のストアの遅延を必要とせずに、最初のロードを遅延させることができます(通常、オンチップキャッシュヒットでも数サイクルかかります)。
ここに少し興味深い情報を追加すると、8つの同じサイズのレジスタを使用すると、オペコードが16進表記との一貫性を維持できることがわかります。たとえば、命令push ax
はx86のオペコード0x50であり、最後のレジスタdiで最大0x57になります。次に、命令pop ax
は0x58から始まり、0x5Fに達しpop di
て、最初のbase-16を完了します。16進数の一貫性は、サイズごとに8つのレジスタで維持されます。