Wenn die Register so blitzschnell sind, warum haben wir dann nicht mehr davon?

88

In 32bit hatten wir 8 "Allzweck" -Register. Mit 64-Bit verdoppelt sich die Menge, aber es scheint unabhängig von der 64-Bit-Änderung selbst zu sein.
Wenn die Register so schnell sind (kein Speicherzugriff), warum gibt es dann natürlich nicht mehr davon? Sollten CPU-Builder nicht so viele Register wie möglich in die CPU einbauen? Was ist die logische Einschränkung, warum wir nur den Betrag haben, den wir haben?

Xeo
quelle
CPUs und GPUs verbergen die Latenz hauptsächlich durch Caches bzw. Massive Multithreading. CPUs haben (oder benötigen) nur wenige Register, während GPUs Zehntausende von Registern haben. Siehe mein Umfragepapier zur GPU-Registerdatei, in dem all diese Kompromisse und Faktoren erörtert werden.
user984260

Antworten:

119

Es gibt viele Gründe, warum Sie nicht nur eine große Anzahl von Registern haben:

  • Sie sind eng mit den meisten Pipeline-Phasen verbunden. Für den Anfang müssen Sie ihre Lebensdauer verfolgen und die Ergebnisse an frühere Phasen zurückleiten. Die Komplexität wird sehr schnell unlösbar und die Anzahl der (buchstäblich) beteiligten Drähte wächst mit der gleichen Geschwindigkeit. Es ist flächenmäßig teuer, was letztendlich bedeutet, dass es nach einem bestimmten Punkt teuer in Bezug auf Leistung, Preis und Leistung ist.
  • Es nimmt Platz für die Befehlskodierung ein. 16 Register belegen 4 Bit für Quelle und Ziel und weitere 4, wenn Sie 3-Operanden-Befehle (z. B. ARM) haben. Das ist eine Menge Befehlssatz-Codierungsraum, der nur zur Angabe des Registers benötigt wird. Dies wirkt sich schließlich auf die Decodierung, die Codegröße und erneut auf die Komplexität aus.
  • Es gibt bessere Möglichkeiten, um das gleiche Ergebnis zu erzielen ...

Heutzutage haben wir wirklich viele Register - sie sind einfach nicht explizit programmiert. Wir haben "Register Umbenennung". Während Sie nur auf einen kleinen Satz (8-32 Register) zugreifen, werden diese tatsächlich von einem viel größeren Satz (z. B. 64-256) unterstützt. Die CPU verfolgt dann die Sichtbarkeit jedes Registers und ordnet sie dem umbenannten Satz zu. Sie können beispielsweise mehrere Male hintereinander laden, ändern und dann in einem Register speichern und jede dieser Operationen unabhängig von Cache-Fehlern usw. unabhängig ausführen lassen. In ARM:

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

Cortex A9-Kerne registrieren das Umbenennen von Registern, sodass das erste Laden von "r0" tatsächlich in ein umbenanntes virtuelles Register erfolgt - nennen wir es "v0". Das Laden, Inkrementieren und Speichern erfolgt auf "v0". In der Zwischenzeit führen wir auch wieder ein Laden / Ändern / Speichern von r0 durch, das jedoch in "v1" umbenannt wird, da dies eine völlig unabhängige Sequenz ist, die r0 verwendet. Angenommen, die Last vom Zeiger in "r4" ist aufgrund eines Cache-Fehlers ins Stocken geraten. Das ist in Ordnung - wir müssen nicht warten, bis "r0" fertig ist. Da es umbenannt wurde, können wir die nächste Sequenz mit "v1" (ebenfalls auf r0 abgebildet) ausführen - und vielleicht ist das ein Cache-Hit und wir hatten gerade einen riesigen Leistungsgewinn.

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

