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は、最近、名前が変更されたレジスタの数が膨大になると思います(ballpark256)。つまり、ソースとデスティネーションが何であるかを言うために、すべての命令に対して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番目のストアの遅延を必要とせずに、最初のロードを遅延させることができます(オンチップキャッシュヒットでも通常は数サイクルかかります)。
これらは常にレジスタを追加しますが、多くの場合、特別な目的の命令(SIMD、SSE2など)に関連付けられているか、特定のCPUアーキテクチャにコンパイルする必要があるため、移植性が低下します。既存の命令は特定のレジスタで機能することが多く、他のレジスタが利用可能な場合はそれを利用できませんでした。レガシー命令セットとすべて。
ここに少し興味深い情報を追加すると、同じサイズのレジスタが8つあると、オペコードが16進表記との一貫性を維持できることがわかります。たとえば、命令Push ax
はx86のオペコード0x50であり、最後のレジスタdiでは0x57になります。次に、命令pop ax
は0x58から始まり、0x5Fまで上がりますpop di
最初のベース16を完了します。 16進の一貫性は、サイズごとに8つのレジスターで維持されます。