Graph Rewrite Systems for Program Optimization Uwe Aßmann Universität Karlsruhe Erschienen in ACM Transactions on Programming Languages and Systems, Vol. 22, No.4, Juli 2000, Seiten 583-637 Zusammengefasst von Tobias Gutzmann im Rahmen des Seminars Zwischensprachen und Codegenerierung am Institut für Innovatives Rechnen und Programmstrukturen (IPD) Universität Karlsruhe Karlsruhe, 18. Mai 2005 1 Motivation Das Implementieren einer Optimierung ist eine zeitaufwendige und fehleranfällige Aufgabe. Weiterhin sind handgeschriebene Optimierungsroutinen auf eine gegebene Zwischensprache zugeschnitten, müssen folglich für verschiedene Übersetzer individuell implementiert werden. Uwe Aßmann stellt ein Verfahren auf Basis von Graphersetzung vor, das den Umgang mit den angesprochenen Problemen vereinfacht. Ziel ist es, die Entwicklungszeit zu verkürzen, die Validierung zu vereinfachen, und die Wartbarkeit zu verbessern. Dabei ist sein Ansatz nicht an eine bestimmte Zwischensprache gebunden und somit portabel. Mit Optimix steht eine Implementierung von Aßmanns Verfahren zur Verfügung. 2 Graphersetzung Graphbasierte Zwischendarstellungen werden zwar schon seit Jahren in modernen Übersetzern eingesetzt, Programmtransformationen auf ihnen werden aber noch in Hochsprachen implementiert, eine Abstraktion erfolgt nicht. Mit Graphersetzungssystemen werden Optimierungen von der Zwischendarstellung abstrahiert beschrieben. Aßmanns Ansatz arbeitet mit Σ-Graphen: Definition Sei Σ eine endliche Menge von Konstanten, den Labels. Σ ist die disjunkte Vereinigung zweier Mengen ΣN ∪ ΣE , den Knoten- und Kanten-labels. Dann besteht ein (relationaler) Σ-Graph aus den folgenden Komponenten: 1. N ist eine endliche Menge von Knoten 2. Knoten werden mit Labels aus ΣN durch eine Funktion nlab : N → ΣN markiert. Ein Knoten mit Label l wird l-Knoten genannt. Die Menge Nl = {n ∈ N |nlab(n) = l} aller l-Knoten bildet einen Knoten-Wertebereich. 3. Die Kanten E sind eine Familie binärer Relationen E = {El }l∈ΣE , El ⊆ Nl1 ×Nl2 , l ∈ ΣE , l1 , l2 ∈ ΣN . Eine Kante mit Label l wird l-Kante genannt. Weiterhin sind durch diese Definition implizit mehrere Funktion gegeben, wie z.B. die Funktion elab, die einer Kante ihr zugehöriges Label zuordnet, sowie die Funktionen source und target, die den (gerichteten) Kanten ihre zugehörigen Knoten zuordnen. Zwei Knoten dürfen durch mehrere Kanten verbunden sein, diese müssen jedoch paarweise verschiedene Labels haben. Multigraphen sind also nicht erlaubt. Es ist möglich, das Modell so zu erweitern, dass Knoten Attribute zugewiesen werden können. Dies entspricht im Allgemeinen einer Erweiterung des Label-Raumes und wird hier der Einfachheit halber ausgelassen.1 Graphersetzungsregel Eine Graphersetzungsregel r = (L, R) besteht aus einer linken Seite L, die einen zu überdeckenden Teilgraphen beschreibt, und einer rechten Seite R, die den einzusetzenden Ersatzgraphen beschreibt. Um auf die Abwesenheit von Teilgraphen prüfen zu können, dürfen in L negierte Kanten auftreten.2 Eine Regel r ist nun auf einen Σ-Graphen anwendbar, wenn folgende drei Bedingungen zutreffen: 1. L ohne negierte Kanten muss als Teilgraph im zu untersuchenden Graphen vorkommen. 2. In L vorkommende negierte Kanten dürfen in diesem Teilgraphen nicht vorkommen. 3. Fügt r Kanten zum Graphen hinzu, so muss mindestens eine Kante zwischen zwei bereits im Graphen befindlichen Knoten hinzugefügt werden, und diese Kante darf im Graphen noch nicht existieren. Abbildung 1 zeigt eine Graphersetzungsregel. Die Knoten werden durch ihr Label sowie einer Nummer, die unveränderte Knoten mit gleichem Label auf der linken und rechten Seite der Regel einander zuordnen, gekennzeichnet. Weiterhin ist der Name der Regel (Sammle-Ausdrücke-1) angegeben. 1 In seiner Doktorarbeit untersucht Aßmann attributierte Knoten Σ-Graph mit negierten Kanten wird als Σ¬ -Graph bezeichnet. 2 Ein 1 Abbildung 1: Graphersetzungsregel Anmerkungen: • Der passende Teilgraph wird Redex genannt, die dazugehörigen Knoten und Kanten entsprechend Redex-Knoten bzw. Redex-Kanten. • Bedingung (3) verhindert, dass eine Regel den Graphen gar nicht oder nur unwesentlich ändert und somit eine Endlosschleife verursacht. • Kanten zu zu löschenden Knoten dürfen existieren. Solche Kanten werden versteckte Kanten genannt. Sie werden bei der Ersetzung gelöscht, es ist nicht möglich, sie an einen anderen Knoten umzuleiten. • Das Label eines Knotens kann nicht geändert werden. • Im zu untersuchenden Graphen dürfen Kanten zwischen Knoten existieren, die in L nicht vorkommen. Solche Kanten werden bei der Ersetzung nicht gelöscht. Somit müssen nicht alle möglichen Kombinationen erlaubter Relationen spezifiziert werden, was zum einen die Anzahl der benötigten Regeln vergrößern würde, zum anderen würde bei einer Erweiterung des Modells (z.B. Hinzufügen eines neuen Typs von Relation) die Anpassung aller bereits spezifizierten Ersetzungsregeln notwendig. Definition Ein (relationales) Graphersetzungssystem G = (S, Z) besteht aus einer Menge S von Graphersetzungsregeln und einem Σ-Graphen Z, dem Axiom. Die Menge aller (relationalen) Graphersetzungssysteme wird mit RGRS3 bezeichnet. 3 Terminierung Eine beliebige Anwendungsreihenfolge der Ersetzungsregeln kann leicht zu einer Endlosschleife führen, z.B. wenn zu einer Regel auch ihre inverse existiert. Allgemein passiert dies, wenn Mengen von Regeln sich gegenseitig Redexe erzeugen. Weiterhin hat ein Graphersetzungssystem (im Allgemeinen) je nach Anwendungsreihenfolge der Regeln mehrere Normalformen, ist also indeterministisch. Um diese Probleme zu umgehen, wird eine Ausführungsreihenfolge für die Regeln berechnet. Die Idee dahinter ist, Regeln in Gruppen (Strata) zusammenzufassen. Dies wird Stratifizierung genannt.4 Grapherweiternde Regeln werden dabei zuerst angewandt, der Graph also größtmöglich „aufgebläht“. Anschließend werden die Regeln angewandt, die die Menge der Ecken und Kanten verringern. Aßmann merkt an, dass dies meist dem intuitiven Verständnis eines Optimierungsprozesses entspricht: 3 für engl.: relational graph rewrite systems Konzept stammt von Datalog 4 dieses 2 Das Hinzufügen von Knoten und Kanten entspricht der vorausgehenden Analyse, das „echte“ Ersetzen von Teilgraphen der anschließenden Optimierung. Für bestimmte Arten von Graphersetzungssystemen kann man nun zeigen, dass sie terminieren, und sogar eine eindeutige Normalform haben, also deterministisch sind. Diese Klassifizierungen von RGRS werden im folgenden beschrieben. EARS Regeln eines EARS5 fügen ausschließlich Kanten (Relationen) zwischen bestehenden Knoten zu einem Graphen hinzu. Ein solches Graphersetzungssystem terminiert offensichtlich in jedem Fall, da in jedem Iterationsschritt nach Bedingung (3) mindestens eine Kante hinzugefügt wird und die Menge der Knoten im Graphen endlich ist. AGRS Eine Obermenge der EARS bilden die AGRS6 . Jede Regel fügt mindestens eine Kante zwischen zwei Knoten ein, von deren Typ in dieser Regelmenge keine Knoten hinzugefügt werden. Eine Regelmenge kann damit zwar für sich sich selbst einen neuen Redex herstellen, aber nur endlich oft. Regeln eines solchen Systems werden Kanten-akkumulierend genannt. Man kann zeigen, dass Teilgraphen eines AGRS terminieren und somit das Graphersetzungssystem allgemein terminiert. AGRS werden dazu verwendet, Vorkommnisse von Mustern in einem Graphen zu identifizieren und mit einem Knoten zu markieren. SGRS, ESGRS In einem (E)SGRS7 steht nicht das Hinzufügen, sondern das Löschen von Knoten und Kanten im Vordergrund. Für ein ESGRS wird die Terminierung analog wie bei einem AGRS bewiesen, nur dass hier bestimmte Kantentypen gelöscht werden. Werden zusätzlich Knoten gelöscht, ist klar, dass ein Teilgraph spätestens dann terminiert, wenn seine Knotenmenge leer ist. Definition Die Menge SGRS S AGRS wird vollständige Graphersetzungssyteme (XGRS8 ) genannt. Abhängigkeiten Bevor Regeln zu Gruppen zusammengefasst werden, müssen Abhängigkeiten zwischen ihnen identifiziert werden. Zwei Regeln r1 und r2 können auf verschiedene Arten miteinander in Konflikt stehen, wobei diese Abhängigkeiten in die Kategorien „paarweise harmlos“, „unterstützend“, „gefährlich“ und „nicht-stratifizierbar“ einzuordnen sind. Beispiele für die erste Kategorie sind Regeln, die beide auf die Anwesenheit (bzw. bei negierten Kanten Abwesenheit) von Teilgraphen prüfen. Unterstützende Abhängigkeiten treten auf, wenn eine Regel den Redex für eine andere Regel erzeugen kann. Löscht r1 eine Kante, auf deren Abwesenheit r2 prüft, so muss Regel r1 entweder dem selben oder einem früheren Stratum zugeteilt werden. Gefährliche Abhängigkeiten sind solche, bei denen der Redex für die andere Regel zerstört wird, r1 beispielsweise auf die Existenz einer Kante testet, die r2 löscht. Die testende Regel muss einem Stratum mit niedrigerer Nummer als die hinzufügende (bzw. löschende) Regel zugeteilt werden. Nicht-stratifizierbare Abhängigkeiten sind schließlich solche, die gegenseitig ihre Redexe zerstören können. Treten solche Abhängigkeiten auf, existiert keine Stratifizierung. Eine solche Abhängigkeit tritt insbesondere jedesmal dann auf, wenn ein Knoten gelöscht wird, da ein zu löschender Knoten von der Regel vorher getestet wird. Dies wird Selbstabhängigkeit genannt. Werden Selbstabhängigkeiten außer acht gelassen, so spricht man von einer schwachen Stratifizierung. Die Menge aller (schwach) stratifizierbaren Graphersetzungssysteme wird (W)STRGRS 9 genannt. Offensichtlich ist WSTRGRS ⊂ STRGRS. Existieren zu einem Graphersetzungssystem mehrere Stratifizierungen, so lässt sich zeigen, dass sie zur gleichen Normalform führen. (Schwache) Stratifizierungen können bestimmte Abhängigkeiten lösen und garantieren, dass eine fest bestimmte Normalform ausgewählt wird. 5 für engl.: engl.: 7 für engl.: 8 für engl.: 9 für engl.: 6 für edge addition rewrite systems edge accumulation graph rewrite systems (edge) subtractive graph rewrite systems exhaustive graph rewrite systems (weakly) stratifiable graph rewrite systems 3 4 Codegenerierung Der generische Algorithmus Ordnungsauswertung, der ein XGRS ausführt, basiert auf geschachtelten Schleifen. Seine Ausführungsgeschwindigkeit hängt vom maximalen Ausgangsgrad eines Knotens bezüglich einer bestimmten Relation ab und arbeitet auf dünn besetzten Graphen besonders effizient. Aus den Regelmengen berechnete Daten werden in den als Template vorhandenen generischen Pseudo-Code eingesetzt, aus dem wiederum Code generiert wird. Abbildung 2 stellt exemplarisch einen Teil dieses Pseudo-Codes dar. Auf die genaue Semantik des Codefragments wird hier nicht eingegangen, lediglich das Prinzip soll verdeutlicht werden. forall-const(n1, n2, l) ∈ ∪r∈S E del (r) do m := nlabL (n1); forall y1 ∈ Nm do forall y2 ∈ y1 .l do if y2 .deleted then y1 .l −= y2 ; Abbildung 2: Pseudocode des generischen Algorithmus Das Codefragment führt drei ineinander geschachtelte Schleifen aus. forall-const ist eine Schleife, deren Iterationsmenge statisch bekannt ist, so dass sie bei der Codegenerierung ausgerollt werden kann. Andere Algorithmen sind ebenfalls anwendbar und für bestimmte XGRS womöglich effizienter, können aber Einschränkungen unterliegen. Für Datalog entwickelte Algorithmen sind beispielsweise anwendbar, falls das Graphersetzungssystem stratifizierbar ist. 5 Evaluierung Aßmann hat Teile der Optimierung ’faule Platzierung’10 mit Optimix implementiert, in einen Modula2 Übersetzer integriert und die Ergebnisse mit gcc und sun-cc mit Hilfe eines Benchmarks verglichen. Dabei wurde ein Geschwindigkeitsunterschied beim Übersetzen um ca. Faktor 10 festgestellt. In einer weiteren Messreihe wurde der Teil des generierten Codes, der die eigentliche Ersetzung durchführt, durch handgeschriebenen Code ersetzt, lediglich der Analyseteil wurde generiert. Die handgeschriebene Version ist dabei nur unwesentlich schneller als die generierte. Da die Anzahl der benötigten Regeln für den Analyseteil um ein Vielfaches höher als für den Transformationsteil ist, lässt sich folgern, dass bei wachsender Regelmenge zunehmend ineffizientere Algorithmen generiert werden. Hier besteht offensichtlich Optimierungsbedarf. 6 Zusammenfassung und Ausblick Bereits viele Aufgaben eines Optimierers können mit dem vorgestellten Ansatz spezifiziert werden. Derzeit ist es jedoch notwendig, generierten und handgeschriebenen Code zu mischen. Doch eignet sich das vorgestellte Verfahren, die Entwicklung von Optimierungsroutinen stark zu beschleunigen. Mit entsprechenden Erweiterungen des vorgestellten Systems werden weitere Aufgabentypen mit vollständigen Graphersetzungssystemen darstellbar sein. Die erreichte Abstraktion von der Zwischensprache erlaubt dabei hohe Wiederverwendbarkeit vom Code. Die noch mäßige Effizienz des generischen Ordnungsauswertungs-Algorithmus kann durch eine intelligentere Analyse und Hinzufügen von Metadaten zur Spezifikation stark verbessert werden. 10 engl.: lazy code motion 4