Ich denke, x86 hat heutzutage eine gigantische Anzahl umbenannter Register (Baseballstadion 256). Das würde bedeuten, 8 Bit mal 2 für jeden Befehl zu haben, nur um zu sagen, was die Quelle und das Ziel sind. Dies würde die Anzahl der im Kern benötigten Drähte und seine Größe massiv erhöhen. Es gibt also einen Sweet Spot zwischen 16 und 32 Registern, mit dem sich die meisten Designer zufrieden gegeben haben, und bei CPU-Designs außerhalb der Reihenfolge ist das Umbenennen von Registern der Weg, dies zu verringern.

Bearbeiten : Die Bedeutung der Ausführung außerhalb der Reihenfolge und der Umbenennung von Registern. Sobald Sie OOO haben, spielt die Anzahl der Register keine Rolle mehr, da es sich nur um "temporäre Tags" handelt, die in den viel größeren virtuellen Registersatz umbenannt werden. Sie möchten nicht, dass die Zahl zu klein ist, da es schwierig wird, kleine Codesequenzen zu schreiben. Dies ist ein Problem für x86-32, da die begrenzten 8 Register bedeuten, dass viele temporäre Elemente den Stapel durchlaufen und der Kern zusätzliche Logik benötigt, um Lese- / Schreibvorgänge in den Speicher weiterzuleiten. Wenn Sie kein OOO haben, sprechen Sie normalerweise von einem kleinen Kern. In diesem Fall ist ein großer Registersatz ein schlechter Kosten- / Leistungsvorteil.

Es gibt also einen natürlichen Sweet Spot für die Größe der Registerbank, der für die meisten CPU-Klassen maximal 32 Architekturregister umfasst. x86-32 hat 8 Register und ist definitiv zu klein. ARM hat 16 Register und es ist ein guter Kompromiss. 32 Register sind etwas zu viele, wenn überhaupt - Sie brauchen am Ende nicht die letzten 10 oder so.

Nichts davon berührt die zusätzlichen Register, die Sie für SSE und andere Vektor-Gleitkomma-Coprozessoren erhalten. Diese sind als zusätzlicher Satz sinnvoll, da sie unabhängig vom ganzzahligen Kern ausgeführt werden und die Komplexität der CPU nicht exponentiell erhöhen.

John Ripley
quelle
12
Hervorragende Antwort - ich möchte einen weiteren Grund in die Mischung einbringen - je mehr Register man hat, desto länger dauert es, sie beim Kontextwechsel auf den Stapel zu werfen oder vom Stapel zu ziehen. Auf jeden Fall nicht das Hauptproblem, sondern eine Überlegung.
Will A
7
@ WillA guter Punkt. Architekturen mit vielen Registern bieten jedoch Möglichkeiten, diese Kosten zu senken. Der ABI speichert normalerweise die meisten Register, sodass Sie nur einen Kernsatz speichern müssen. Das Umschalten des Kontexts ist normalerweise so teuer, dass das zusätzliche Speichern / Wiederherstellen im Vergleich zu allen anderen Bürokratien nicht viel kostet. SPARC umgeht dies tatsächlich, indem es die Registerbank zu einem "Fenster" in einem Speicherbereich macht, so dass es mit diesem etwas skaliert (Art von Handbewegung).
John Ripley
4
Betrachten Sie mich als überwältigt von einer so gründlichen Antwort, die ich mit Sicherheit nicht erwartet hatte. Vielen Dank auch für die Erklärung, warum wir nicht wirklich so viele benannte Register brauchen, das ist sehr interessant! Ich habe es wirklich genossen, Ihre Antwort zu lesen, weil ich total interessiert bin an dem, was "unter der Haube" vor sich geht. :) Ich werde noch ein bisschen warten, bevor ich eine Antwort akzeptiere, weil du es nie weißt, aber meine +1 ist sicher.
Xeo
1
Unabhängig davon, wo die Verantwortung für das Speichern von Registern liegt, ist der Verwaltungsaufwand zeitaufwändig. OK, Kontextumschaltung ist möglicherweise nicht der am häufigsten auftretende Fall, Interrupts jedoch. Handcodierte Routinen sparen möglicherweise Register, aber wenn Treiber in C geschrieben sind, speichert die Interrupt-deklarierte Funktion wahrscheinlich jedes einzelne Register, ruft das isr auf und stellt dann alle gespeicherten Register wieder her. IA-32 hatte einen Interrupt-Vorteil mit seinen 15-20 Regs im Vergleich zu 32 + Regs von RISC-Architekturen.
Olof Forshell
1
Ausgezeichnete Antwort, aber ich bin nicht einverstanden mit dem direkten Vergleich von "umbenannten" Registern mit "echten" adressierbaren Registern. Unter x86-32 können Sie selbst mit 256 internen Registern nicht mehr als 8 temporäre Werte verwenden, die in Registern an einem einzelnen Ausführungspunkt gespeichert sind. Grundsätzlich ist das Umbenennen von Registern nur ein merkwürdiges Nebenprodukt von OOE, nichts weiter.
Noop
12

