Ershov-Zahl
Ershov-Zahlen werden im Bereich der Informatik bei der Zuweisung von Registern benötigt. Sie sind nach dem russischen Informatiker Andrei Petrowitsch Jerschow benannt, der die Idee des Verfahrens zur Registerzuteilung publiziert hat. Das Verfahren geht auf die Hydrologen Robert Elmer Horton und Arthur Newell Strahler zurück, welche das Verfahren zur Berechnung der Flussordnungszahl eines Fließgewässerraumes entwickelten. Mit dem Verfahren kann nach Jerschow ebenfalls bestimmt werden, wie viele Register benötigt werden, um einen Ausdruck auszuwerten, und ermöglicht so die Auswertung großer Ausdrücke mit möglichst wenigen Registern.
Berechnung
Die Ershov-Zahl eines Binärbaumes berechnet sich wie folgt:
- Jedes Blatt hat den Wert (Konstanten oder Variablen).
- Jeder Knoten mit nur einem Unterknoten hat denselben Wert wie der Unterknoten (Unäre Operatoren).
- Für jeden anderen Knoten gilt
Herleitung der Berechnung
Das Verfahren spezifiziert eine Funktion der Form: , die einem gegebenen arithmetischen Ausdruck die Anzahl der zur Auswertung benötigten Register angibt. Hierfür ist eine Fallunterscheidung notwendig:
Variablen und Konstanten
Um eine Variable oder Konstante auszuwerten, wird genau ein Register benötigt. Demnach gilt:
Unäre Verknüpfung
Zur Auswertung eines unären Operators auf einen Ausdruck wird lediglich die Anzahl der Register benötigt, die braucht, denn das Ergebnisregister von kann einfach überschrieben werden.
Binäre Verknüpfung
Bei der Auswertung einer binären Verknüpfung zweier Ausdrücke der Form sind weiterhin drei Fälle zu unterscheiden:
Fall I: ershov(a1) < ershov(a2)
Angenommen, würde zuerst evaluiert, dann würde das Ergebnis in einem Register liegen und es wären noch weitere Register nötig, um zu evaluieren. Insgesamt wären also Register notwendig, um zu evaluieren.
Wird stattdessen zuerst evaluiert, so sind auch nur genau Register notwendig, um , da zwar das Ergebnis von in einem zusätzlichen Register steht, jedoch alle anderen Register wieder für die Evaluation von zur Verfügung stehen und daher, zusätzlich zu diesem einen Ergebnisregister, nur weitere Register notwendig sind. Da kleiner ist als , ist auch kleiner oder gleich , was offensichtlich auch kleiner ist als .
Demnach gilt:
Fall II: ershov(a1) = ershov(a2)
Es ist gleichgültig, welcher Ausdruck zuerst evaluiert wird, da beide Ausdrücke gleich viele Register benötigen. Beispielhaft kann daher zuerst der Ausdruck evaluiert werden, für den Register notwendig sind. Nach der Evaluation liegt das Ergebnis in einem Ergebnisregister und Register stehen nun für die Evaluation von zur Verfügung. Da die Evaluation des Ausdruckes ebenfalls beziehungsweise Register bedarf, werden, zusätzlich zum Register für das Ergebnis von , noch weitere Register, also insgesamt Register, für die Evaluation von benötigt.
Demnach gilt:
Fall III: ershov(a1) > ershov(a2)
Die Argumentation ist analog zu Fall I:
Verallgemeinerung auf Funktionsaufrufe
Ershov-Zahlen können auch auf Funktionsaufrufe ausgeweitet werden. Die Berechnungen für unäre und binäre Verknüpfungen können sogar als Spezialfälle von Funktionsaufrufen gesehen werden.
Wenn die Argumente eines Funktionsaufrufes auf den Stack geschrieben werden, ist es nicht notwendig, das Ergebnis auch noch in einem Register abzuspeichern. Aus derselben Argumentation wie in Fall I für binäre Ausdrücke folgt:
Es gibt aber auch Aufrufkonventionen, bei denen Argumente über bestimmte Register übergeben werden, was die Geschwindigkeit von Funktionen verbessern kann. Hier gilt wie für binäre Operatoren, welche ein Spezialfall von Funktionsaufrufen mit zwei Argumenten in Registern sind, dass die Argumente mit der höchsten Ershov-Zahl zuerst berechnet werden, damit für diese möglichst viele Register zur Verfügung stehen. Da alle Argumente schließlich in Register geschrieben werden müssen, ist die Ershov-Zahl des Funktionsaufrufes mindestens so groß wie die Anzahl der Argumente in Registern:
Wenn für mehr als ein Argument der Funktion die Ershov-Zahl gleich diesem Minimum ist, dann gilt, ebenfalls analog zu binären Operatoren, dass sich die Ershov-Zahl des Funktionsaufrufes für jedes weitere solche Argument um Eins erhöht:
Für gewöhnlich werden aber nur die ersten zum Beispiel 5 Argumente in Registern übergeben und die restlichen auf den Stack gelegt. Dann ist es sinnvoll, zuerst die letzten Argumente zu berechnen und auf den Stack zu legen, damit für die Berechnung der ersten Argumente alle Register verwendet werden können. Während die Argumente in Registern in beliebiger Reihenfolge berechnet werden können, müssen die Argumente auf dem Stack in der logischen Reihenfolge liegen.
Sethi-Ullman-Algorithmus
Der Sethi-Ullman-Algorithmus, welcher nach Ravi Sethi and Jeffrey Ullman benannt ist, wandelt einen abstrakten Syntaxbaum in die Befehle einer Registermaschine wie beispielsweise Maschinencode um.[1] Der Algorithmus ist im Prinzip derselbe wie zur Berechnung der Ershov-Zahlen, berücksichtigt aber einige Eigenschaften von Befehlen realer Registermaschinen. So erlaubt der Befehlssatz von x86-Prozessoren die direkte Verwendung von Konstanten und Werten im Speicher als Argumente. Es sind jedoch nicht alle Kombinationen erlaubt. Erlaubt sind:
add dword [ a ], 0x1337 ; Konstanten können direkt zu Variablen im RAM addiert werden.
add eax, 0x2342 ; Konstanten können direkt zu Registern addiert werden.
mov eax, dword [ a ]
add dword [ b ], eax ; Register können direkt zu Variablen addiert werden.
add eax, dword [ c ] ; Variablen können direkt zu Registern addiert werden.
Berechnung der Ershov-Zahlen
Die Berechnung der Ershov-Zahlen gestaltet sich je nach Befehlssatz anders. Im einfachsten Fall erlaubt der Befehlssatz nur Operationen mit Registern, womit Variablen und Konstanten erst in Register geladen werden müssen. In diesem Falle wird allen Blättern die Ershov-Zahl zugewiesen. Wenn der Befehlssatz auch Operationen mit Variablen und Konstanten erlaubt, muss unterschieden werden, ob der Befehlssatz nur Befehle in der Zwei-Operanden-Form oder auch in der Drei-Operanden-Form erlaubt.
In der Zwei-Operanden-Form wird das Ergebnis in dem Register des ersten Operanden abgelegt. Ein Beispiel sei die Addition auf x86-Prozessoren:
mov eax, a
add eax, b ; eax + b → eax. eax ist zugleich ein Operand als auch das Ergebnisregister.
In diesem Falle kann einem rechten Blatt im abstrakten Syntaxbaum der Wert zugewiesen werden, einem linken Blatt der Wert . Im Beispiel ist b
der rechte Operand, er muss nicht erst in ein Register geladen werden, und a
der linke Operand. Dieser muss erst in ein Register eax
geladen werden, damit berechnet und in eax
abgelegt werden kann.
In der Drei-Operanden-Form ist der erste Operand das Ergebnisregister:
add r1, a, b ; a + b → r1.
In diesem Falle wird jedem Blatt im abstrakten Syntaxbaum der Wert zugewiesen.
Diese Darstellung der Berechnung ist jedoch idealisiert: Tatsächliche Prozessoren erlauben beispielsweise
- nur Konstanten als direkte Operanden, aber keine Variablen oder umgekehrt.
- Konstanten als Operanden nur dann, wenn sie in einem begrenzten Wertebereich, beispielsweise in , liegen. Ansonsten müssen Konstanten erst in ein Register geladen werden, womit sich die Zahl der notwendigen Register und damit die Ershov-Zahl erhöht.
- für einige Befehle die Drei-Operanden-Form, für andere jedoch nur die Zwei-Operanden-Form.
- die Drei-Operanden-Form, aber der zweite Operand darf keine Konstante und/oder keine Variable sein. In diesem Fall verhält sich die Drei-Operanden-Form wie die Zwei-Operanden-Form.
Erzeugung des Codes
Anschließend wird der Baum in Nebenreihenfolge traversiert, wobei erst der Unterbaum mit der höchsten Ershov-Zahl ausgewertet wird. Würde stattdessen der Unterbaum mit der niedrigsten Ershov-Zahl ausgewertet, so muss das Ergebnis in einem Register gespeichert werden, welches damit nicht mehr für die Berechnung des anderen Unterbaumes zur Verfügung steht. Bei einer begrenzten Zahl an Registern tritt unweigerlich die Situation auf, dass für die Berechnung eines Baumes nicht genügend Register zur Verfügung stehen. Die Zahl der notwendigen Register zur Berechnung eines Baumes ist nur dann größer als die Zahl der notwendigen Register der Unterbäume, wenn beide Unterbäume dieselbe Ershov-Zahl haben beziehungsweise wenn beide Unterbäume gleich viele Register brauchen. Nur dann wird die Grenze der verfügbaren Register überschritten und das Zwischenergebnis eines Unterbaumes muss in einem Speicher, beispielsweise auf dem Stack oder in einer Red Zone, abgelegt werden, indem
- erst der rechte Unterbaum ausgewertet,
- ein Befehl zum Speichern des Zwischenergebnisses eingefügt,
- dann der linke Unterbaum ausgewertet,
- ein Befehl zum Laden des Zwischenergebnisses eingefügt und
- schließlich der Befehl für die Verknüpfung der beiden Unterbäume eingefügt wird.
Die Reihenfolge ist eigentlich irrelevant, aber es kann von Vorteil sein, erst den rechten und dann den linken Unterbaum auszuwerten, wenn die Operation einen Wert im Speicher als rechten Operanden erlaubt. Dann können die letzten beiden Schritte zusammengefasst werden.
Referenzen
- ↑ Sethi Ravi, Jeffrey D. Ullman: The Generation of Optimal Code for Arithmetic Expressions. In: Journal of the Association for Computing Machinery. Band 17, Nr. 4, 1970, S. 715–728, doi:10.1145/321607.321620.