Hallo zusammen,
in diversen Büchern habe ich gelesen, dass RISC-Prozessoren eine
LOAD-STORE-Architektur zugrunde liegt. Das bedeutet nach meinem
Verständnis, dass - wenn im Assemblerprogramm in einer arithmetischen
Operation eine Variable verwendet wird, die sich noch im Hauptspeicher
befindet - auf diese nicht direkt zugegriffen werden kann, sondern, dass
sie erst mit dem LOAD-Befehl in eines der internen Register geschoben
werden muss, wo sie dann mit schnellen arithmetischen
Register-Register-Operationen weiterverarbeitet werden kann.
Ich frage mich, ob ich das richtig verstanden habe und ob daraus ein
Geschwindigkeitsvorteil resultiert.
Über Antworten würde ich mich freuen.
Vielen Dank.
Torben schrieb:> Ich frage mich, ob ich das richtig verstanden habe und ob daraus ein> Geschwindigkeitsvorteil resultiert.
Ein Geschwindigkeitsvorteil gegenüber was?
CISC/RISC ist heute längst obsolet, weil auch CPUs mit CISC-Befehlssatz
wie x86 intern RISC-CPUs sind, die die externen Befehle in Mikro-Ops
aufspalten und diese dann ausführen.
Umgedreht hat ARM bei Cortex-M auch mit Thumb-2 einen Mischbefehlssatz
mit variabler Länge, was klassischem RISC widerspräche. Aber bei rein
32-bit ist die Codedichte mau (zuviel Bloat) und bei rein 16 bit die
Performance (oftmals zwei Befehle statt einem).
Was aber für einen RISC-Befehlssatz spricht, ist die Tatsache, daß es
weitaus leichter ist, ein optimierendes Compiler-Backend dafür zu
schreiben. Das ist insbesondere für einen neuen Befehlssatz wie RISC-V
wichtig, denn ohne Toolchain ist eine CPU wertlos.
Zwar gibt es für x86 mt komplexem Befehlssatz auch gute
Compiler-Backends, aber die sind historisch gewachsen. Für eine neue CPU
hätte man jetzt nicht die Zeit, da noch Mann-Jahrhunderte reinzustecken.
Üblicherweise sind tatsächlich bei RISC, durch die einfacheren
Instruktionen, höhere Taktraten erreichbar. Auch sind diese
Architekturen als Zielarchitekturen für Compiler einfacher zu
handhaben¹. Andererseits braucht man viele Register, um zu häufigen
Speicherzugriff zu vermeiden.
Das führt dazu, dass RISC üblicherweise schnell ist, aber Nachteile bei
der Codegröße hat.
Allerdings ist der Übergang fließend, und auch Prozessoren, die auf
Ebene der ISA CISC sind, sind oft auf Mikroarchitekturebene RISC. Bei
einer RISC-ISA wird die Übersetzung zwischen ISA und Mikroarchitektur
allerdings viel einfacher (und schneller), da direkt in Hardware
umgesetzt, nicht durch ein Mikroprogramm.
Philipp
¹ Ein Beispiel für einen typischen CISC-Befehl wäre cpir beim Z80.
Diesen vom Compiler verwenden zu lassen, würde eine sehr aufwendige
Analyse des Programms erfordern. So wird dieser Befehl z.B. von SDCC
bisher nicht verwendet (außer für strlen aus der Standardbibliothek).
Philipp Klaus K. schrieb:> Andererseits braucht man viele Register, um zu häufigen> Speicherzugriff zu vermeiden.
Hier wird es für mich interessant. Warum vermeidet man häufige
Speicherzugriffe? Nach der RISC-Philosophie muss man doch die Variablen
vorher aus dem Speicher holen, um sie in interne Register abzulegen.
Dabei sind doch genauso viele Speicherzugriffe notwendig, wie bei einer
Register-Speicher-Architektur.
Torben schrieb:> Philipp Klaus K. schrieb:>> Andererseits braucht man viele Register, um zu häufigen>> Speicherzugriff zu vermeiden.>> Hier wird es für mich interessant. Warum vermeidet man häufige> Speicherzugriffe? Nach der RISC-Philosophie muss man doch die Variablen> vorher aus dem Speicher holen, um sie in interne Register abzulegen.> Dabei sind doch genauso viele Speicherzugriffe notwendig, wie bei einer> Register-Speicher-Architektur.
Betrachten wir nur lokale Variablen, da diese eh am häufigsten vorkommen
und am häufigsten verwendet werden (neben den explizit vom Programmierer
deklarierten gibt es noch weitere, temporäre für Zwischenergebnisse).
Diese müssen ja irgendwo gespeichert werden. Üblicherweise auf dem Stack
(erfordert Speicherzugriff) oder in Registern.
Je mehr Variablen in Register passen, desto weniger Speicherzugriffe
sind also nötig.
Bei RISC hat man einen orthogonalen Befehlssatz, typischerweise so, dass
die meisten Befehle drei Operanden (ein Ziel, zwei Quellen) haben. Alle
davon sind Register, und der Befehl ist gleichschnell, egal auf welche
Register er zugreift. Damit lassen sich alle Register flexibel verwenden
(auf einfache Art für den Compiler, z.B. durch einen graphfärbenden
Registerallokator nach Chaitin - es kommt ja nur darauf an, ob eine
Variable in einem Register liegt, nicht darauf, in welchem).
Torben schrieb:> Ich frage mich, ob ich das richtig verstanden habe und ob daraus ein> Geschwindigkeitsvorteil resultiert.
Also diese Frage noch frisch war, d.h. in der 90ern, ergab sich das aus
der Möglichkeit eines Compilers, die Wartezeit von Start der
Ladeoperation bis Verwendung des Registers mit anderen Befehlen zu
füllen.
Mittlerweile machen das die Highend-Prozessoren durch interne
Befehlsumordnung selbst. Prozessoren wie Cortex A55 sind davon noch
betroffen.
Torben schrieb:> Hier wird es für mich interessant. Warum vermeidet man häufige> Speicherzugriffe?
Aufgrund der Zugriffszeit:
Register: 1 Takt,
L1-Cache: 4-5 Takte,
DRAM: Grössenordnung 200 Takte.
> Nach der RISC-Philosophie muss man doch die Variablen> vorher aus dem Speicher holen, um sie in interne Register abzulegen.
Vorausgesetzt sie sind nicht schon im Register. im Idealfall bleiben
lokale Variablen in Registern, mindestens zeitweilig, sehen überhaupt
keinen Speicher. Weshalb RISCs meist eine deutlich grössere Anzahl
Register haben.
Philipp Klaus K. schrieb:> Betrachten wir nur lokale Variablen, da diese eh am häufigsten vorkommen> und am häufigsten verwendet werden (neben den explizit vom Programmierer> deklarierten gibt es noch weitere, temporäre für Zwischenergebnisse).> Diese müssen ja irgendwo gespeichert werden. Üblicherweise auf dem Stack> (erfordert Speicherzugriff) oder in Registern.> Je mehr Variablen in Register passen, desto weniger Speicherzugriffe> sind also nötig.
Danke für die gute Erklärung. Das kann ich nachvollziehen und habe es
jetzt verstanden. Dürfte ich noch eine weitere Frage dazu stellen?
Müssen die vom Compiler erzeugten Maschinenbefehle, die ja nach meiner
bescheidenen Vorstellung irgendwo im Hauptspeicher liegen müssten,
nacheinander vom Hauptspeicher in das Befehlsregister des Prozessors
übertragen werden und und sind dafür nicht auch Speicherzugriffe
notwendig?
Torben schrieb:> Müssen die vom Compiler erzeugten Maschinenbefehle, die ja nach meiner> bescheidenen Vorstellung irgendwo im Hauptspeicher liegen müssten,> nacheinander vom Hauptspeicher in das Befehlsregister des Prozessors> übertragen werden
Ja, aber nicht nacheinander.
> und und sind dafür nicht auch Speicherzugriffe notwendig?
Der Trick ist, daß die mit einer Art Vorauslesen übertragen werden,
dafür ist die Cache-Pipeline da. Bei sequentiellen Befehlen ist das sehr
leicht möglich, während bei bedingten Sprüngen eine Sprungvorhersage zum
Einsatz kommt.
Das Vorauslesen ist deswegen schneller, weil man dabei nicht bloß einen
Befehl überträgt, sondern gleich einen ganzen Batzen. Die Latenz ist
dabei ja dieselbe, verteilt sich dadurch aber auf mehr Befehle.
Man kann bei einem bedingten Sprung sogar beide Fälle der Verzweigung
spekulativ laden und ausführen, und dann einen der beiden wieder
verwerfen. Allerdings kann das ungewollte Seiteneffekte haben, die uns
die ganzen Sicherheitslücken bei Intels x86ern beschert haben, in
minderem Grad aber auch bei AMD und ARM.
A. K. schrieb:> Aufgrund der Zugriffszeit:> Register: 1 Takt,> L1-Cache: 4-5 Takte,> DRAM: Grössenordnung 200 Takte.
Was ist mit Takt gemeint? Etwa die Schrittgeschwindigkeit, also der
minimale zeitliche Abstand zwischen zwei Spannungspegeln?
Torben schrieb:> in diversen Büchern habe ich gelesen, dass RISC-Prozessoren eine> LOAD-STORE-Architektur zugrunde liegt.
Das mag zwar sein, ist aber nicht bezeichnend für RISC.
RISC heißt im Klartext lediglich, daß die Anzahl der veschiedenen
Maschinenbefehle, die die CPU kennt, eben _R_eduziert ist. Also weniger
verschiedene Befehle als bei CISC. Damit so eine CPU dann nicht doof
dasteht im Vergleich zu CISC, müssen die verbleibenden Befehle eben
anders organisiert und vielseitiger sein als bei CISC. Und das erfordert
dann einen darauf abgestimmten Registersatz in der CPU.
Aber Load-Store ist eine ganz andere Sache. Das bedeutet lediglich, daß
das Verändern von Variablen im Daten-Speicher etwa so geht:
gewünschte Operation: ottokar = ottokar + 1;
1. Lade ottokar aus Speicher in CPU-Register R
2. addiere 1 zu R
3. Speichere R zurück nach ottokar
Das hat nix mit RISC zu tun. Schau dir mal als Gegenbeispiel die
Architektur der kleinen PIC's an. Die sind in ihrer Art auch RISC, haben
aber keine Load-Store Architektur, sondern erledigen das o.g.
Verändern von Variablen direkt im Datenspeicher. Bei denen geht obiges
Beispiel dann so:
1. INCF ottokar,F
und fertig.
So etwas setzt eine Harvard-Architektur voraus, wenn es effektiv
stattfinden soll. Denn damit können das Befehls-Holen und
Ergebnis-Speichern parallel zur gleichen Zeit stattfinden.
Und nochwas zum Thema Register in der CPU: Es ist klar, daß man damit
umso mehr Variablen in der CPU halten kann, je mehr Register man dafür
hat.
Aber die allerwenigsten Funktionen kommen ohne Aufruf von weiteren
Funktionen aus, weswegen bei Funktionsaufrufen auch mehr Register auf
dem Stack gerettet werden müssen. Sowas ergibt dann einen doppelten
Zeitbedarf (rauf auf den Stack und hinterher wieder runter). Viele
Register in der CPU sind also nicht per se auch besser als wenige
Register, sondern es ist immer eine Art Grauzone. Deshalb ist der
folgende Satz nicht wirklich richtig:
Philipp Klaus K. schrieb:> Je mehr Variablen in Register passen, desto weniger Speicherzugriffe> sind also nötig.
Siehe oben. Es kommt also auf die konkrete Situation und die
Konstruktion der CPU drauf an.
Torben schrieb:> Müssen die vom Compiler erzeugten Maschinenbefehle, die ja nach meiner> bescheidenen Vorstellung irgendwo im Hauptspeicher liegen müssten,> nacheinander vom Hauptspeicher in das Befehlsregister des Prozessors> übertragen werden und und sind dafür nicht auch Speicherzugriffe> notwendig?
Ob Compiler oder Assembler, in jedem Fall müssen die abzuarbeitenden
Befehle aus dem Befehlsspeicher geholt werden (oder aus dem Cache, wenn
dort vorhanden).
Bei v.Neumann ist das zugleich auch der Datenspeicher. Siehe
Rechner-Architekturen. Und ja, auch dafür braucht es Speicherzugriffe.
Und je schmaler ein Befehlssatz ist, desteo weniger Bits braucht man
dafür. Wenn ein Speicher 32 Bit breit ist, dann würde da bei einem
Zugriff 1 Befehl á 32 Bit oder eben 2 Befehle á 16 Bit mit einem Rutsch
zu holen sein.
W.S.
Torben schrieb:> Ich frage mich, ob ich das richtig verstanden habe und ob daraus ein> Geschwindigkeitsvorteil resultiert.
Richtig verstanden, wuerde sagen, ja.
Voellig unabhaengig davon, ob jetzt RISC oder nicht: Aufeinanderfolgende
LOAD/STORE sind nicht effektiv und sind natuerlich, atomar gesehen (als
einzelner Befehl) langsamer als eine Register-Operation.
Der Geschwindigkeitsvorteil einer CPU kommt daher alleine durch's
Pipelining und natuerlich durch die maximale Taktfrequenz zustande.
Gibt hier als Bsp. etwas ASCII-Art zu einem LOAD vom internen memory:
https://section5.ch/index.php/2019/09/27/risc-v-experiments/
Im Fall, wo das RAM einen Wartezyklus braucht (READ_WAIT), bis die Daten
nach Anlegen der Adresse valide sind, ist es demnach unguenstig, sie
gleich weiterzuverarbeiten, besser ist es, wenn der Compiler die Befehle
so anordnet, dass ein LOAD frueh stattfindet und waehrend des Totzyklus
des Memory eine andere Operation ausgefuehrt werden kann.
Das Optimum - fuer eine einfache Pipeline - ist, ohne
Instruction-Reordering (zur Laufzeit) einen Taktzyklus pro Operation zu
brauchen. Kann man natuerlich mit superskalaren Designs noch weiter auf
die Spitze treiben.
Nop schrieb:> Zwar gibt es für x86 mt komplexem Befehlssatz auch gute> Compiler-Backends, aber die sind historisch gewachsen. Für eine neue CPU> hätte man jetzt nicht die Zeit, da noch Mann-Jahrhunderte reinzustecken.
Liegt auch bei GCC nicht gerade bei Mann-Jahrhunderten, mein letztes
Projekt hat ca. ein Mannjahr (plus ca. 3 sinnlos outgesourcte
Fernost-Mannjahre, d.h. fuer die Tonne) gekostet. Mit LLVM kocht sich's
noch weiter runter.
W.S. schrieb:> Das mag zwar sein, ist aber nicht bezeichnend für RISC.Martin S. schrieb:> Der Geschwindigkeitsvorteil einer CPU kommt daher alleine durch's> Pipelining und natuerlich durch die maximale Taktfrequenz zustande.
Kann ich dann folgenden Schluss ziehen?:
Prozessoren, die eher an der RISC-Philosophie angelegt sind, verfügen
über kurze elementare Befehle. Infolgedessen ist das Maschinenprogramm
länger, das z. B. der Compiler generieren würde. Daraus ergeben sich
dann auch viele Speicherzugriffe, weil jeder einzelne Assemblerbefehl
aus dem Hauptspeicher geholt werden muss. Diese Speicherzugriffe wirken
sich aber schlecht auf die Performance aus. Geheilt wird dieses Problem
durch Pipelining.
W.S. schrieb:> gewünschte Operation: ottokar = ottokar + 1;> 1. Lade ottokar aus Speicher in CPU-Register R> 2. addiere 1 zu R> 3. Speichere R zurück nach ottokarW.S. schrieb:> Architektur der kleinen PIC's an. ...> 1. INCF ottokar,F> und fertig.
Solange es kein Ram gibt, daß selbstätig rechnen kann, muß auch der PIC
den Wert aus dem Speicher holen, verändern, und wieder zurückschreiben.
Oliver
Torben schrieb:> Was ist mit Takt gemeint? Etwa die Schrittgeschwindigkeit, also der> minimale zeitliche Abstand zwischen zwei Spannungspegeln?
Wir reden aber schon von digitalen Prozessoren, nicht von
Analogrechnern?
Torben schrieb:> W.S. schrieb:>> Das mag zwar sein, ist aber nicht bezeichnend für RISC.>> Martin S. schrieb:>> Der Geschwindigkeitsvorteil einer CPU kommt daher alleine durch's>> Pipelining und natuerlich durch die maximale Taktfrequenz zustande.>> Kann ich dann folgenden Schluss ziehen?:>> Prozessoren, die eher an der RISC-Philosophie angelegt sind, verfügen> über kurze elementare Befehle. Infolgedessen ist das Maschinenprogramm> länger, das z. B. der Compiler generieren würde. Daraus ergeben sich> dann auch viele Speicherzugriffe, weil jeder einzelne Assemblerbefehl> aus dem Hauptspeicher geholt werden muss. Diese Speicherzugriffe wirken> sich aber schlecht auf die Performance aus. Geheilt wird dieses Problem> durch Pipelining.
Andererseits sind bei RISC alle Befehle gleichgroß, was das Laden
selbiger vereinfacht.
Bei CISC mit üblicherweise unterschiedlich langen Befehlen kann selbst
das Laden eines einzigen Befehls viele Speicherzugriffe erfordern.
Da der kritische Pfad verkürzt wird, besteht natürlich ein
Geschwindigkeitsvorteil gegenüber einer register memory architecture in
der Gestalt einer höheren Taktrate. Transfers über den externen Bus sind
wegen der Parasitären Kapazitäten am Interface relativ langsam.
Diese Frage berührt das Prinzip der Entkopplung des Rechenwerkes vom
Speicher. Insofern legt LoadStore auch die Grundlage für weitere
Optimierungen wie Caches und Memory-Bursts.
W.S. schrieb:> So etwas setzt eine Harvard-Architektur voraus, wenn es effektiv> stattfinden soll. Denn damit können das Befehls-Holen und> Ergebnis-Speichern parallel zur gleichen Zeit stattfinden.
Hauptsache mal wieder was geschrieben?
Das hat nix mit CISC/RISC und Load-Store zu tun, sondern mit Pipelining.
Ein RISC/CISC Prozessor ist auch als Multicycle baubar, aber dann eben
lahm.
Torben schrieb:> Kann ich dann folgenden Schluss ziehen?:>> Prozessoren, die eher an der RISC-Philosophie angelegt sind, verfügen> über kurze elementare Befehle. Infolgedessen ist das Maschinenprogramm> länger, das z. B. der Compiler generieren würde. Daraus ergeben sich> dann auch viele Speicherzugriffe, weil jeder einzelne Assemblerbefehl> aus dem Hauptspeicher geholt werden muss. Diese Speicherzugriffe wirken> sich aber schlecht auf die Performance aus. Geheilt wird dieses Problem> durch Pipelining.
Nee, das wuerde ich so nicht sagen. Was du ansprichst, ist die
klassische FETCH-Stage. Das 'Problem' hat jede Architektur, die Stage
kann voellig unterschiedlich ausfallen (und u.U auch Wartezyklen
unterliegen). Sobald aber eine Programmsequenz im L1-RAM (am naechsten
am CPU-Kern) vorliegt, ist das nahezu 'gratis' fuer die Performance. Im
Silizium ist das eine Frage der Flaeche der Logikelemente (und wo man
seinen Flaschenhals haben will). Oder, was man fuer Memory-Typen/Busse
zur Verfuegung hat (Stichworte Harvard vs. von Neumann).
Bei manchen Architekturen gibt's eine PREFETCH-stage, die z.b. gleich
mehrere Instructionen holt, ev. sogar von unterschiedlichen Adressen
(moeglicher Branch). Dann liegen die Instruktionen in (nicht
exportierten)
Registern.
Die Pipeline heilt das Problem nur, wenn sie keine Pause einlegen muss
(wenn besagter LOAD-Hazard eintritt: will X verrechnen, aber X noch
nicht da).
Ansonsten zu empfehlen: Die CS152-Kurse und viel PDF-Material zu
MIPS-Architekturen von berkeley.edu.
Es gibt auch RISC-Architekturen, die keine Registerbank besitzen
(Stack-Maschinen) und bessere Code-Dichten aufweisen als i86, oder ARM
Thumb.
Schlussendlich sind fuer die Performance die genannten Faktoren
(Wartezyklen, Taktfrequenz) und schliesslich dann die Anzahl der Befehle
fuer eine gewisse Operation relevant.
In der Praxis ist das mit RISC vs. CISC immer etwas verwirrend, die
klassischen CISC haben oft eine starke Varianz in den Zyklen pro Befehl
und weniger Pipelining, die klassischen RISC eher wenige Befehle und
~1-2 Z/B, dann gibt es aber auch neuere DSP-Hybriden, die man als CISC
sehen wuerde, aber genau 1 Z/B benoetigen. Deswegen ist die Unterteilung
sehr schwammig.
Weniger Zyklenvarianz macht's einem Compiler sehr viel einfacher, zu
optimieren, deswegen machen die RISC-Instruction-Sets fuer den
Compilerbau schlicht weniger Arbeit.
Philipp Klaus K. schrieb:> Andererseits sind bei RISC alle Befehle gleichgroß, was das Laden> selbiger vereinfacht.
Das gilt laengst auch nicht mehr, siehe auch RISC-V 'compressed' oder
Instruction-Sets wie von Xtensa.
Torben schrieb:> in diversen Büchern habe ich gelesen, dass RISC-Prozessoren eine> LOAD-STORE-Architektur zugrunde liegt.
Papier ist geduldig. Und zeitlos. Schau mal auf das Datum der
Veröffentlichung.
> Das bedeutet nach meinem> Verständnis, dass - wenn im Assemblerprogramm in einer arithmetischen> Operation eine Variable verwendet wird, die sich noch im Hauptspeicher> befindet - auf diese nicht direkt zugegriffen werden kann, sondern, dass> sie erst mit dem LOAD-Befehl in eines der internen Register geschoben> werden muss, wo sie dann mit schnellen arithmetischen> Register-Register-Operationen weiterverarbeitet werden kann.
Du verwendest da unangebrachte Adjektive wie "arithmetisch" oder
"schnell". Tatsächlich bedeutet LOAD-STORE-Architektur nur, daß die
einzigen Befehle, die auf externen Speicher (lies: alles was kein
Register ist) zugreifen können, welche sind die Daten in Register laden
(LOAD) bzw. Daten aus Registern sichern (STORE). Mehr nicht.
> Ich frage mich, ob ich das richtig verstanden habe und ob daraus ein> Geschwindigkeitsvorteil resultiert.
"LOAD-STORE-Architektur" ist ein beschreibender Begriff. Noch dazu
einer, der eher wenig mit der alten RISC vs. CISC Debatte zu tun hat.
Und die Frage nach einem Geschwindigkeitsvorteil ist auf dieser Ebene
vollkommen fehl am Platz.
Allerdings erscheint es mir offensichtlich, daß man einen Befehlssatz
mit kürzeren Befehlen hinbekommt, wenn man sich bei der Mehrheit der
Befehle auf Addressierungsarten beschränkt, die nur interne Register
umfassen. Weil es davon sehr viel weniger gibt, als Speicherplätze im
externen Adreßbereich. Weil deswegen viel weniger Adreßbits im Befehl
gebraucht werden.
Und auf der Ausführungsseite ist auch klar, daß Zugriffe auf Register
schneller sind, als Zugriffe auf RAM. Trotzdem ist genauso klar, daß nur
die wenigsten Programme mit den paar Hundert Bits auskommen, die in
Registern gehalten werden können. Es wird also in jedem Fall Zugriffe
auf externen Speicher geben müssen. Ob unter diesen Umständen eine CPU
mit strikter LOAD-STORE-Architektur besser abschneidet als eine, die
sagen wir mal indirekte Adressierung für alle Befehle anbietet, wäre
noch zu untersuchen. Es wird auf jeden Fall auch vom Programm abhängen.