Wir tun haben mehr von ihnen

Da fast jeder Befehl 1, 2 oder 3 architektonisch sichtbare Register auswählen muss, würde eine Erweiterung ihrer Anzahl die Codegröße bei jedem Befehl um mehrere Bits erhöhen und so die Codedichte verringern. Es erhöht auch die Menge an Kontext , die als Thread-Status gespeichert und teilweise im Aktivierungsdatensatz einer Funktion gespeichert werden muss . Diese Operationen treten häufig auf. Pipeline-Verriegelungen müssen für jedes Register eine Anzeigetafel überprüfen, und dies hat eine quadratische zeitliche und räumliche Komplexität. Und vielleicht ist der größte Grund einfach die Kompatibilität mit dem bereits definierten Befehlssatz.

Aber es stellt sich heraus, dank Umbenennung registrieren , wir wirklich haben viele Register zur Verfügung, und wir brauchen noch nicht einmal , sie zu retten. Die CPU verfügt tatsächlich über viele Registersätze und wechselt automatisch zwischen diesen, wenn Ihr Code ausgeführt wird. Dies geschieht lediglich, um mehr Register zu erhalten.

Beispiel:

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

In einer Architektur mit nur r0-r7 kann der folgende Code von der CPU automatisch wie folgt umgeschrieben werden:

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

In diesem Fall ist r10 ein verstecktes Register, das r1 vorübergehend ersetzt. Die CPU kann erkennen, dass der Wert von r1 nach dem ersten Speichern nie wieder verwendet wird. Dadurch kann das erste Laden verzögert werden (selbst ein On-Chip-Cache-Treffer dauert normalerweise mehrere Zyklen), ohne dass die Verzögerung des zweiten Ladens oder des zweiten Speichers erforderlich ist.

DigitalRoss
quelle
2

Sie fügen ständig Register hinzu, sind jedoch häufig an spezielle Anweisungen (z. B. SIMD, SSE2 usw.) gebunden oder müssen auf eine bestimmte CPU-Architektur kompiliert werden, was die Portabilität verringert. Bestehende Anweisungen arbeiten häufig mit bestimmten Registern und können andere Register nicht nutzen, wenn sie verfügbar sind. Legacy-Befehlssatz und alles.

Seth Robertson
quelle
1

Um hier ein paar interessante Informationen hinzuzufügen, werden Sie feststellen, dass Opcodes mit 8 Registern gleicher Größe die Konsistenz mit der hexadezimalen Notation aufrechterhalten können. Zum Beispiel ist der Befehl push axauf x86 Opcode 0x50 und geht für das letzte Register di auf 0x57. Dann pop axbeginnt der Befehl bei 0x58 und geht bis zu 0x5F pop di, um die erste Basis-16 zu vervollständigen. Die hexadezimale Konsistenz wird mit 8 Registern pro Größe beibehalten.


quelle
2
Auf x86 / 64 erweitern die REX-Befehlspräfixe die Registerindizes um mehr Bits.
Alexey Frunze