Gibt es einen Prozessor, der arithmetische Operationen auf einem Stapel und nicht auf Registern ausführt? Um die Leistung aufrechtzuerhalten, sollte dieser Prozessor natürlich den obersten Block eines Stapels in demselben Speichertyp zwischenspeichern, der für Register verwendet wird.
Ich habe in einem Artikel gelesen (David R. Ditzel, HR McLellan. Register Allocation for Free: The C Machine Stack Cache. ), dass ein Cache zweimal langsamer ist als Register, weil:
Das Papier ist alt. Vielleicht sind Verbesserungen des Prozessordesigns aufgetaucht, die den Stack-Cache rentabel machen? Ich glaube, dass es die Komplexität von Compilern reduzieren und das Kopieren zwischen Registern und dem Rest des Speichers optimieren wird.
Aktualisierung 18.10.2012. Da dieses Konzept bekannt war (mir nicht), ändere ich die Frage in „… moderne Prozessoren?“
Aktualisierung 18.10.2012. Ich muss ausdrücklich sagen, dass ich nicht von einer „Null-Adress-Maschine“ spreche. Caching und „Nulladresse“ sind orthogonal. Mein hypothetischer Prozessor kann sogar eine 5-stellige Addition wie „r3 := r0+r2+r11+r5+r8“ haben. „rn“ bedeutet die Speicherzelle bei sp+n, wobei sp ein Stapelzeiger ist. sp ändert sich vor und nach einem Codeblock. Ein sehr ungewöhnliches Programm verändert sp bei jeder arithmetischen Operation.
Ja, die gesamte Linie der Mainframe-Computer von Burroughs , beginnend 1961 mit dem B5000, verwendete eine Stapelarchitektur.
In dieser Architektur ist die Verwaltung des Datenflusses zum und vom Stack eigentlich kein allzu großer Engpass für die Leistung. Ein größeres Problem ist die Tatsache, dass eine "Nulladressen"-Maschine viel mehr Anweisungen benötigt, um eine bestimmte Aufgabe zu erledigen, als eine Maschine mit einer, zwei oder drei Adressen. Die Befehlsdecodierung und die Ausführungspipeline werden zum primären Engpass.
Als ich dort in den frühen 1980er Jahren arbeitete, gab es Bestrebungen, eine CPU zu bauen, die relativ große Sequenzen von Anweisungen mit Nulladresse vorab abrufen und sie im laufenden Betrieb in Operationen mit drei Adressen übersetzen konnte, die in die Ausführungspipeline eingespeist wurden. (Denken Sie an einen in Hardware implementierten Java-JIT-Compiler.) Es wurde ziemlich komplex, insbesondere für die damals verfügbaren Implementierungstechnologien, und ich weiß nicht, ob diese Strategie letztendlich erfolgreich war.
Falls Sie sich fragen, bezieht sich die Terminologie "N-Adresse" auf die Anzahl der Operanden, die in einer einzelnen Anweisung angegeben werden können. Alle Operationen auf einer Stapelmaschine befinden sich implizit an den obersten ein oder zwei Stellen auf dem Stapel, daher gibt es in den Anweisungen null Operanden. Eine Maschine, die einen Akkumulator hat, der für alle Operationen in Verbindung mit einem anderen Register oder Speicherplatz verwendet wird, ist eine Maschine mit einer Adresse. Eine Maschine mit zwei Adressen kann einen beliebigen Quell- und Zieloperanden in einer Anweisung spezifizieren, und eine Maschine mit drei Adressen kann zwei Quelloperanden spezifizieren und das Ergebnis in ein unabhängiges Ziel setzen.
movf src / addwf dest,f
ist schneller als ldr r0,[src+r13] / ldr r1,[dest+r13] / add r0,r0,r1 / str [src+r13]
(und führt seine Zielaktualisierung atomar durch). Schade, dass das Hinzufügen einer Zahl zu einer anderen, während der Wert von W
für etwas anderes benötigt wird, vier Zyklen kostet (einen zum Speichern von W, einen zum Laden eines Operanden, einen zum Ausführen der Operation und einen zum Wiederherstellen von W). So etwas usefw
könnte das auf zwei reduzieren.Ich erinnere mich, dass ich vor etwa 17 Jahren einen ähnlichen Artikel (vielleicht denselben) gelesen habe. Ein solcher Ansatz könnte gut sein, wenn man einen Prozessor entwickeln würde, um schnell eine Anweisung nach der anderen auszuführen. Leider funktioniert es nicht gut mit der Out-of-Order-Befehlsplanung. Wenn man Code hat wie:
ldr r1,[r0] ... irgendwas machen, ohne r1, r2 oder [r2] einzubeziehen str r1,[r2]
Einem Befehlsplaner steht es frei, diese beiden Befehle nach Belieben zu verschieben. Während es für den Befehlsplaner schwierig sein kann zu wissen, ob ein Schreibvorgang in einen Speicherort ein Schreibvorgang in [r2] sein könnte, erfordern viele kompilierte Sprachen, dass Programmierer angeben, welche Dinge Alias sein können oder nicht.
Im Gegensatz dazu waren die Anweisungen eher wie folgt:
mov.l [r0],[--sp] ; Schieben Sie [r0] auf den Stapel ... etwas tun, was sp betrifft mov.l [sp++],[r2] ; Pop [r2] aus dem Stapel
es wäre für eine Out-of-Order-Ausführungsmaschine viel schwieriger zu bestimmen, ob der Quelloperand für die letztere Anweisung immer derselbe wie der Zieloperand der ersteren wäre und ob irgendwelche dazwischenliegenden Anweisungen ihn beeinflussen könnten.
In der Vergangenheit habe ich mit dem Saab Ericsson Space Thor gearbeitet, einem Mikroprozessor für Weltraumanwendungen. Es funktionierte, hatte aber einige schwerwiegende Nachteile. Nur eins: Die Anweisungspipeline wurde offengelegt: Die Anweisung, die ein Wort aus dem Speicher geladen hat, wurde vor 2 Anweisungen als Adresse für die Spitze des Stapels verwendet . Ich habe eine schnelle Speicherkopierroutine dafür geschrieben, aber Saab sagte, sie könne nicht verwendet werden, weil Interrupts Probleme verursachen würden ...
Es gab dedizierte Forth-Prozessoren, die früher als Boot-Prozessor für Sun/Sparc-Maschinen verwendet wurden, deren dedizierte Architektur der Sprache zugeordnet war. Aber nicht allgemein verfügbar.
Der x86 ist fast einer davon :-) (und der x87 fp-Teil noch näher)
In modernen Systemen ist Stack jedoch schrecklich, da es über Kerne oder sogar NUMA-Knoten hinweg Alias sein kann, sodass viele langsame Signalübertragungen über große Entfernungen beteiligt sein können. Oder zumindest mehr Verriegelungen als bei einer Registerdatei und einer Registerumbenennung.
Bedenken Sie, dass nicht einmal CPUs, sondern andere Geräte Daten in Ihren Stack DMA können – denken Sie an Lesepuffer!
Superkatze
Superkatze
David Tweed
beroal
beroal
beroal