Funktionale Programmierkonzepte f¨ur die verteilte numerische

Werbung
Funktionale Programmierkonzepte
für die verteilte numerische Simulation
Ralf Ebner
Institut für Informatik
der Technischen Universität München
Lehrstuhl für numerische Programmierung
und Ingenieuranwendungen in der Informatik
Funktionale Programmierkonzepte
für die verteilte numerische Simulation
Ralf Ebner
Vollst¨andiger Abdruck der von der Fakult¨at für Informatik der Technischen Universit¨at
München zur Erlangung des akademischen Grades eines
Doktors der Naturwissenschaften (Dr. rer. nat.)
genehmigten Dissertation.
Vorsitzender:
Univ.-Prof. Dr. Heinz-Gerd Hegering
Prüfer der Dissertation:
1.
Univ.-Prof. Dr. Christoph Zenger
2.
Univ.-Prof. Dr. Manfred Broy
Die Dissertation wurde am 1.7.1999 bei der Technischen Universit¨at München eingereicht
und durch die Fakult¨at für Informatik am 6.12.1999 angenommen.
Funktionale Programmierkonzepte
für die verteilte numerische Simulation
Zusammenfassung
Die Arbeit zeigt, wie die g¨angigen funktionalen Sprachkonzepte der Einfach-Zuweisung,
der Rekursion, der hierarchischen Datentypen und der Funktionen höherer Ordnung für
die automatisierte Parallelisierung und Verteilung von Anwendungen im Bereich der
adaptiven numerischen Simulation einsetzbar sind.
Hierzu wird die im Rahmen dieser Arbeit weiterentwickelte funktionale Sprache FASAN
beschrieben. FASAN-Programme bilden eine Koordinationsschicht, deren parallele Semantik mit Hilfe von Datenflussgraphen spezifiziert wird. Sie schließen typische Fehler
des parallelen Programmierens wie Verklemmungen, Laufbedingungen und falsche Kommunikation aus. Weiterhin werden für die Komponenten hierarchischer Datenstrukturen
direkte Kommunikationsverbindungen erzeugt und eine zu starke Synchronisation vermieden. Schließlich verbindet die Übersetzung von FASAN in Java die Forderungen nach
Portabilit¨at und nach paralleler Effizienz.
Eingesetzt wird das FASAN-System für die rekursive Substrukturierung, ein hierarchisches Gebietszerlegungs- und numerisches Lösungsverfahren für Aufgaben aus dem Bereich der Strukturmechanik, die auf elliptische lineare partielle Differentialgleichungen
führen. Hierzu werden für die iterative Lösungsmethode drei neue Varianten vorgeschlagen, die sich besonders für die verteilte Bearbeitung eignen, sowie eine Möglichkeit zur
dynamischen Lastverteilung der entstehenden adaptiven Baumstruktur. Beispielrechnungen belegen die praktische Einsetzbarkeit des FASAN-Systems, das parallele Effizienz
mit einfacher Programmierung verbindet.
An dieser Stelle möchte ich mich bei meinem Doktorvater Prof. Dr. Christoph Zenger
bedanken, der mir die Aufgabenstellung überlassen, durch seine unerschöpflichen Ideen
und Anregungen diese Arbeit gelenkt und mir trotzdem viel Freiraum für eigene Konzepte gelassen hat. Bei Prof. Dr. Manfred Broy bedanke ich mich für dieÜbernahme des
Zweitgutachtens.
Mein herzlicher Dank gilt außerdem folgenden Leuten, die zum Gelingen dieser Arbeit
beigetragen haben: Martin Backschat hat durch Diskussionen über die Verteilungsaspekte
des Substrukturierungsprogramms und durch Testen von Programmteilen viel beim Anwendungsteil für diese Arbeit mitgewirkt. Er war immer über die neuesten Entwicklungsumgebungen, gerade im Java-Bereich, bestens informiert und hat mir wertvolle Hinweise
gegeben. Seine gründliche und kritische Durchsicht dieser Arbeit hat wesentlich zu einer klareren Darstellung beigetragen. Eifrige Korrekturleser waren auch meine Ehefrau
Elfriede und mein Vater. Alexander Pfaffinger hat mit mir viele der Konzepte von FASAN erörtert, mit seinem Dünngitter-Programm eine wertvolle Testanwendung bereitgestellt und mich immer wieder zur Weiterentwicklung von FASAN ermuntert. Der intensiven Zusammenarbeit mit Stefan Bischof und Thomas Erlebach im Rahmen des SFB
342 verdanke ich die Entwicklung der Lastverteilungskonzepte für die adaptive rekursive
Substrukturierung. Dank meiner Kolleginnen und Kollegen, die zu dem ¨außerst guten Arbeitsklima am Lehrstuhl beigetragen haben, konnte ich auch so manche Durststrecke in
den vergangenen vier Jahren überstehen. Schließlich hat mir meine Frau die notwendige
moralische Unterstützung w¨ahrend meiner T¨atigkeit am Lehrstuhl und vor allem in der
Endphase der Arbeit gegeben.
Inhaltsverzeichnis
i
Inhaltsverzeichnis
1
Einführung
1
1.1 Schwierigkeiten verteilter Programmierung . . . . . . . . . . . . . . . .
2
1.2 Verwandte Arbeiten im Parallelisierungsbereich . . . . . . . . . . . . . .
7
1.3 Aufgabenstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.4 Überblick über die Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2
Grundlagen funktionaler Programmierung und Semantik
2.1 Kennzeichen funktionaler Sprachen . . . . . . . . . . . . . . . . . . . .
2.2 Formale Beschreibung und operationelle Semantik funktionaler Sprachen
2.3 Parallele Semantik mit Prozessnetzen . . . . . . . . . . . . . . . . . . .
15
15
18
22
3
Die funktionale Koordinationssprache FASAN
3.1 Sprachbeschreibung . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.1.1 Externe Funktionen und externe Typen . . . . . . . . . . . . . .
3.1.2 Rekursive Funktionen . . . . . . . . . . . . . . . . . . . . . . .
3.1.3 Hierarchische Datentypen . . . . . . . . . . . . . . . . . . . . .
3.1.4 Endrekursion . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.1.5 Lokationen zur verteilten Berechnung . . . . . . . . . . . . . . .
3.1.6 Funktionen höherer Ordnung . . . . . . . . . . . . . . . . . . . .
3.2 Eine abstrakte Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2.1 Rekursive Funktionen . . . . . . . . . . . . . . . . . . . . . . .
3.2.2 Hierarchische Datentypen . . . . . . . . . . . . . . . . . . . . .
3.2.3 Externe Funktionen, externe Typen und Funktionstypen . . . . .
27
27
29
31
32
33
34
36
39
41
44
45
4
Operationelle Semantik von FASAN
4.1 Datenflussgraphen . . . . . . . . . . . . . . . . . . . . .
4.2 Abbildung der abstrakten Syntax auf Datenflussgraphen .
4.3 Datenflussgraphen als Berechnungsmodell . . . . . . . .
4.3.1 Auswertung von Funktionsknoten . . . . . . . .
4.3.2 Entfaltung von -Knoten . . . . . . . . . .
47
47
49
54
55
57
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Inhaltsverzeichnis
ii
.
.
.
.
58
59
63
65
5
Übersetzung von FASAN in Java
5.1 Erfahrungen aus vorhandenen Implementierungen . . . . . . . . . . . . .
5.2 Auswahl der Zielsprache . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2.1 Auswahl der Parallelisierungsplattform . . . . . . . . . . . . . .
5.3 Übersetzung in ein sequentielles Grundprogramm . . . . . . . . . . . . .
5.3.1 Rekursive Funktionen . . . . . . . . . . . . . . . . . . . . . . .
5.3.2 Hierarchische Datentypen . . . . . . . . . . . . . . . . . . . . .
5.3.3 Externe Funktionen und Typen . . . . . . . . . . . . . . . . . . .
5.3.4 Funktionen höherer Ordnung . . . . . . . . . . . . . . . . . . . .
5.4 Erweiterungen für gemeinsamen Speicher mit Threads . . . . . . . . . .
5.4.1 Rekursive Funktionen . . . . . . . . . . . . . . . . . . . . . . .
5.4.2 Hierarchische Datentypem . . . . . . . . . . . . . . . . . . . . .
5.5 Erweiterungen für verteilten Speicher mit RMI . . . . . . . . . . . . . .
5.5.1 Rekursive Funktionen . . . . . . . . . . . . . . . . . . . . . . .
5.5.2 Hierarchische Datentypen . . . . . . . . . . . . . . . . . . . . .
5.6 Praktische Verwendung von FASAN-Programmen . . . . . . . . . . . . .
71
71
73
75
78
78
81
82
83
85
85
86
88
89
91
97
6
Anwendung von FASAN: Die rekursive Substrukturierung in der numerischen Simulation
99
6.1 Gebietszerlegung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
6.1.1 Beschreibung der Substrukturierung . . . . . . . . . . . . . . . . 102
6.1.2 Klassifikation der Punkte . . . . . . . . . . . . . . . . . . . . . . 103
6.2 Aufbau der Gleichungssysteme . . . . . . . . . . . . . . . . . . . . . . . 105
6.2.1 Assemblierung der Gleichungssysteme aus den Sohnknoten . . . 106
6.2.2 Hierarchisierung des Gleichungssystems . . . . . . . . . . . . . 106
6.2.3 H¨angende Punkte und Dirichlet-Randwerte . . . . . . . . . . . . 110
6.2.4 Algebraische Vorkonditionierung . . . . . . . . . . . . . . . . . 112
6.2.5 Auswirkung auf die Lösungsiterationen . . . . . . . . . . . . . . 112
6.3 Bisherige Lösungsvarianten . . . . . . . . . . . . . . . . . . . . . . . . . 112
6.3.1 Ein Gauß-Seidel-Löser auf hierarchischer Basis . . . . . . . . . . 114
6.4 Neue Lösungsvarianten . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
6.4.1 Ein Gauß-Seidel-Löser mit teilweiser Elimination von starken
Kopplungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
6.4.2 Ein Jacobi-Löser auf hierarchischer Basis . . . . . . . . . . . . . 118
4.4
4.3.3 Entfaltung von Funktionsknoten . . . . . . . . .
4.3.4 Entfaltung von Konstruktor- und Selektorknoten
4.3.5 Lokationen . . . . . . . . . . . . . . . . . . . .
Eigenschaften der Datenfluss-Semantik . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Inhaltsverzeichnis
6.5
6.6
iii
6.4.3 Ein CG-Löser auf hierarchischer Basis . . . . . . . . . . . . . . . 120
Das Koordinationsprogramm in FASAN . . . . . . . . . . . . . . . . . . 123
Lastverteilung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
6.6.1 Ein statisches Verfahren . . . . . . . . . . . . . . . . . . . . . . 127
6.6.2 Kostenabsch¨atzung der Lösungsverfahren . . . . . . . . . . . . . 128
6.6.3 Ein dynamisches Verfahren . . . . . . . . . . . . . . . . . . . . . 131
7
Beispielrechnungen und Leistungsmessungen
135
7.1 Testbeispiele für die Simulation . . . . . . . . . . . . . . . . . . . . . . 135
7.2 Numerische Ergebnisse der rekursiven Substrukturierung . . . . . . . . . 139
7.2.1 Gleichm¨aßige Verfeinerung . . . . . . . . . . . . . . . . . . . . 140
7.2.2 Adaptive Verfeinerung . . . . . . . . . . . . . . . . . . . . . . . 143
7.3 Paralleler Ablauf mit FASAN . . . . . . . . . . . . . . . . . . . . . . . . 144
7.3.1 Einsatz von Threads . . . . . . . . . . . . . . . . . . . . . . . . 145
7.3.2 Verteilte Berechnung bei gleichm¨aßiger Verfeinerung . . . . . . . 146
7.3.3 Verteilte Berechnung bei adaptiver Verfeinerung . . . . . . . . . 147
8
Schlussbetrachtungen und Ausblick
149
Literaturverzeichnis
153
Index
163
iv
Inhaltsverzeichnis
1
Kapitel 1
Einführung
Rechnernetze sind heutzutage sehr weit verbreitet. Aus Sicht des Programmierers handelt
es dabei fast immer um Systeme mit verteiltem Speicher, im Gegensatz zu parallelen
Programmierumgebungen im engeren Sinn mit einem gemeinsamen Adressraum. Mit der
Nutzung von Rechnernetzen für den Bereich der numerischen Simulation zur Verteilung
von Anwendungen werden folgende Ziele verfolgt:
1. W¨ahrend nicht-interaktiven Arbeitszeiten können die vorhandenen Rechnerkapazit¨aten für langlaufende Berechnungen sinnvoll genutzt werden.
2. Langdauernde Rechnungen können bei verteilter Ausführung beschleunigt werden.
3. Um der physikalischen Realit¨at möglichst nahe zu kommen, ist man an einer
möglichst feinen diskreten Auflösung (Gitterweite) interessiert, der aber vom
Hauptspeicher des Rechners Grenzen gesetzt sind. Eine kleinere Gitterweite ist
möglich, wenn die Anwendung die Speicher-Ressourcen mehrerer Rechner gleichzeitig nutzt und das Gitter auf die Rechner verteilt, beispielsweise durch Gebietszerlegung.
W¨ahrend Ziel 1 noch leicht zu erreichen ist, wenn voneinander unabh¨angige Berechnungen auszuführen sind, erhöht sich die Schwierigkeit der Aufgabe, sobald die Berechnungen (wie bei Ziel 2 und 3) kooperieren, also zur richtigen Zeit Daten austauschen müssen.
Zun¨achst stellt sich die Frage, welche Programmiersprache für solche verteilten numerischen Anwendungen eingesetzt werden soll. Programmiersprachen (ob für sequentielle, parallele oder verteilte Anwendungen) lassen sich grob in zwei Kategorien einteilen:
W¨ahrend prozedurale Sprachen stark am Maschinenmodell des von-Neumann-Rechners
orientiert sind und feste Berechnungsabl¨aufe und Speichermanipulationen vorschreiben,
legen deklarative Sprachen nur die Ergebnisse einer Berechnung fest, nicht aber den genauen Rechenweg.
Zur deklarativen Sprachfamilie gehören funktionale Sprachen, auch applikative Sprachen genannt. Berechnungen in funktionalen Programmen sind Auswertungen von Ausdrücken. Logikprogrammierung, ebenfalls eine Art deklarativen Programmierens, basiert
2
Einführung
auf dem Berechnungsprinzip der Deduktion. Der Vorteil von funktionaler Programmierung gegenüber Logik-Programmen ist darin zu sehen, dass das Ablaufverhalten in operationellen Semantiken leichter erfassbar und damit auch leichter optimierbar ist.
Konzepte der Objektorientierung wie abstrakte Datentypen und polymorphe Datenstrukturen sind bei Sprachen aus beiden Familien zu finden und stellen somit ein eigentlich
orthogonales Paradigma dar. Dieser Umstand wird sich auch bei dem in dieser Arbeit
entwickelten System darstellen, indem die funktionale Koordinationsschicht von FASAN
auf die objektorientierte Sprache Java aufgesetzt wird.
1.1 Schwierigkeiten verteilter Programmierung
Viele Schwierigkeiten bei der Entwicklung sowohl sequentieller als auch paralleler Programme sind darauf zurückzuführen, dass prozedurale Sprachen immer mit einer Semantik von Zustands¨anderungen versehen sind, wobei der Zustand“ sehr groß ist und im Prin”
zip den ganzen Speicherbereich umfasst, den das Programm beansprucht. Diese Problematik wurde schon frühzeitig unter anderem von Backus erkannt, der als möglichen Ausweg die funktionale Programmierung vorgeschlagen hat [Bac78]. Ein Konzept für funktionale Programmierung in technisch-wissenschaftlichen Anwendung wird in [Zen92]
vorgestellt.
Mit dem Einsatz funktionaler Sprachen werden einige Ursachen dafür, dass parallele und
verteilte Programmierung schwieriger und aufwendiger ist als sequentielle, vermieden.
Insbesondere müssen nicht mehr bei jedem Berechnungsschritt der globale Speicherzustand und die implizit in einem prozeduralen Programm enthaltenen Seiteneffekte berücksichtigt werden. Doch stellen sich noch weitere Probleme, wie das folgende Beispiel zeigen soll.
Beispiel 1.1 Um ein -dimensionales Problem zu lösen, kann man es oft auf ein dimensionales Problem sowie kleinere Probleme der Dimension reduzieren. In
der numerischen Simulation wird h¨aufig auf ein geometrisch vorgegebenes Gebiet
ein Gitter gelegt, an dessen Kreuzungspunkten die (diskrete) N¨aherungslösung für
ein (kontinuierliches) physikalisches oder technisches Problem zu ermitteln ist. So
können die Punkte eines zweidimensionalen Gitters, wie es schematisch in Abb. 1.1
zu sehen ist, als eine Liste von Listen gespeichert werden mit Hilfe einer Datenstruktur der Form
type Area = empty2()| cons2(first:Line; rest:Area)
type Line = empty() | cons (head:double; tail:Line).
In Abb. 1.2 ist diese Datenstruktur über dem Gitter dargestellt. Die cons-Knoten
bauen die eindimensionalen Listen und die cons2-Knoten die zweidimensionalen
Listen auf. Dies ist auch für viele Algorithmen vorteilhaft, die unidirektional auf
diesem Gitter arbeiten. Sie zerlegen für die Berechnung die zweidimensionale Liste
1.1 Schwierigkeiten verteilter Programmierung
3
1
2
3
4
5
n
2
3
4
5
6
n+1
m
m+1
m+2
m+3
m+4
m+n
-1
Abbildung 1.1 Ein zweidimensionales Gitter mit Zeilen und Spalten über einem rechteckigen Gebiet. In den Punkten ist der von Datenabh¨angigkeiten bestimmte frühestmögliche Zeitschritt
eingetragen, zu dem sein Wert von der Funktion wavefront aus Beispiel 1.1 berechnet werden
kann.
in ihre eindimensionalen Teillisten, die dann jede für sich bearbeitet werden, in etwa
wie folgende Funktion:
function wavefront (prec:Line; ar:Area) -> ar’:Area
{
if not isempty2(ar) then
line = step(prec, ar.first);
// (1)
ar’ = wavefront(line, ar.rest); // (2)
else
ar’ = empty();
fi
}
Wie die Formulierung der Funktion wavefront schon andeutet, treten allerdings Datenabh¨angigkeiten in vertikaler Richtung auf, nicht nur in Richtung der cons-Listen: Zeile (2) h¨angt über die Variable line von Zeile (1) ab. Es sieht also so aus, als ob eine
(horizontale) Liste erst berechnet werden kann, wenn die darüberliegende Liste fertiggestellt ist. Wünschenswert ist aber eine Berechnungsreihenfolge, die Werte einer Liste
möglichst unmittelbar der benachbarten unteren Liste zur Verfügung stellt, auch wenn
im Programmtext syntaktisch die untere Liste von der komplett aufgebauten oberen Liste
abh¨angt.
Enth¨alt das Gitter Zeilen mit je Punkten, so führt die Auswertung auf einen Zeitbedarf
von
. Dies sehen wir an der Nummerierung in den Gitterpunkte in Abb. 1.2, die
angibt, in welchem Zeitschritt die Berechnung für den betreffenden Punkt erfolgen kann.
Bei ausreichend Prozessoren (mindestens ) ist aber die optimale Auswertungsstrategie
diejenige von Abb. 1.1, die mit
Zeitschritten auskommt.
cons2
first
cons
cons
tail
Einführung
tail
4
cons
cons
cons
cons
2
3
4
5
n
cons
cons
cons
cons
cons
n+2
n+3
n+4
n+5
2n
cons
cons
cons
cons
cons
head
head
1
cons2
first
cons
tail
rest
head
rest
n+1
Cons2
first
cons
tail
...
head
n(m-1)
n(m-1)
n(m-1)
n(m-1)
n(m-1)
+1
+2
+3
+4
+5
mn
Abbildung 1.2 Eine Liste von Listen als Datenstruktur für die Punkte zur Diskretisierung eines
zweidimensionalen Gebiets.
Auch bei Problemen aus dem nicht-numerischen Bereich kann ein solcher Konflikt zwischen bequemer Aufschreibung und optimaler paralleler Berechnung auftreten. Nehmen
wir einen Algorithmus, der aus mehreren Einzelschritten besteht, von denen jeder für
seine Argumente und Ergebnisse Listen verwendet. Es ist dann nicht notwendig, jeden
Schritt streng sequentiell nach dem anderen auszuführen. Besser ist es, genau wie oben
bei der zweidimensionalen Liste die Schritte in einer Pipeline anzuordnen, bei der jedes Teilergebnis des vorausgehenden Schritts dem folgenden Schritt sofort zur Verfügung
steht.
Statt Listen finden sich bei rekursiven numerischen Algorithmen meist B¨aume, also hierarchische Datenstrukturen, so z. B. in vielen Gebietszerlegungsalgorithmen für die numerische Simulation, die nach dem Divide&Conquer-Prinzip verfahren.
Beispiel 1.2 Ein typisches Programm-Schema für derartige Algorithmen unterteilt etwa
ein zweidimensionales Gebiet in zwei 2D-H¨alften und eine 1D-Trennlinie mit folgender Datenstruktur:
Tree1 = nil1() | tree1(left:Tree1; val:double; right:Tree1)
Tree2 = nil2() | tree2(top: Tree2; sep:Tree1; bottom:Tree2)
Damit l¨asst sich ein dünnes Gitter wie in Abb. 1.3 aufbauen. Ein Algorithmus, der die
Baumstruktur von oben herab durchl¨auft, verwendet die Ergebnisse der Mittellinie
mid für Berechnungen auf oberem und unterem Teilgebiet:
topdown2D (border:Tree1; area:Tree2) -> ... {
{
mid = topdown1D (area.sep);
topdown2D (mid, area.top);
topdown2D (mid, area.bottom); ... }
1.1 Schwierigkeiten verteilter Programmierung
5
top
mid
bottom
Abbildung 1.3 Eine dünnes Gitter, dessen Punkte in einem Baum von B¨aumen gehalten werden.
Auch hier stellt sich wieder das Problem der überm¨aßigen Synchronisation, da Datenabh¨angigkeiten in vertikaler Richtung und damit orthogonal zur Richtung der Datenstrukturen bestehen. Benötigt wird also ein System, das die strenge Auswertungsreihenfolge,
die sich aus der Aufschreibung der Datenabh¨angigkeiten ergibt, aufbricht und die voneinander abh¨angigen Funktionsaufrufe verzahnt ausführt.
Nicht immer sind die Datenstrukturen regul¨ar. Wenn bestimmte Punkte eines Gitters
weggelassen werden oder in Gebieten mit größerem Diskretisierungsfehler eine feinere Auflösung durch mehr Punkte notwendig ist, sind nicht alle Unterb¨aume eines Knotens der Datenstruktur gleich tief wie im obigen Beispiel. Es entsteht dann eine weitere
Schwierigkeit bei der verteilten parallelen Berechnung: Der Programmieraufwand wird
bedeutend größer, weil zus¨atzlich zu jeder Datenstruktur angegeben werden muss, wie
diese zu verpacken und wann und wohin diese Daten zu senden sind.
Um uns klar zu werden, welche Anforderungen wir an ein System stellen, das diese Probleme angehen kann, ist es vorteilhaft, die Konstruktion des zu erstellenden verteilten
Programms in verschiedene Teilaspekte zu gliedern [AG94, Qui94]:
der (in der Regel sequentielle) Algorithmus,
die Zerteilung des Algorithmus in parallel ausführbare Teile,
die Konsequenzen der Zerteilung, insbesondere die notwendige Synchronisation
der entstehenden Programmteile und ihre wechselseitige Kommunikation, und
schließlich
die Lastverteilung, also die Angabe, auf welchem Rechner welche Teilberechnung
auszuführen ist.
6
Einführung
Der sequentielle Algorithmus muss notwendigerweise explizit erstellt oder vorgegeben
werden. Bei der Zerteilung des Algorithmus in parallele Teilberechnungen stehen wir oft
vor dem Problem, dass ein automatisches Vorgehen zu viele kleine Teile produziert und
damit die Granularit¨at zu fein und der Overhead der Parallelisierung zu groß wird. Hier erscheint es sinnvoll, dem Programmierer die Möglichkeit zur Angabe großer Teilprobleme
offenzuhalten. In verschiedenen Arbeiten (z. B. [Ham94, Pfa97]) wird dargestellt, dass es
in lose gekoppelten Rechnernetzen nicht sinnvoll ist, feine Granularit¨at auszuschöpfen.
Ist die Zerteilung festgelegt, folgt die Organisation der Kommunikation und Synchronisation, die in einem komfortablen System voll automatisch erledigt werden muss. Denn
gerade in diesem Bereich sind die meisten Fehlerquellen angesiedelt, die die Entwicklung
verteilter Programme so aufwendig und zeitraubend machen. Um typische Schwierigkeiten der parallelen Programmierung wie Verklemmungen, race conditions (deutsch auch
Laufbedingungen genannt) und Kommunikationsfehler soll sich der Programmierer nicht
kümmern müssen. Auch die Speicherverwaltung muss idealerweise im verteilten System
automatisch erfolgen.
Bei der Lastverteilung h¨angt es vom Algorithmus ab, ob sie automatisch vorgenommen
oder besser vom Programmierer festgelegt wird. In jedem Fall sollte eine Möglichkeit
bestehen, den Lastverteilungsmechanismus leicht auszuwechseln. Für manche Anwendungen kann es sinnvoll sein, zum Beispiel ein Verfahren einzusetzen, das eine neu entstehende Teilaufgabe an einen Rechner zuweist, der aus einer Menge von momentan nicht
oder wenig besch¨aftigten Rechnern ausgew¨ahlt wird.
Oft kann aber speziell bei der Lastverteilung auf die Erfahrung, Intuition und die Expertise des Programmierers nicht verzichtet werden. Dies erkennen wir beispielsweise in
der ausgefeilten Lastverteilung für Algorithmen auf dünnen Gittern in der Arbeit [Pfa97],
die einerseits ein automatisierter Lastverteiler nicht finden kann und die andererseits zu
anwendungsspezifisch ist, als dass sie vorteilhaft als grundlegendes Verteilungsmuster in
ein parallelisierendes System eingebaut werden könnte.
Es gibt schon eine ganze Reihe von Ans¨atzen auf höherer Ebene. Diese unterscheiden
sich zun¨achst darin, ob der Programmierer auf die Parallelisierung Einfluss nehmen kann.
Implizite Parallelisierung scheint zwar komfortabel, l¨asst aber kaum Optimierungsspielraum, wenn die gewünschte oder erwartete Effizienz nicht erreicht wird. Wenn wir uns
also für eine teilweise explizite Parallelisierung in dem zu entwickelnden Werkzeug
entscheiden, stellt sich die Frage, ob eine vorhandene Sprache durch neue Schlüsselworte
und Konstrukte erweitert oder eine neue Sprache zur Koordination definiert werden soll.
In eng gekoppelten Systemen mit geringen Kommunikationskosten kann auch feingranulare Parallelit¨at ausgenutzt werden. Dies ist ein Grund, weshalb in diesem Bereich fast
ausschließlich mit Spracherweiterungen gearbeitet wird, die eine enge Verflechtung von
parallelem Kontrollfluss und eigentlicher Berechnung erlauben. Für unsere Aufgabenstellung der verteilten Simulation haben wir es aber mit Plattformen zu tun, in denen Kommunikation um viele Größenordnungen teurer als die Berechnung ist. Hier ist es ganz
natürlich, große sequentielle Blöcke auf einer programmtechnisch hohen Ebene zu ko-
1.2 Verwandte Arbeiten im Parallelisierungsbereich
7
ordinieren. Koordinationssprachen haben in der Regel einen sehr kleinen Umfang und
erleichtern damit die Formalisierung der durch sie beschriebenen Konzepte. Die Anzahl
der syntaktischen Konstrukte, ihre auf die Aspekte der Parallelisierung eingeschr¨ankte
Semantik und ihre abgegrenzte Wechselwirkung mit dem Berechnungsteil ist überschaubarer als bei Spracherweiterungen.
1.2 Verwandte Arbeiten im Parallelisierungsbereich
Zur Parallelisierung in funktionalen Programmen gibt es bereits eine große Zahl von Vorschl¨agen, Methoden und Systemen. Wir wollen hier die wichtigsten nennen und darstellen, inwieweit sie sich für unser Ziel der verteilten numerischen Simulation mit rekursiven
Algorithmen eignen und welche zus¨atzlichen Anforderungen wir haben.
Implizite Parallelisierung: Von den weit verbreiteten funktionalen Sprachen SML
[Pau91] und Haskell [HF93, Tho96, PH97] existieren schon seit einiger Zeit parallele
Varianten, mit denen Parallelit¨at automatisch erkannt wird, z. B. das parallele Haskell pH
[Hil93, JH93, Mae96]. Es handelt sich im Grunde um Datenfluss-Sprachen, in denen Datenabh¨angigkeiten ermittelt werden und daraus die parallel bearbeitbaren Teilberechnungen gebildet werden. Als Auswertungsstrategie wird meist lenient evaluation verwendet,
eine Form der strikten Auswertung mit nicht-strikten Fallunterscheidungen. Diese Sprachen sind aber zu unterscheiden von Datenfluss-Sprachen im engeren Sinn, die Programme auf Maschinenebene und meist für spezielle Datenfluss-Architekturen beschreiben
[PT94].
Die Datenfluss-Sprachen Id [ANP89, Nik90] und SISAL [CFBO92, FMS 95] sind ebenfalls funktionale Sprachen. Ihre Kontrollstrukturen und Parallelisierungsstrategien basieren auf Iterationen und Array-Strukturen, w¨ahrend rekursiver Parallelismus nicht ausgenützt werden kann. Für unseren Anwendungsbereich benötigen wir aber in erster Linie
rekursive Funktionen und baumartige dynamische verteilte Datenstrukturen, deren Komponenten sich gegenseitig nicht synchronisieren. Dies wird von diesen Sprachen nicht
direkt unterstützt.
Noch ein anderer Ansatz zur impliziten Parallelisierung funktionaler Programme ist die
Transformation in einen Graph von Kombinatoren, also von variablenfreien Ausdrücken
[Tur79]. Die Berechnung erfolgt dann mittels Graphreduktion, einem Verfahren, das viel
inh¨arente Parallelit¨at bietet. Allerdings gibt es keine effizienten Implementierungen für
verteilte Umgebungen [Pey89].
Problematisch ist bei impliziter Parallelisierung außerdem der Umstand, dass meist zu
viele und zu feingranulare parallele Kontrollflüsse erzeugt werden, die nicht nur in verteilten Umgebungen nicht verwaltet werden können, sondern auch g¨angige Laufzeitsysteme
für Threads überfordern. Ein Ausweg für die Implementierung w¨aren Laufzeitsysteme,
8
Einführung
die die Erzeugung extrem leichtgewichtiger Threads erlauben. Eine solche Plattform bietet z. B. Cilk [BJK 96, Joe96, Sup98], das dann nicht als Programmier-, sondern als Zielsprache verwendet werden kann. Allerdings ist auch Cilk nur für gemeinsamen Speicher
einsetzbar. In jüngster Zeit gibt es zwar Bestrebungen für Implementierungen von Cilk
auf verteiltem Speicher, doch sind diese auf eine sehr leistungsf¨ahige Netz-Hardware angewiesen [Leb98].
Meist sind Systeme zur impliziten Parallelisierung nur für gemeinsamen Speicher konzipiert. Sie leisten nur die Zerteilung und Parallelisierung der Berechnung. Die Zuordnung
von parallelen Teilaufgaben zu Prozessoren oder Rechnern kann nicht spezifiziert werden.
Es ist dann Aufgabe des Zielsystems, etwa des Schedulers des Betriebssystems oder einer Thread-Bibliothek, neue parallele Berechnungen zu verteilen. Damit kommen sie für
unseren Anwendungsbereich nicht in Frage, weil sie sich nicht für Rechnernetze eignen,
wo sich zus¨atzlich das Problem der Verteilung und Lokalit¨at von Datenstrukturen stellt.
Concurrent Constraint-Programmierung ist eine besondere Variante der impliziten Parallelisierung mit Logikprogrammen, bei der auch mit unvollst¨andigen Informationen Deduktionsschritte möglich sind, ¨ahnlich wie bei den hierarchischen Datenstrukturen in unseren einleitenden Beispielen. Vertreter sind Oz [MS96, SHW93] sowie Janus [SKL90],
dessen semantische Fundierung in [SRP90] zu finden ist. Für Aufgaben aus dem Bereich des technisch-wissenschaftlichen Rechnens sind Logik- oder Constraint-Sprachen
meist zu komplex und abstrahieren zu stark von der Berechnungsreihenfolge, da für einen
numerischen Algorithmus meist schon ein Grobkonzept für die Ausführung und das Verhalten vorhanden ist.
Datenparallelismus: Datenparallele Sprachen dienen dazu, Daten zu partitionieren und
dann Funktionen (parallel) auf diese Datenblöcke anzuwenden [HL92, KPS94]. Dies ist
das duale Paradigma zum Task- oder Prozess-Parallelismus, wo verteilt aufgerufene Funktionen mit den nötigen Daten versorgt werden.
Die datenparallele Sprache NESL [BCH 94, BG96, Ble96] bietet als Grundstrukturen
geschachtelte Arrays und Lastbalancierungsstrategien. Allerdings stoßen datenparallele
Ans¨atze, sobald Datenabh¨angigkeiten auftreten, schnell an ihre Grenzen, werden ineffizient oder erlauben es nicht, solche Abh¨angigkeiten überhaupt auszudrücken. So fehlen
auch in NESL die sprachlichen Möglichkeiten, Datenabh¨angigkeiten wie aus Beispiel 1.1
auf Seite 2 zu beschreiben.
Spracherweiterungen: GpH [THM 96, LT97, HLPT98] ist ein Beispiel für eine minimale Spracherweiterung, aufbauend auf Haskell. Es werden nur die beiden zus¨atzlichen
Operatoren
und
eingeführt, die angeben, ob strenge Datenabh¨angigkeit oder
ein potentiell parallel auszuwertender Ausdruck vorliegt. Die explizite Verwendung von
ist wichtig, weil Haskell einerseits eine Sprache mit verzögerter Auswertung ist,
aber andererseits Teilberechnungen schon parallel angestoßen werden sollen, auch wenn
1.2 Verwandte Arbeiten im Parallelisierungsbereich
9
ihr Ergebnis noch von keinem anderen Ausdruck gebraucht wird (spekulative Auswertung).
Eine komplexere Spracherweiterung für Haskell stellt Caliban [Kel89] dar, w¨ahrend Concurrent Clean [NSvP93] ein Mittelweg zwischen großer Ausdrucksmöglichkeit und Ein
fachheit ist. In Concurrent Clean gibt es außer reinen Partitionierungsannotationen (für echt parallele Berechnung), und (für quasiparallele Berechnungen), wie
sie im Prinzip auch GpH bietet, zudem die Möglichkeit, mit explizit zu spezifizieren, wo eine parallele Berechnung ausgeführt werden soll.
Unter der Bezeichnung para-funktionale Programmierung finden wir Erweiterungskonzepte, etwa für Haskell [Hud91, Hud88], die ebenfalls explizite Platzierung von Prozessen
durch einen Ausdruck der Form "! erlauben. Dabei gibt die Annotation mit der
Prozessor-ID #"! an, auf welchem Prozessor der Ausdruck auszuwerten ist. Zus¨atzlich
kann das Scheduling von Kontrollflüssen gesteuert werden, indem Ausdrücken Labels
zugewiesen werden.
In eine andere Richtung orientiert sich die Sprache Eden [BLOMP98], die Haskell um
die explizite Beschreibung von Prozessen erweitert. Diese Prozesse werden funktional
spezifiziert (Prozess-Abstraktion) und bei ihrer Erzeugung (Prozess-Instantiierung) durch
Kan¨ale miteinander verbunden. Auf den Kan¨alen können Datenströme oder auch Kanalnamen ausgetauscht werden. Direkte Kan¨ale, sogenannte Bypasses, können dynamisch
erzeugt werden. Allerdings muss diese Erzeugung explizit angegeben werden, im Gegensatz zu den von uns gewünschten impliziten direkten Verbindungen für Komponenten
hierarchischer Datenstrukturen.
Koordinationssprachen: Das Programm wird in zwei Einheiten aufgeteilt: Das Berechnungsmodell beschreibt den Algorithmus, das Kooridinationsmodell das Verhalten
und die Parallelit¨at [GC92]. Diese Zweiteilung bringt Flexibilit¨at in den Entwurf. Beispielsweise können unterschiedliche Paradigmen unterstützt werden. Oft wird der algorithmische Teil in einer imperativen Sprache beschrieben, w¨ahrend die Koordination in
einer deklarativen Sprache anzugeben ist. Zudem ist es einfacher und übersichtlicher, die
Semantik der Koordinationsschicht zu untersuchen, weil sie von der zugrundeliegenden
Hostsprache getrennt ist. Die bekanntesten Vertreter von Koordinationssprachen sind Linda [GC92, Wes92] und PCN [FOT92, FT93, FT94] sowie dessen Vorg¨anger STRAND
[FT90]. W¨ahrend Linda auf einem (ggf. virtuell) gemeinsamen Speicherbereich für Objekte, dem Tupelraum, aufbaut, stellt PCN auf niedrigerer Ebene drei Konstrukte zur sequentiellen, parallelen und bedingten Komposition von Tasks bereit. Zu diesen Koordinationssprachen gibt es auch ausgereifte Implementierungen.
Algorithmische Skeletons [Col98, DGTY95, Ski95] sind ein Sammelbegriff für effiziente
Implementierungen von h¨aufig auftretenden parallelen Operationen auf rekursiven Datenstrukturen (Listen, B¨aume etc.). Genauer handelt es sich um Funktionen höherer Ordnung,
die die Anwendung beliebiger Funktionen auf die einzelnen Elemente der Datenstrukturen festlegen. Im Grunde sind Skeletons sprachunabh¨angig und auch in Koordinations-
10
Einführung
sprachen formulierbar, sofern die verwendeten Datentypen auf der Ebene der jeweiligen
Sprache bekannt und zugreifbar sind.
SCL [DGTY95] ist eine Methodik, die die parallele Koordination durch algorithmische
Skeletons leistet. SCL stellt drei Arten von Skeletons zur Verfügung: Konfigurationen
zur expliziten Datenverteilung, elementare datenparallele Funktionen (map, fold, filter
etc.) und kontrollflussbasierte Funktionen. Vom Standpunkt der Koordinationssprachen
ist SCL ein Ansatz auf sehr hohem Abstraktionsniveau und großer M¨achtigkeit. Nachteilig ist allerdings, dass es nur experimentelle Implementierungen auf Basis von Arrays
gibt, etwa Fortran-S auf Basis von Fortran, aber keine mit komplexeren Datenstrukturen.
Für unseren Anwendungsbereich stellt sich bei den vorhandenen Koordinationssprachen
wieder das Problem, dass sie nicht darauf achten, dass Daten als Teil von Strukturen
so weit wie möglich auf ihrem Ursprungsrechner gehalten werden und beim Versenden
den direktesten Weg nehmen. Denn für die meist sehr großen Datenmengen in numerischen Anwendungen ist es nicht ausreichend, nur die funktionalen Abh¨angigkeiten aus
einem Programmstück herauszufinden, sondern auch so viel Kommunikation wie möglich
zu vermeiden. Je teurer die Kommunikation ist, desto wichtiger wird Datenlokalit¨at und
Kommunikationsvermeidung, besonders auf Rechnernetzen.
Statische Analyse: Sie hat natürlich den großen Vorteil, dass sie zur Laufzeit keinen
Overhead erzeugt. Nachteilig sind aber Voraussetzungen, die oftmals nur die Optimierung
stark eingeschr¨ankter Programme oder Anwendungen zulassen.
Zur statischen Analyse von Baumstrukturen gehört beispielsweise die sogenannte
Deforestation-Technik [Wad90, GLP93, Gil96] zur Elimination tempor¨arer B¨aume. Sie
setzt strenge Linearit¨at voraus, schreibt also vor, dass das Ergebnis einer Ausdrucksberechnung nur einmal als Argument eines weiteren Ausdrucks verwendet werden darf.
Die mehrfache Verwendung ist aber wichtig z. B. beim Top-down-Durchlauf von B¨aumen, wenn die Berechnungen in den Sohnknoten von den Ergebnissen des Vaterknotens
abh¨angen und jeder Sohnknoten den Wert des Vaters verwenden muss.
Der Diffusion-Ansatz [HTI99] erlaubt die Optimierung von parallelen Koordinationsfunktionen höherer Ordnung für die datenparallele Ausführung. Die gefundenen Transformationen können in allen parallelen Sprachen verwendet werden, die Funktionen höherer
Ordnung unterstützen. Datenparallelismus ist aber, wie schon erw¨ahnt, meist zu einschr¨ankend für rekursive numerische Anwendungen, da oft Abh¨angigkeiten zwischen den
Teildaten bestehen.
Ein weiteres Problem statischer Analysemethoden ist, dass der Programmierer keine Kontrolle mehr darüber hat, wie das Programm tats¨achlich ausgeführt wird (es sei denn es
handelt sich um sehr einfache Optimierungen), und damit auch kaum noch die Möglichkeit, selbst Optimierungen in den Programmtext einfließen zu lassen.
1.3 Aufgabenstellung
11
Explizite Parallelisierung: Dies ist wohl die ¨alteste Form der Parallelisierung funktionaler Sprachen. Zu erw¨ahnen ist das future-Konzept, z. B. in MultiLisp [Hal85, Hal90],
das parallel auswertbare Ausdrücke kennzeichnet. Dabei besteht allerdings noch die
Möglichkeit für das Laufzeitsystem zu entscheiden, ob tats¨achlich ein neuer Task für ein
future-Konstrukt in Abh¨angigkeit von der Granularit¨at erzeugt wird [MH91]. In QLisp
[GG98] wird diese Entscheidung durch benutzerdefinierte Pr¨adikate gef¨allt. Doch auch
in aktuellen Arbeiten und Systemen hat die explizite Parallelisierung, u. a. aus Gründen
der Effizienz, ihre Berechtigung. Auf vergleichsweise niedrigem Abstraktionsgrad muss
beispielsweise in CML (Concurrent ML [Rep91, Rep98]) Kommunikation und Synchronisation der parallelen Prozesse von Hand programmiert werden, ebenso bei Distributed
ML [Kru93].
Zusammenfassend stellen wir fest, dass zwar schon viele Methoden und Systeme für die
Parallelisierung in funktionalen Programmen verfügbar sind. Jedoch werden unsere besonderen Forderungen nach Datenlokalit¨at, direkter Kommunikation und Aufbrechen von
Datenabh¨angigkeiten nicht oder nur unzureichend erfüllt. Dennoch können wir aus den
vorhandenen Arbeiten verschiedene Ans¨atze wie z. B. die Datenflussanalysen, algorithmische Skeletons und Techniken der Koordination aufgreifen, um mit einer eigenen Koordinationssprache FASAN diese Anforderungen zu erfüllen.
1.3 Aufgabenstellung
Ziel der Sprache FASAN (Functions Applied to Substructured Data in Algorithms
for Numerical Simulations) und ihrer Implementierung ist es, die Parallelisierung von
Algorithmen der numerischen Simulation auf hierarchischen Datenstrukturen für die
Ausführung auf Rechnernetzen zu automatisieren. Dabei soll die Effizienz einer von Hand
vorgenommen Parallelisierung erreicht oder — bei Datenabh¨angigkeiten wie in den Beispielen 1.1 und 1.2 — sogar übertroffen werden. Rekursive Algorithmen auf hierarchischen Datenstrukturen spielen einerseits in modernen adaptiven numerischen Verfahren
zur technisch-wissenschaftlichen Simulation eine zentrale Rolle, andererseits können sie
gerade durch die Möglichkeit der Adaptivit¨at und resultierender Unregelm¨aßigkeit in den
Datenstrukturen nur sehr mühsam von Hand für die verteilte Berechnung aufbereitet werden.
Eine teilweise explizite Parallelisierung ist für FASAN erwünscht, damit der Programmierer seine Ideen und Erfahrungen verwerten kann. Daran scheint in verteilten Umgebungen allgemein auch kein Weg vorbeizuführen [Ham94]. Andererseits sollen Routineund Fleißarbeiten, die nur der Organisation der Parallelisierung, Kommunikation und
Synchronisation dienen, auf jeden Fall eingespart werden.
Wie oben erw¨ahnt, eignen sich funktionale Sprachen besonders für diesen Anwendungsbereich und zur einfacheren parallelen Beschreibung. Im Grunde soll FASAN eine Koordinationssprache sein, die Funktionen aus einer sequentiellen Hostsprache zur nebenl¨aufigen Ausführung konfiguriert. Als Hostsprachen sind C, C++, FORTRAN sowie
12
Einführung
Java nötig. Denn es sollen große Programmteile aus bereits bestehendem Code wiederverwendet werden können, da der Anwender meist nicht bereit ist, auf Unterroutinen,
in denen viel Entwicklungsarbeit steckt, zu verzichten. Für numerische Aufgaben stehen zudem hoch-optimierte Bibliotheken (BLAS, LAPACK etc.) zur Verfügung, die auch
für neue Anwendung nutzbar sein müssen. Deshalb soll die funktionale Ebene, die ein
FASAN-Programm beschreibt, lediglich der parallelen Komposition der sequentiellen
Module dienen, obgleich einfache arithmetische Berechnungen auch direkt in FASAN
formulierbar sein sollen.
Verteilte Bearbeitung erfordert Konzepte zur verteilten Datenhaltung und für verteilte
Datenstrukturen. Dabei ist bei den Verfahren in Anbetracht der hohen Kommunikationskosten besonderes Augenmerk auf Reduzierung der Kommunikation zu legen. Die Anforderung an FASAN ist die Bildung hierarchischer Datenstrukturen nach dem sogenannten
Kabelbaum-Prinzip: Sie müssen unnötige Kommunikation, die durch eine bequemere
textuelle Beschreibung zustande gekommen ist, eliminieren. Wesentliche Idee ist dabei,
dass die Daten der Baumkomponenten direkt vom Sender zu den jeweiligen Empf¨angern
gelangen, auch wenn es im Programmtext so erscheint, als liefen sie über verschiedene
Funktionsaufrufe und u. U. auch verschiedene Rechner als Zwischenstationen. Eine weitere Anforderung ist die Unabh¨angigkeit der Knotenwerte. Ist der Wert in einem Baumknoten berechnet, so muss er entsprechenden Verbraucherfunktionen zur Verfügung stehen, ohne dass der komplette Baum aufgebaut ist. Hier w¨are die Programmierung mit
konventionellen, wenig abstrahierenden parallelen Modellen wie Nachrichtenaustausch
zu aufwendig und fehleranf¨allig. Gefragt ist also ein Verfahren, das die hierarchischen Datenstrukturen automatisch entsprechend der verteilten Ausführung verschickt, aber wirklich nur dorthin, wo sie für die Berechnung auch gebraucht werden.
In numerischen Simulationen werden oft mit gleichen Ablaufstrukturen verschiedene Datens¨atze berechnet, etwa für mehrere aufeinanderfolgende Zeitschritte. Auch beim Lösen
eines Gleichungssystems kann es interessant sein, nur die rechten Seiten zu variieren, was
etwa in der Strukturmechanik verschiedenen Belastungen des zu untersuchenden Objekts
entspricht. Auch innerhalb eines iterativen Lösers sind Ergebnisse einer Iteration als Argumente der folgenden Iteration zu verwenden. In prozeduralen Sprachen wird dies durch
Schleifen ausgedrückt. Das funktionale Pendant sind endrekursive Funktionen. Hier ist es
möglich, die Ergebnisse einer Iteration als Startwerte für die neue Iteration auf demselben
Stack- und Speicherbereich zu verwenden.
Die Lastverteilung soll benutzerdefiniert geschehen, weil für komplizierte Abh¨angigkeitsstrukturen die optimale Verteilung nicht automatisch gefunden werden kann. Dabei
ist zu berücksichtigen, dass auch dynamische Lastverteilung auf zur Laufzeit ermittelte
Zielrechner möglich sein muss und in FASAN entsprechende Schnittstellen vorzusehen
sind.
Als Ergebnis soll ein portables, wartbares und leicht anzuwendendes Werkzeug entstehen, das FASAN-Programme in eine ausführbare Form übertr¨agt, die diese Aufgaben
bew¨altigt. Weiterhin soll die praktische Verwendbarkeit an einer größeren Anwendung
aus dem Bereich der numerischen Simulation demonstriert werden.
1.4 Überblick über die Arbeit
13
1.4 Überblick über die Arbeit
Im folgenden Kapitel 2 geben wir zun¨achst die formalen Grundlagen funktionaler Programmierung an. Es werden Möglichkeiten für die Beschreibung der operationellen Semantik mit Hilfe von Regelsystemen und — für den parallelen Fall — mit Hilfe von
Prozessnetzen dargestellt.
Daran schließt sich das Kapitel 3 über die Beschreibung der Syntax von FASAN an. Diese Syntax ist, ganz im Sinn von Koordinationssprachen, wenig umfangreich, erfasst aber
die wesentlichen Aspekte funktionaler Sprachen, also Einfach-Zuweisungen, Rekursion
und hierarchische Datenstrukturen. Im Vergleich zu den Vorg¨angerarbeiten werden hier
außerdem Funktionen höherer Ordnung unterstützt. Als Beispielprogramme dienen Funktionen, die für Teilaufgaben der numerischen Simulation verwendet werden können und
damit den unmittelbaren Bezug zum Anwendungsbereich von FASAN zeigen. Als Vorbereitung für die Spezifikation der Semantik enth¨alt das Kapitel die Transformation in eine
abstrakte Syntax.
Kapitel 4 führt das mathematische Modell der Datenflussgraphen ein. Dieses Modell eignet sich sehr gut als operationelle parallele Semantik von FASAN-Programmen und wurde informell u. a. bereits in [EPZ96, EP96, EP98, Pfa97] vorgestellt. Hier wird Wert auf
eine durchg¨angige formale Abbildung von FASAN-Programmen in abstrakter Syntax auf
(zun¨achst statische) Datenflussgraphen gelegt. Das dynamische Verhalten der Programme wird dann durch die ebenfalls formal eingeführten Konzepte der Auswertung und der
Entfaltung von Funktionsknoten im Datenflussgraphen beschrieben. Das Kapitel schließt
mit dem Nachweis von Sicherheitsbedingungen (Verklemmungsfreiheit, Ausschluss von
Laufbedingungen) und Effizienzaspekten (direkte Kommunikation, Datenlokalit¨at, höherer Parallelit¨atsgrad).
Für den praktische Einsatz beschreibt Kapitel 5 eine Implementierung von FASAN. Wir
werden von Erfahrungen aus vorausgegangenen Implementierungen [Rau92, Ebn94] berichten und die Gründe für die Wahl von Java als Implementierungsplattform darstellen.
Die nachher vorgeschlagene Übersetzung von FASAN in Java vermeidet die explizite
dynamische Generierung von Datenflussgraphen. Die Idee ist, durch eine ausreichende
Zahl von Kontrollflüssen dafür zu sorgen, dass die von FASAN garantierte Parallelit¨at
ausgenützt wird. Das neue FASAN-System ist dadurch wesentlich flexibler und benutzerfreundlicher als die Vorg¨angerversionen.
Eine größere Anwendung, das Verfahren der rekursiven Substrukturierung zur numerischen Lösung elliptischer linearer partieller Differentialgleichungen aus den Gebieten
der Strukturmechanik und Strömungsmechanik wird in Kapitel 6 dargestellt. Für dieses
Verfahren schlagen wir nach der Beschreibung der Ablauf- und Organisationsstrukturen
drei neue Lösungsvarianten vor, die für die verteilte Ausführung gewisse Vorteile gegenüber dem bisherigen Lösungsverfahren bringen. Außerdem geben wir die FASANImplementierung der zugehörigen Koordinationsschicht an. Das Kapitel endet mit der
Kostenabsch¨atzung der verschiedenen Lösungsvarianten und einer darauf aufbauenden
14
Einführung
und in FASAN übernommenen dynamischen Lastverteilung.
Kapitel 7 w¨ahlt konkrete Anwendungsbeispiele für die numerische Simulation aus. Es
werden sowohl numerische Ergebnisse der neuen Lösungsvarianten, insbesondere das
Konvergenzverhalten und die Rechenkosten, experimentell ermittelt als auch deren Laufzeiten auf Rechnernetzen durch Koordination mit dem FASAN-System aufgeführt.
Schließlich geben wir in Kapitel 8 nochmals einen kurzen Überblick über die Methoden
und Ergebnisse der Arbeit sowie Möglichkeiten für aufbauende und erg¨anzende Untersuchungen.
15
Kapitel 2
Grundlagen funktionaler
Programmierung und Semantik
Diese Kapitel fasst formale Grundlagen zusammen, die wir im Weiteren für die Beschreibung von Rekursion, hierarchischen Datentypen und Funktionen höherer Ordnung brauchen. Wir beschreiben zun¨achst genauer die Merkmale funktionaler Sprachen anhand von
Beispielen (Abschnitt 2.1). Sodann geben wir die formale Beschreibung funktionaler Programme und ihrer operationellen Semantik an (Abschnitt 2.2). Für die Modellierung verteilter Ausführung werden wir sp¨ater Prozessnetze verwenden. Die nötigen Grundlagen
dafür stellen wir in Abschnitt 2.3 zusammen.
2.1 Kennzeichen funktionaler Sprachen
In den modernen funktionalen Sprachen stoßen wir fast durchwegs auf Konzepte, die
eine gewisse Abgrenzung zu prozeduralen Sprachen erlauben. Darin stecken gleichzeitig
die wesentlichen Ideen funktionaler Programmierung, weshalb wir sie hier im Einzelnen
erl¨autern.
Das Fehlen von Seiteneffekten ist das wichtigste Merkmal funktionaler Sprachen. Es
gibt keinen Programmzustand; eine Funktion liefert bei mehrmaligem Aufruf immer das
gleiche Ergebnis. Die Seiteneffektfreiheit ist der Grund für die referentielle Transparenz,
d. h. der einer Variablen einmal zugewiesene Wert ¨andert sich nicht im Laufe des Berechnungsvorgangs. Somit wird die Bearbeitungsreihenfolge von Berechnungstermen, zwischen denen keine expliziten Datenabh¨angigkeiten bestehen, beliebig und die parallele
Bearbeitung möglich. Da sich eine Funktion allein durch die Abbildung ihrer Eingabedaten auf die Ausgabe beschreiben l¨asst, ist der Nachweis von Programmeigenschaften
meist leichter durchführbar als in prozeduralen Sprachen.
Es gibt funktionale Sprachen wie Lisp mit seinen vielen Dialekten [SG86, KCR98], die
zur Übersetzungszeit keine Typisierung vornehmen. Ein polymorphes Typsystem hat
16
Grundlagen funktionaler Programmierung und Semantik
sich mittlerweile aber für funktionale Sprachen bew¨ahrt, da Typprüfung zurÜbersetzungszeit schon viele Fehler abfangen kann. Moderne funktionale Sprachen wie SML
[Pau91] oder Haskell [HF93, Tho96, PH97] erlauben, Typvariablen einzuführen, um Datentypen zu parametrisieren (parametrischer Polymorphismus).
Beispiel 2.1
In Haskell schreiben wir beispielsweise
data tree a = Leaf a
| Branch (a, tree a, tree a)
und haben damit eine generische Baumstruktur mit Knoten eines beliebigen, aber
innerhalb eines Baums festen Typs a definiert.
Haskell übernimmt ein Konzept aus der objekt-orientierten Welt für Datentypen, n¨amlich
das Vererbungskonzept, in dem Datentypen eine Hierarchie der Spezialisierung bilden. In
einem Ausdruck darf dann ein Argument immer auch einen spezielleren Typ haben als
der formale Parameter.
Hier ist anzumerken, dass parametrischer Polymorphismus, also Datentyp-Definitionen
mit Typvariablen, in objektorientierten Sprachen nicht selbstverst¨andlich ist, z. B. nicht
in Java [Bis98]. Er kann allerdings durch Vorübersetzer erreicht werden; bei Java etwa sorgen Pakete namens Pizza [OW97b] oder GJ [BOSW98] für parametrischen Polymorphismus. Pizza ist eine Obermenge der Java-Sprache und dazu gedacht, Java durch
Einführung von Templates (¨ahnlich wie C++) besonders bei der Wiederverwendung von
algebraischen Datentypen flexibler zu machen.
Automatische Speicherverwaltung von dynamisch erzeugten Datenstrukturen ist zwar
Standard in funktionalen Sprachen, aber kein eindeutiges Kennzeichen für sie. Die sogenannten garbage collectors finden sich auch in zahlreichen objektorientierten Sprachen,
etwa Java oder Eiffel, da sie den Aufwand beim Programmieren und bei der Fehlersuche
entscheidend verringern.
Funktionen sind Bürger 1. Klasse“, d. h. Funktionen sind als Werte von Variablen so”
wie als Argumente und Ergebnisse anderer Funktionen erlaubt. Dies führt dann zu Funktionen höherer Ordnung, wobei die Ordnung durch den Typkonstruktor für Funktions
typen wie folgt festgelegt ist: Die Ordnung eines Typs ist induktiv definiert:
falls Grundtyp oder Datentyp ohne Funktionskomponente
falls
und Wenn eine Funktion wieder Funktionen als Parameter bekommt oder zurückgibt, spiegelt
sich dies unmittelbar im Typ wider, der dieser Funktion zugeordnet ist. Betrachten wir
das folgende Beispiel.
2.1 Kennzeichen funktionaler Sprachen
Beispiel 2.2
17
Mit den Abkürzungen
type R = Float
type F = (R,R)->R
hat eine zweidimensionale reellwertige Funktion u in Haskell den Typ F, geschrieben u::F. Differentialoperatoren bilden Funktionen vom Typ F auf Funktionen gleichen Typs ab. Der zweidimensionale Laplace-Operator (in der Analysis meist als
geschrieben) erh¨alt in seiner diskretisierten Form den Typ F->F. Oder anders ausgedrückt: Laplace liefert, angewandt auf eine Funktion (R,R)->R, wieder eine Funktion vom Typ (R,R)->R. Dies entspricht der vollst¨andigen Klammerung
((R,R)->R)->((R,R)->R). Der Typkonstruktor -> ist also rechtsassoziativ.
Verwenden wir die weniger abgekürzte Form dieses Typs,
Laplace :: F->(R,R)->R
kann Laplace auch als Funktion mit zwei Argumenten aufgefasst werden: Dabei
ist Laplace eine Funktion, die als erstes Argument eine differenzierbare Funktion
vom Typ F erwartet, z. B u, und als n¨achstes Argument eine Auswertungsstelle (Typ
(R,R)), an der der Funktionswert berechnet werden soll.
Ein generischer numerischer Löser NumSolve, der für mehrere Differentialoperatoren einsetzbar ist, bekommt einen solchen Operator, der vom Typ der Ordnung 2
ist, als Argument und wird damit zu einer Funktion vom Typ der Ordnung 3, also
NumSolve:: (F->F)->(R,R)->(R,R)
Viele prozedurale Sprachen erlauben keine Funktionen als Ergebnis anderer Funktionen,
so auch nicht C, C++ und Java. Für objektorientierte Sprachen existieren Aufs¨atze auf
die Grundsprache, die Funktionen erster Klasse“ ermöglichen; für Java leistet dies unter
”
anderen das Paket Pizza [OW97b].
Als Auswertungsstrategie gibt es zun¨achst die (in prozeduralen Sprachen durchwegs
anzutreffende) eager evaluation durch call by value. SML und Lisp sind solche — als
strikt bezeichnete — Sprachen und werten die Argumente vor einem Funktionsaufruf
vollst¨andig aus. Manche funktionale Sprachen verwenden dagegen verzögerte Auswertung (lazy evaluation); zu dieser Familie gehören Gofer und Haskell. Die Terme, die nicht
weiter ausgewertet werden, sind Anwendungen von Datentypkonstruktoren. Sie stellen
die Normalformen dar, genauer schwache Kopfnormalformen, weil sie nur den Kopf“,
”
also den Anfang oder die oberste Ebene der Datenstruktur berechnen.
Beispiel 2.3
Form
In Haskell ist mit dem Listenkonstruktor (:) eine Funktionsdefinition der
squares :: Int -> [Int]
squares x = (x*x):squares(x+1)
nicht fehlerhaft, da Komponenten von Datenstrukturen — hier der Ausdruck
squares(x+1) — erst ausgewertet werden, wenn eine andere Funktion darauf
zugreift.
18
Grundlagen funktionaler Programmierung und Semantik
In parallelen Systemen ist die ausschließlich verzögerte Auswertung allerdings nicht immer sinnvoll [THM 96]. Wenn ein neuer paralleler Kontrollfluss mit einem Teilterm eines
zu berechnenden Ausdrucks erzeugt wird, muss durch spezielle Operatoren dafür gesorgt
werden, dass der Teilterm spekulativ ausgewertet wird. Die Rekursionstiefe der spekulativen Auswertungen kann man zum Beispiel von den Systemressourcen, speziell der
Prozessorenanzahl, abh¨angig machen [HLPT98].
2.2 Formale Beschreibung und operationelle Semantik
funktionaler Sprachen
Wie wir bereits gesehen haben, wird der Typ von Funktionen mit dem Typkonstruktor
angegeben. Bei Funktionen höherer Ordnung können Terme, die mit
aufgebaut sind,
auch geschachtelt sein. Formal sind es Typausdrücke:
Definition 2.4 Sei eine Menge von Typsymbolen. Dann ist die Menge
ausdrücke induktiv definiert:
Jedes Typsymbol
ist
ein
Typausdruck:
.
Sind , so ist auch der Ausdruck
der Typ-
. Programmiersprachen bieten typischerweise mehrere syntaktische Varianten, um Deklarationen, Kontrollstrukturen, Berechnungsterme etc. auszudrücken. Ziel einer abstrakten Notation ist es, nur den Kern einer Sprache zu repr¨asentieren, ohne syntaktische
Erg¨anzungen, die zwar dem Programmierkomfort dienen, aber nicht weiter untersucht
werden müssen, weil sie auf diesen Sprachkern zurückgeführt werden können. Neben
den Typsymbolen , die wir gerade benutzt haben, sind dazu nur noch Funktionssymbole
nötig:
besteht aus einer Menge von Typsymbolen (den
Definition 2.5 Eine Signatur
Grundtypen) sowie einer Menge von Funktionssymbolen.
Für die Funktionssymbole ist zus¨atzlich eine Abbildung definiert, die
jedem Funktionssymbol einen Typausdruck (oder Funktionstyp) zuordnet.
Statt schreiben wir meist kürzer
oder . Fast immer wird eine algorithmische Signatur Verwendung finden, die Fallunterscheidungen ermöglicht:
"! #%$'&)( &)*+,& .- ./ 10.2 Definition 2.6 Eine Signatur
Mit den Symbolen aus dieser Signatur
heißt algorithmisch, wenn
können wir Terme bilden:
2.2 Formale Beschreibung und operationelle Semantik funktionaler Sprachen
19
Meist verwenden wir für Terme die Bezeichnung Ausdrücke. Die Terme ohne Variablen
und Funktionssymbole aus heißen Grundterme.
Definition 2.7 Die Menge
der Terme mit Variablen über einer Signatur
ist induktiv definiert:
Alle Variablen sind Terme:
.
Jedes nullstellige
Funktionssymbol
aus
ist
ein
Term:
, so ist für jedes
Sind
,
aus auch
ein Term in
,
0 0
1
Definition 2.8 Die Funktion ermittelt die in einem Term vorkommenden Variablen, wobei
für für
,
und
für
, ,
und
00 0 Ein technisches Hilfsmittel, von dem wir sp¨ater oft Gebrauch machen werden, sind endliche Sequenzen von Elementen aus . Die Menge dieser Sequenzen bezeichnen wir mit
. Namen für Sequenzen versehen wir ebenfalls mit einem Stern, z. B. . Zum
Aufbau von Sequenzen verwenden wir einen Konkatenationsoperator sowie einen Operator zur Formierung einer Sequenz aus ihren Elementen. Die leere Sequenz ist .
Analog zu Mengen definieren wir noch
! #"
!$"
&%' !$" / 0.
!()"*+( ( -, ( Bisweilen werden wir endliche Sequenzen auch als Tupel für Funktionsargumente ver wenden, also ! " gleichbedeutend mit . Mit Sequenzen lassen sich
Operationen auf Listen von Symbolen bequem ausdrücken, zum Beispiel die Typisierung
.( ergibt für eine Sequenz von
einer Parameterliste. Die Abbildung 0 Variablen die Sequenz der diesen Variablen zugeordneten Typen:
0
0
0
.( !$" !$"
! "*
/
!" *" 0 /
Solche induktive Definitionen mathematischer Abbildungen werden wir in dieser Arbeit
sehr h¨aufig verwenden. Durch die Rekursion über den strukturellen Aufbau einer Sequenz, die selbst endlich ist, ist sofort klar, dass die Abbildung auch wohldefiniert ist.
1
Mit
0.13254 bezeichnen wir die Potenzmenge einer Menge 2
.
20
Grundlagen funktionaler Programmierung und Semantik
In Programmen können Rekursionen wesentlich allgemeinere Form haben. In funktionalen Sprachen bildet Rekursion die Grundlage der Berechnungsvollst¨andigkeit, deshalb
legen wir eine Möglichkeit fest, dies notationell auszudrücken. Dazu erweitern wir eine
algorithmische Signatur um die Formeln zu :
Definition 2.9 Eine rekursive Programmdefinition ist eine Sequenz von Termpaaren
! "
"
0
0
.
Jeder Formel aus einer rekursiven Programmdefinition können
zuordnen und deshalb selbst als Funktionswir den Funktionstyp
wobei für jedes i mit
die Form hat und
der Ausdruck aus
sein muss mit symbol auffassen.
Da diese Art von Rekursion nicht mehr notwendig eine einfache strukturelle Rekursion ist, sondern von allgemeineren Abbruchbedingungen abh¨angt, muss die Bedeutung einer rekursiven Programmdefinition festgelegt werden, die fehlerhafte oder nichtterminierende Programme identifiziert.
Bei funktionalen Programmen, die ja beschreiben, was berechnet werden soll, aber nicht
festlegen, wie genau bei der Berechnung vorgegangen wird, ist in erster Linie das Ergebnis wichtig, das die Auswertung von Ausdrücken liefert. Dies wird durch eine denotationelle Semantik für ein rekursives Programm beschrieben. Eine solche Semantik ist
die Fixpunkt-Deutung für die aus den Rekursionsgleichungen gebildeten Funktionalgleichungen, wie sie in [Bro92, Bes95, Thi94] zu finden ist. Damit wird festgelegt, welcher
mathematischen Funktion ein rekursives Programm entspricht, also welche Ergebnisse
das Programm liefert.
Wird allerdings die parallele Abarbeitung funktionaler Programme betrachtet, interessiert
auch, wie die Auswertungen vonstatten gehen und wie die Teilberechnungen verteilt werden. Wie der tats¨achliche Berechnungsvorgang l¨auft, beschreibt eine operationelle Semantik (auch natürliche Semantik genannt), die durch Regelsysteme angegeben wird. Ein
Regelsystem setzt Terme mit ihren Normalformen in Relation. Dazu benötigen wir eine
-Algebra, die jedem Typkonstruktor und jedem Funktionssymbol aus der Signatur eine
Bedeutung gibt:
über einer Signatur besteht aus
Definition 2.10 Eine -Algebra sowie einer Interpretation der Funktionssymbole aus ,
Tr¨agermengen
eine Funktion wobei
für
die
Interpretation
ist.
% % Im -Kalkül [Bar84, Thi94] wird von der -Algebra der -Terme Gebrauch gemacht,
um eine operationelle Semantik durch syntaktische Umformungen zu beschreiben. Wir
2.2 Formale Beschreibung und operationelle Semantik funktionaler Sprachen
21
benötigen eine -Algebra, um eine rekursive Programmdefinition zu einem kompletten
Programm zu machen:
ist eine rekursive ProgrammdefinitiDefinition 2.11 Ein rekursives Programm
on und eine -Algebra über einer algorithmischen Signatur .
Sei im Folgenden
ein rekursives Programm. Die in den Tr¨agermen
gen aus enthaltenen Konstanten bilden die Normalformen
der Berechnungsterme
aus
über , weil sie nicht weiter reduziert werden können. Damit kann eine
Berechnungsrelation
als operationelle Semantik für das rekursive Programm angegeben werden. Das dazu verwendete Regelsystem legt fest, ob für
das Urteil bewiesen werden kann. Wenn ein
einen Ausdruck solcher Beweis existiert, bedeutet das, dass der Wert von ist, und jeder Beweisschritt
entspricht dann einem Berechnungsschritt. Existiert kein Beweis, ist der Wert der Berechnung , was als fehlerhafte oder nicht-terminierende Berechnung zu interpretieren ist. Es
sind folgende Regeln nötig:
% 1. Normalformen können nicht reduziert werden:
falls
(2.1)
2. Die Grundfunktionen sind strikt, deshalb werden die Argumente vor dem Einsetzen
zuerst ausgewertet:
für
und falls
(2.2)
3. Auch die rekursiv definierten Funktionen werden strikt ausgewertet:
für
und falls 4. Einzig die bedingten Ausdrücke sind nicht-strikt; zuerst wird die Bedingung
wertet und abh¨angig von ihrem Ergebnis nur einer der beiden Zweige:
- / ! %# $'&)( / &)*"+ & / 0 "! #%$ &)( / & *"+ & (2.3)
ausge-
(2.4)
Dieses Regelsystem ist deterministisch, da für jede Form eines Berechnungsterms nur
eine einzige Regel anwendbar ist.
22
Grundlagen funktionaler Programmierung und Semantik
Eine nicht-strikte Semantik entsteht, wenn wir statt der Regel (2.3) die Terme
zuerst reduzieren, sondern gleich in den Ausdruck einsetzen (call by name):
für
und falls nicht
(2.5)
Die mögliche Parallelit¨at bei der Reduktion eines Berechnungsterms ergibt sich implizit
durch alle unabh¨angigen Teilb¨aume und Zweige des Ableitungsbaums. Soll jedoch wie
im Fall von FASAN eine Sprache untersucht werden, die speziell der Parallelisierung und
der verteilten Koordination dient, sollte das parallele Verhalten auch explizit modellierbar
sein. Dazu eignen sich Prozessnetze wesentlich besser, die wir im n¨achsten Abschnitt
beschreiben.
2.3 Parallele Semantik mit Prozessnetzen
Eine Beschreibungsmöglichkeit parallelen Verhaltens verteilter Systeme liefern PetriNetze in ihren vielen Varianten [Rei85, RW91, Jen97]. Sie verbinden in idealer Weise die
Formalisierung paralleler Abl¨aufe mit der Anschaulichkeit, die aus ihrer graphischen Darstellung resultiert. Um funktionale Programme mit Petrinetzen beschreiben zu können,
wird jedem Funktionssymbol in einem Berechnungsterm eine Transition, also ein Knoten
im Netz, zugeordnet. Die genaue Transformation von Programmen in Petri-Netze verschieben wir auf Kapitel 4. Dort ist die angestrebte Semantik eines Programms in der
Koordinationssprache FASAN ein Datenflussgraph, der als Abart von Petri-Netzen gesehen werden kann. Wir wollen hier die grundlegenden und in dieser Arbeit benötigten
Definitionen von Petri-Netzen geben, die bei Vertrautheit mit der Netz-Terminologie auch
übersprungen werden können.
, wobei und
Definition 2.12 Ein Petri-Netz oder kurz Netz ist ein Tripel
disjunkte Mengen sind und
.
wird Verbindungsrelation genannt, die Elemente von Plätze und die Elemente von
Transitionen.2 Die Menge der Netze bezeichnen wir mit .
%
%
( Die transitive Hülle der Flussrelation
wird
geschrieben. Anstelle von schreiben wir auch kurz . Wir führen noch eine Schreibweise ein, um alle
Vorg¨anger und Nachfolger einer Transition oder eines Platzes bezeichnen zu können:
Definition 2.13 Für ein heißt die Menge Vorbereich von , die Menge Nachbereich von .
(
( ( ( (
2
Ein Petri-Netz ist ein gerichteter bipartiter Graph. Oft wird die Verbindungsrelation auch Flussrelation genannt und mit bezeichnet. Die Plätze werden in der Literatur auch Stellen genannt und für meist geschrieben; für die Menge der Transitionen wird normalerweise geschrieben. Um in den folgenden Kapiteln aber Verwechslungen mit syntaktischen Kategorien, speziell mit Mengen von Funktions-,
Selektor- und Typensymbolen zu vermeiden, werden in dieser Arbeit durchgehend die Namen verwendet.
1
4
2.3 Parallele Semantik mit Prozessnetzen
PSfrag replacements
erzeugen
23
ablegen
entnehmen
verbrauchen
Abbildung 2.1 Graphische Darstellung eines einfachen Netzes für ein Erzeuger-VerbraucherSystem mit einelementigem Puffer.
, die jedem Platz
Die Markierung eines Netzes
ist die Abbildung
eine Anzahl von sogenannten Datenelementen zuordnet. Die Menge aller Markierungen
eines Netzes bezeichnen wir mit . Das dynamische Verhalten eines Netzes wird durch
das Schalten von Transitionen beschrieben:
, wenn für alle .
gilt:
Eine Transition schaltet oder feuert bei einer Markierung , in Zeichen
, und erzeugt dabei eine neue Markierung , wobei definiert ist durch
falls falls sonst
Definition 2.14
aktiviert ein Netze lassen sich graphisch darstellen. Üblicherweise werden die Pl¨atze durch Kreise und
die Transitionen durch Quadrate oder Rechtecke dargestellt. Jede Markierung
wird als gefüllter Kreis im Platz gezeichnet. So ergibt sich für ein einfaches ErzeugerVerbraucher-System die Darstellung von Abb. 2.1.
Die sequentielle Semantik eines Netzes wird durch Ausführungen beschrieben:
Definition 2.15 Die Folge von
ausgehende sequentielle Ausführung, wenn für alle
.
mit
mit
heißt eine
gilt:
heißt Folgemarkierung von
, wenn eine von
ausgehende endliche
Ausführung existiert, die
in überführt. Die Menge der Folgemarkierungen von
wird mit bezeichnet.
"
Definition 2.16 Ein Platz eines Netzes
mit Anfangsmarkierung
n-beschränkt, wenn für alle Folgemarkierungen
gilt:
.
heißt sicher, wenn alle 1-beschr¨ankt sind.
Das Netz
"
heißt
Parallele Programme, insbesondere diejenigen, die mit FASAN beschrieben werden
können, bestehen immer aus sequentiellen Komponenten. Wenn ein solches Programm
als Netz modelliert werden soll, sind wir daran interessiert, Konflikte beim Zugriff auf
die Pl¨atze zu vermeiden. Jede sequentielle Komponente, als Transition im Netz modelliert, soll ohne Konflikte mit anderen Komponenten auf ihren Vor- und Nachbereich zum
24
Grundlagen funktionaler Programmierung und Semantik
PSfrag replacements Abbildung 2.2 Graphische Darstellung eines einfachen Prozessnetzes: Die Transitionen und
sind parallel. Die Pl¨atze und werden durch die Transition ihres gemeinsamen Nachbereichs
synchronisiert.
Zweck des Lesens und Schreibens von Datenelementen zugreifen können. Dies kann im
Modell der Netze dadurch erreicht werden, dass jeder Platz einen höchstens einelementigen Vor- und Nachbereich hat.
Definition 2.17 Ein Netz
heißt konfliktfrei, falls für alle gilt: und .
Für die Beschreibung einer parallelen Ausführung eines Netzes ist es wichtig, Puffer und
Transitionen auf ihre wechselseitige Unabh¨angigkeit vergleichen zu können.
Definition 2.18 In einem Prozessnetz
ist
eine
Transition
von einer Tran
abhängig, wenn
sition
und wenn also Zeichen (für concurrent).
. Wenn nicht von abh¨angt und nicht von ,
, so heißen und parallel oder unabhängig, in
Dieselbe Definiton kann auch auf Pl¨atze bezogen werden. Ein Schnitt durch ein
Netz ist eine maximale Menge von paarweise unabh¨angigen Pl¨atzen, wenn also für alle
, gilt: und für alle gibt es ein mit .
Definition 2.19 Ein Netz
mit Startmarkierung
heißt Prozessnetz, wenn
es konfliktfrei ist,
die transitive Hülle irreflexiv (und damit das Netz zyklenfrei) ist,
ein Schnitt ist und
alle Elemente erreichbar sind,
gilt:
d. h. wenn für alle und ein .
oder
Prozessnetze eignen sich besonders gut für die Beschreibung parallelen Verhaltens von
Systemen [Bes95, Rei98]. Dazu werden oft die Transitionen mit Aktionsnamen versehen.
2.3 Parallele Semantik mit Prozessnetzen
25
Eine parallele Ausführung wird dann durch Elemente aus dargestellt, wobei
beliebig viele paarweise parallele Transitionen aus zwischen einer Markierung
und
ihrer Folgemarkierung
stehen können.
Schließlich benötigen wir noch den Begriff der Synchronisation: Zwei Pl¨atze ) synchronisiert, weil Datenelewerden durch einen gemeinsamen Nachbereich ( mente in ihnen aufeinander warten müssen, bevor sie gemeinsam weitergeschaltet werden.
Wenn ein Programm in Form eines Prozessnetzes vorliegt, können wir damit sein paralleles Verhalten und seine Synchronisationspunkte explizit beschreiben und erkennen. Was
uns noch fehlt, ist die Transformation eines funktionalen Programms in ein Prozessnetz,
um explizit das parallele Verhalten des Programms festlegen zu können. Diese Aufgabe
stellen wir aber bis Kapitel 4 zurück und führen im n¨achsten Kapitel zuerst eine konkrete
Syntax für die Sprache FASAN ein, in der wir dann praktische Anwendungen schreiben
können.
26
Grundlagen funktionaler Programmierung und Semantik
27
Kapitel 3
Die funktionale Koordinationssprache
FASAN
Wir haben uns in Abschnitt 1.2 für die Einführung einer eigenen funktionalen Koordinationssprache namens FASAN (Functions Applied to Substructured Data in Algorithms
for Numerical Simulations) entschieden, die die Programmierung verteilter numerischer
Simulation unterstützen soll. Besonders zu berücksichtigen sind die in diesem Umfeld
entstehenden große Datenmengen, die grobgranulare Parallelisierung sowie spezielle Datenabh¨angigkeitsstrukturen, die sich aus rekursiven Beschreibungen ergeben.
Die konkrete Syntax, die im Abschnitt 3.1 eingeführt wird, ist direkt zum Programmieren in FASAN bestimmt. Ein FASAN-Programm besteht im Wesentlichen nur aus Datentypdefinitionen sowie Funktionsdefinitionen mit Fallunterscheidungen und Funktionsanwendungen. Zum genaueren Studium von Programmeigenschaften und insbesondere
zur Angabe der im n¨achsten Kapitel vorgestellten Datenfluss-Semantik ist eine abstrakte,
einfachere Syntax vorteilhaft, wie sie in Abschnitt 3.2 vorgeschlagen wird. Die Transformation von konkreter in abstrakte Syntax bildet den zweiten Teil dieses Kapitels.
3.1 Sprachbeschreibung
Obwohl es in der Informatik viele hochsprachliche Ans¨atze mit starkem Abstraktionsgrad von der Implementierungsplattform gibt, haben sie oft (noch) keinen Eingang in
praktische Anwendungen gefunden. Dies liegt nicht zuletzt daran, dass die Einarbeitung
in diese Konzepte recht mühsam und aufwendig sein kann. Um als Konzept der Informatik Aussicht auf Akzeptanz und Anwendung zu haben, ist es wichtig, möglichst wenige
und möglichst vertraute Elemente aus bekannten Verfahren, Methoden oder Systemen zu
übernehmen.
Dies wird auch hier für die konkreten Syntax von FASAN versucht, die sich vom Erscheinungsbild an die prozedurale Programmierung anlehnt, denn im Bereich des technisch-
28
Die funktionale Koordinationssprache FASAN
wissenschaftlichen Rechnens dominieren nach wie vor FORTRAN und C. Funktionsargumente werden geklammert, Ergebnisparameter von Funktionen benannt. Funktionsrümpfe sind vom Stil her eher zuweisungs- als applikationsorientiert. Des Weiteren wird auf
Pattern-matching-Mechanismen und auf die Verwendung von gleichungssystem-artigen
Rekursionsschemata zur Definition von Funktionen verzichtet. Insgesamt achten wir also
auf eine Notation, die n¨aher an prozeduralen Sprachen steht als an Notationen im Stil
von SML, Haskell oder des -Kalküls. Denkbar w¨are auch, sich streng an eine eingeschr¨ankte Java-, C- oder C++-Syntax zu halten. Der entsprechende FASAN-Compiler
müsste zus¨atzlich durch Kontextbedingungen die Eigenschaft der Einfachzuweisung an
Variablen (Single Assignment) überprüfen.
Die von FASAN direkt verwendeten Sprachkonzepte sind Funktionsdefinitionen (auch
höherer Ordnung), Datentypdefinitionen und Ausdrücke. Als Meta-Sprache verwenden
wir eine erweiterte BNF-Notation:
/
[ ]
[ ]
[ ]
[ ]
[ ] /
Alternative
oder
optionaler Teil
ein- oder mehrmalige Wiederholung von
null- oder mehrmalige Wiederholung von
[
] (Wiederholung mit Trennzeichen )
[
] , leeres Wort
Identifikatoren in FASAN entsprechen in ihrer Syntax Identifikatoren in Java [AG98] und
dürfen zus¨atzlich Apostrophe (’) enthalten. Im Folgenden verwenden wir die Nonterminale
( !
für Variablennamen,
für Funktionsnamen,
für Konstruktornamen,
für Selektornamen und
für Typnamen.
Für den Variablennamen dürfen auch numerische Konstanten eingesetzt werden (z. B.
9.81e+1), für Typnamen auch Felder (z. B. int[]). Variablen- und Typnamen sind
innerhalb einer Funktionsdefinition ab der Stelle ihres ersten Vorkommens sichtbar, alle
übrigen Namen in und nach ihrer Definition. Ein FASAN-Programm besteht dann aus
einer Sequenz von Funktions- und Typ-Deklarationen.
3.1 Sprachbeschreibung
]
.(
"
.( .( ::= [
29
Weiterhin kann ein FASAN-Programm zus¨atzlichen Leerraum (Tabulatoren, Leer- und
Neue-Zeile-Zeichen) sowie Kommentare (zwischen /* */ oder zwischen // und Zeilenende) enthalten.
3.1.1 Externe Funktionen und externe Typen
Mit externen Funktionen werden sequentielle Funktionen aus der Hostsprache, also aus
externen Programmbibliotheken, Klassen- oder Objektdateien eingebunden. Die Signatur
einer solchen externen Funktion wird im FASAN-Programm vereinbart durch:
::=
(
[ [ ]
] [ -> [ [ ] ] ]
extern [ Java C C++ FORTRAN ]
Da wir FASAN in Java übersetzen werden, können Java-Methoden ohne weitere
Maßnahmen aufgerufen werden. Für C, C++ und FORTRAN ist eine Schnittstelle mit JNI (Java Native Interface, [Sun98]) zu schreiben. Diese Aufgabe könnte ein
Interface-Generator automatisieren, der für C++-Objekte und C-Strukturen geeignete
Java-Schnittstellenobjekte erzeugt. Mit anderen Sprachen wie Pascal und SML gibt es
Kompatibilit¨atsprobleme mit den verschiedenen Laufzeitsystemen.
Für die Verwendung von Argumenten externer Funktionen in der Hostsprache ist zu beachten, dass in Java, C und C++ Argumente, die nicht von Grundtypen 1 sind, und in
FORTRAN alle Argumente durch Referenz an die aufgerufene Methode übergeben werden. Dadurch ist deren Wert modifizierbar. FASAN ist aber eine funktionale Koordinationssprache, und funktionales Verhalten wird auch von den externen Funktionen gefordert.
Die eingebundene Funktion darf in der Regel auf keinen Speicherbereich, für den ein Argumentparameter eine Referenz liefert, schreiben.
Ist die Referenz die einzige im momentanen Programmablauf, dann bleibt ein Beschreiben des zugehörigen Speicherbereichs ohne Seiteneffekte. Soll der so modifizierte Inhalt über das FASAN-Programm in weiteren Funktionen verwendet werden, muss für ihn
1
Das sind in Java die Typen boolean,byte,char,short,int,long,float,double.
30
Die funktionale Koordinationssprache FASAN
auch ein Ergebnisparameter vorgesehen sein, sonst existiert der Wert anschließend aus
Sicht des FASAN-Programms nicht mehr. Diese Wiederverwendung der Objekte ist eine
leichte Effizienzsteigerung, weil die Neu-Allokation und die sp¨atere Entfernung des alten Objekts durch die Speicherverwaltung entfallen. Als Nachteil ist aber der Verlust der
referentiellen Transparenz dieses Objekts zu berücksichtigen.
Als einfache und nützliche externe Funktionen werden
die arithmetischen Operatoren +,-,*,/, % (modulo),
die artihmetischen Vergleichsoperatoren ==, <, >, <=, >= sowie
die booleschen Operatoren && (and), || (or), ! (not)
vordefiniert übernommen und können direkt in FASAN-Programmen in InfixSchreibweise verwendet werden.
Vordefiniert sind auch die Java-Grundtypen und Arrays dieser Grundtypen. Weitere Typen
können, analog zu externen Funktionen, aus den Hostsprachen importiert und als solche
durch Deklaration im FASAN-Programm verfügbar gemacht werden:
::= extern Java [ isa [ ! ] ]
.(
Der Zusatz isa“ kennzeichnet den Typ als Spezialisierung der Typen ! in der Ver”
erbungshierarchie der Hostsprache (bisher nur für Java unterstützt). Ein Argument spezielleren Typs darf für einen formalen Parameter allgemeineren Typs eingesetzt werden
oder an eine Variable allgemeineren Typs zugewiesen werden. Tritt kein solcher Fall im
FASAN-Programm auf, kann der Zusatz entfallen.
Beispiel 3.1 Eine oft benötigte Datenstruktur ist die einer dünnbesiedelten Matrix, deren
konkrete Struktur aber im FASAN-Programm verborgen bleibt:
type SMat extern Java
Als externe Funktion kann ein SOR-Gleichungslöser nützlich sein:
function sor(A:SMat; b:double[])->x:double[] extern Java
Um die prototypische Implementierung einfach zu halten, sind in FASAN keine in
Java üblichen Type-Casts erlaubt; nur die Vererbungshierarchie wird durch die isaAnnotation externer Funktionen berücksichtigt. Auch auf weitergehende softwaretechnische Konzepte wie Typvariablen (parametrischen Polymorphismus wie in Pizza
[OW97b]) wird hier der Einfachheit halber verzichtet. Auf unser Ziel der verteilten Berechnung würden diese Konzepte keinen Einfluss haben.
Der FASAN-Compiler akzeptiert auch ein Programm, in dem alle externen Typdeklarationen von Variablen weggelassen werden und stattdessen eine (austauschbare) Konfigurationsdatei mit den Signaturen der Funktionen bereitgestellt wird. Damit kann ein
FASAN-Programm auch unabh¨angig von einer Hostsprache geschrieben werden.
3.1 Sprachbeschreibung
31
3.1.2 Rekursive Funktionen
Als rekursive Funktionen bezeichnen wir hier diejenigen Funktionen, die in FASAN direkt programmiert sind, im Unterschied zu den externen Funktionen.
::= [ [ ] ] [ -> [ [ ] ] ]
! ]
::= [
(
Eine Zuweisung kann eine Wertzuweisung an eine oder mehrere Variablen sein, wobei
die rechte Seite der Zuweisung wieder aus einer Variablen oder einem Funktionsaufruf
besteht und beliebig geschachtelt sein kann:
::= [ [ ] ] ::= ::= [ ]
s muss gleich viele ErgebDer Funktionsaufruf auf der rechten Seite eines
nisparameter haben wie auf der linken Seite Variablen stehen. Steht auf der rechten Seite
der Aufruf einer Funktion ohne Ergebnis, so entf¨allt die linke Seite.
(
Ein Funktionsaufruf belegt alle Variablen auf der linken Seite der Zuweisung. Eine Variable ist belegt, wenn sie formaler Argumentparameter einer Funktionsdeklaration ist.
Alle auf der rechten Seite verwendeten Variablen werden gelesen. Für die Anordnung
von Funktionsaufrufen im Block einer Funktionsdeklaration muss gelten:
Eine Variable darf nur einmal belegt werden.
Eine Variable darf erst gelesen werden, nachdem sie belegt wurde.
Eine Variable darf beliebig oft gelesen werden.
Zuweisungsfolgen können von einem boolschen Ausdruck abh¨angig gemacht werden:
!
::= Beispiel 3.2 Zur Verdeutlichung der FASAN-Syntax zeigen wir einen möglichen ¨außeren Rahmen für eine Simulation, die nach jeder Berechnung step einer diskreten
Lösung x’ prüft, ob die gewünschte Genauigkeit erreicht ist:
type Domain extern Java;
type Solution extern Java;
function solve(x:Solution; omega:Domain)-> x’:Solution { }
function simulationStep(omega:Domain;x:Solution;iter:int)
-> x’:Solution
{
x_ = solve (x, omega);
if accuracyReached (x_) then
x’ = x_;
else
omega_ = refine (omega);
x’ = simulationStep (omega_, x_, iter+1);
fi
}
32
Die funktionale Koordinationssprache FASAN
3.1.3 Hierarchische Datentypen
Hierarchische Datentypen2 bilden den Kern von FASAN. Im Kapitel 4 werden wir sehen, wie sie sich von herkömmlichen Datentypen unterscheiden: FASAN-Datentypen
synchronisieren n¨amlich ihre Komponenten nicht. Hierarchische Datentypen werden in
einer Typdeklaration vereinbart:
.(
"
::= [ ] ::= [ ] !
::=
[
]
Hierarchische Datentypen können für ihre Komponenten sowohl Grund- und Referenztypen der Hostsprache als auch hierarchische Datentypen ! verwenden. Hierarchische
Datentypdefinitionen dürfen sich auf Typen beziehen, die entweder textuell weiter oben
deklariert sind, in der aktuellen Deklaration festgelegt oder in einer textuell folgenden
Deklaration vereinbart werden (verschr¨ankte Rekursion). Funktionstypen, die in Abschnitt 3.1.6 eingeführt werden, schließen wir als Teil hierarchischer Datentypen aus.
Die Identifikatoren für Konstruktoren und für Selektoren werden durch eine Datentypdefinition vereinbart und müssen voneinander und von allen anderen Funktions- und
Typsymbolen verschieden sein.
Außerdem wird für jeden Konstruktor eines hierarchischen Datentyps auch ein Pr¨adikat
is erzeugt, das prüft, ob das Argument mit dem Konstruktor aufgebaut worden ist.
Ein Konstruktoraufruf ist als Abkürzung auch auf der linken Seite zugelassen: Er baut
dort aber keine neue Datenstruktur auf, sondern dient der Zerlegung der Datenstruktur
auf der rechten Seite und der Zuweisung von Werten an seine Argumente. Wenn also ein
Konstruktor für einen hierarchischen Typ die Selektoren als Argumente hat,
dann ist ein Aufruf
( ( als syntaktische Abkürzung3 zu sehen für
( ( In Anlehnung an die Java-Schreibweise dürfen Selektoraufrufe auch nachgestellt werden,
also etwa gleichbedeutend mit .
2
Hierarchische Datentypen wurden in vorigen Arbeiten als Kabelbäume“ oder wrapper streams“ be”
”
zeichnet.
3
Im ursprünglichen FASAN-System, also in den früheren Publikationen [Ebn94, EPZ96, EP96, Pfa97],
wurde anstatt eines Konstruktors der -Operator verwendet. Auf der linken Seite einer Zuweisung diente
er als Selektor-Operator für alle Komponenten, ebenso wie hier die Konstruktoren.
3.1 Sprachbeschreibung
33
Beispiel 3.3 Hier zeigen wir einige gültige Anwendungen von Konstruktoren und Selektoren für einen hierarchischen Datentyp Tree am Beispiel einer Divide&ConquerFunktion dc:
type Node extern Java
type Tree = Branch (val:Node; left,right:Tree) | Nil()
function down (n, parent: Node) -> n’:Node extern Java
function up (n, son1, son2: Node) -> n’:Node extern Java
function dc (t:Tree) -> t’:Tree
{ if isNil(t) then
t’ = Nil();
else
Branch(v,l,r) = t; // Zerlegung von t
// in Komponenten v,l,r
v0 = down(v);
// Funktion bei Abstieg im Baum
l1 = dc(l,v0);
// Rekursion linker Teilbaum
r1 = dc(r,v0);
// Rekursion rechter Teilbaum
v1 = up(v0, val(l1), r1.val); //Funktion bei Aufstieg
t’ = Branch (v1,l1,r1);
fi
}
3.1.4 Endrekursion
In funktionalen Programmiersprachen ist es üblich, einen rekursiven Aufruf, der als syntaktisch ¨außerster Aufruf in einem Ausdruck den Rückgabewert einer Funktion bestimmt,
in eine Iteration umzuformen. Ein solcher Aufruf heißt Endrekursion. Der Aufruf kann
für seine Argumente, seine lokalen Variablen und seine Rückgabewerte denselben StackBereich wie der Aufrufer verwenden und damit Speicher sparen.
Beispiel 3.4 Erinnern wir uns nochmals an das Beispiel 1.1 auf Seite 2, in dem günstigerweise mehrere Inkarnationen der Funktion wavefront nebenl¨aufig ausgeführt
werden. Hier könnte einerseits der rekursive Aufruf bzgl. Speicherplatz optimiert
werden. Andererseits bedeutet die Speicheroptimierung der Endrekursion aber eine
Sequentialisierung der Iterationen, weil zu jedem Zeitpunkt nur Speicher für einen
Aufrufkontext auf dem Stack verfügbar ist.
Deshalb wird es dem Programmierer überlassen, sich für Speicheroptimierung (also Ausnutzen der Endrekursion) oder für Laufzeitoptimierung im parallelen Fall durch Ausnutzung der entstehenden Pipeline zu entscheiden.
Der erste Fall, also die Variante der Speicheroptimierung durch Auflösung in eine Iteration, wird in FASAN mit dem Schlüsselwort recycle vor dem endrekursiven Aufruf
34
Die funktionale Koordinationssprache FASAN
besonders gekennzeichnet.
::= recycle
Beispiel 3.5 Eine typische Anwendung von Endrekursion in FASAN ist die Iteration über
eine Divide&Conquer-Funktion:
function iterate (t:Tree) -> t’:Tree
{
if stop(t.val) then
t’ = t;
else
t0 = dc(t);
t’ = recycle iterate(t0);
fi
}
Nachdem die Parallelit¨at innerhalb der Funktion dc erzeugt wird, ist es hier sinnvoll,
recycle zu verwenden und dadurch Speicherplatz zu sparen.
3.1.5 Lokationen zur verteilten Berechnung
Funktionsaufrufe sind die Grundlage für Berechnungen in funktionalen Programmen.
Deshalb liegt es nahe, sie als parallele Prozesse aufzufassen. Dies ist auch aus der Sicht
des Programmierers eine intuitive Sichtweise. Eine mögliche parallele Berechnung eines
Funktionsaufrufs wird durch Annotation mit einer sogenannten Lokation angegeben. Der
Wert einer Lokation bezeichnet die Nummer des Zielrechners, auf dem die Auswertung
des Aufrufs erfolgen soll. Lokationen werden durch Ausdrücke berechnet oder einfach
durch den Wert einer Variable vorgegeben:
::=
[ on on
]
Der Wert der Variablen bzw. das Ergebnis der zweiten Funktionsanwendung muss einen
int-Wert im Bereich von
bis numHosts sein. Die vordefinierte Variable numHosts
ist die Anzahl der im Rechnernetz insgesamt für das FASAN-Programm zur Verfügung
stehenden Rechner. Eine zweite vordefinierte Variable ist myHost, die die Nummer des
Rechners liefert, auf dem der Lokationsaufruf stattfindet.
Ein Wert der Lokation kleiner 0 bedeutet die nicht-parallele Bearbeitung im selben Kontrollfluss. Ist der Wert gleich der Nummer des aktuellen Rechners, so ist dies als Möglichkeit zur Erzeugung eines neuen nebenl¨aufigen Kontrollflusses — im Sinne eines Threads
— zu verstehen.
Beispiel 3.6 Ein Lastverteilungsfunktion, die einen Funktionsaufruf auf den Rechner
mit der n¨achsthöheren Nummer verlagert, etwa in einem ringförmigen Rechnernetz,
kann direkt in FASAN codiert werden:
3.1 Sprachbeschreibung
35
function next_proc() -> loc:int
{
loc = (myHost + 1) % numHosts;
}
Lokationen sind eine konzeptionell einfache, aber sehr flexible Möglichkeit zur Lastverteilung. Mit der Angabe einer Lokationsfunktion, die zur Laufzeit einen Rechner bestimmt, auf dem der Aufruf erfolgen soll, ist auch dynamische Lastverteilung möglich.
Beispiel 3.7 Ein Master-Programm produziert laufend neue Auftr¨age, die auf SlaveProgramme verteilt werden sollen, etwa beim Ray-Tracing. Der Master kann hierfür
eine Warteschlange verwalten, in der die zur Zeit nicht oder wenig belasteten Rechner stehen. Die beiden zugehörigen Lokationsfunktionen sehen, als externe Funktionen in Java geschrieben, folgendermaßen aus:
java.util.LinkedList lowLoadHosts;
int MAX_LOAD = 4;
int getFreeHost() {
int host = lowLoadHosts.getFirst();
if (load(host) < MAX_LOAD)
lowLoadHosts.addLast(new Integer(host));
return host;
}
void setFreeHost (int host) {
if (load(host) < MAX_LOAD)
lowLoadHosts.addLast(new Integer(host));
}
Die Funktion im FASAN-Programm zum Start eines neuen Auftrags
function rayTraceTask (x:...) -> y:...; targetHost:int
{
y = rayTrace(x);
targetHost = myHost;
}
wird dann beim Aufruf im Master-Programm mit einer Lokation versehen:
result, targetHost = rayTraceTask (...) on getFreeHost();
setFreeHost (targetHost);
Im Ergebnisparameter von rayTraceTask wird dabei vermerkt, auf welchem
Host der Auftrag gerechnet worden ist. Dazu setzt rayTraceTask einfach den
Wert von myHost (den Zielrechner) in das Ergebnis ein.
Lokationsfunktionen sind auch für komplizierte Verteilungen der Datenstrukturen bzw.
der sie bearbeitenden Funktionen geeignet. Ein Beispiel, das die Einbettung einer DünnGitter-Datenstruktur in eine Dualhyperwürfel-Topologie leistet, findet sich in [Pfa97].
36
Die funktionale Koordinationssprache FASAN
Lokationen können als modulares Konzept zur Lastverteilung gesehen werden. Ihre Implementierung kann unabh¨angig vom FASAN-Programm ausgetauscht werden, etwa für
eine neue Rechnertopologie oder zur Integration dynamischer Lastverteilungsverfahren.
Stellt die Lastverteilungskomponente die obere Schicht im System dar, etwa das marktwirtschaftlich orientierte Lastbalancierungssystem DYNASTY [BPZ96, Bac98], so ist es
sinnvoll, die Lokationsergebnisse auf zwei Werte zu beschr¨anken: 0 bedeutet dann die
Generierung eines neuen parallel bearbeitbaren Auftrags (eine split-Operation eines Teilproblems), und
die Weiterführung im selben Kontrollfluss. Letzteres bedeutet einen
merge-Vorgang, falls in vorigen Iterationen entsprechende Funktionsaufrufe für die Teilprobleme noch parallel abgearbeitet wurden.
Beispiel 3.8 Für Divide&Conquer-Algorithmen können wir solche zweiwertigen Lokationen auf folgende Weise in des FASAN-Programm integrieren. Das Lastverteilungssystem ruft dabei die Funktion mit einer Variablen needsSplit auf, die angibt, dass rekursive Aufrufe abgespalten und dem System zur Neuplatzierung zur
Verfügung gestellt werden:
function dc (t:Tree; needsSplit:boolean) -> t’:Tree
{
if isNil(t) then
t’ = Nil();
else
if needsSplit then loc = 0; else loc = -1; fi
Branch(v,l,r) = t;
v0 = down(v);
l1 = dc(l) on loc;
r1 = dc(r) on loc;
v1 = up(v0, l1.val, r1.val);
t’ = Branch(v1,l1,r1);
fi
}
Bei den rekursiven Aufrufen von dc mit Lokation größer legt das FASANSystem zwei neue Tasks an, die dann vom Lastverteilungssystem weiterverwaltet
werden können.
Dieses Beschr¨ankung auf die Werte 0 und
kann auch für alle anderen Zielsysteme verwendet werden, die die Verteilung paralleler Teilberechnungen selbst organisieren, also
z. B. für POSIX-Threads auf SMP-Maschinen.
3.1.6 Funktionen h¨
oherer Ordnung
FASAN unterstützt Funktionen höherer Ordnung, weil damit Rekursionsmuster oft noch
kürzer und pr¨agnanter ausgedrückt werden können. Es gibt auch eine Reihe m¨achtiger,
meist datentypbezogener Theorien wie den Bird-Meertens-Formalismus (BMF, [Bir88]),
der beispielsweise allgemeine Listenoperationen wie map, scan und fold definiert. Auch
3.1 Sprachbeschreibung
37
für numerische Divide&Conquer-Algorithmen wurde der BMF schon erfolgreich verwendet [GL95].
Für Funktionen höherer Ordnung können wir in FASAN entsprechende Funktionstypen
definieren:
.(
( .( .(
-> ::= ::=
[ ]
Diese Definition von Funktionstypen vermeidet die syntaktische Verschachtelung. Um
einen Funktionstyp -ter Ordnung zu definieren, müssen bereits Namen für die benötigten
Typen der Ordnung
s vereinbart worden sein.
, durch
.(
Mit Funktionstypen kann eine Variable dann auch eine Funktion bezeichnen und auf Argumente angewendet werden:
(
::=
[
] Beispiel 3.9 Eine oft benötigte Funktion 2. Ordnung auf Listen ist die Funktion map. Sie
wendet ihr erstes (Funktions-)Argument auf alle Elemente ihres zweiten (Listen-)
Arguments an; hier wird die Liste von B¨aumen (ein Wald) verwendet:
type Forest = empty() | forest (first:Tree; rest:Forest)
function map (f:Tree->Tree; w:Forest)-> w’:Forest
{
if isempty(w) then
w’ = empty();
else
forest(x,rest) = w;
w’ = forest(f(x), map(f,rest));
fi
}
Auch Ergebnisse von Funktionsaufrufen können wieder von einem Funktionstyp sein,
wenn eine oder mehrere Variablen ausgelassen und syntaktisch durch einen Unterstrich
ersetzt werden:
::= (
[
[
]
]
Ein oder mehrere Unterstriche als Funktionsargumente bewirken eine partielle Funktionsanwendung (Currying). Dadurch wird ein Abschluss (engl. closure), also wieder eine
Funktion, als Ergebnis erzeugt [Thi94]. Diese neue Funktion hat dann so viele Parameter
wie Unterstriche im Aufruf stehen, mit den gleichen Typen der entsprechenden Parameterstellen. Solche Ausdrücke können nicht mit Lokationen versehen werden, weil sie selbst
noch keine Berechnungen anstoßen.
38
Die funktionale Koordinationssprache FASAN
Beispiel 3.10 Wir gehen von einem hierarchischen Datentyp für B¨aume mit beliebigem
Verzweigungsgrad aus:
type Tree
=
|
type Forest =
|
leaf (val:Object)
branch (node:Object; sons:Forest)
empty()
forest (first:Tree; rest:Forest)
Eine Funktion höherer Ordnung, die den Baum durchl¨auft und dabei Ergebniswerte
für die Berechnungen in den Söhnen nach unten transportiert, zeigt das folgende
Programm:
function
mapDown (f:(Object,Object)->Object; s:Object; t:Tree)
-> t’:Tree
{
if isleaf(t) then
t’ = leaf (f(s,t.val));
else
branch(x,sons) = t; // sons:Forest
y = f(s,x);
ff = mapDown(f,y,_); // ff:Tree->Tree
ys = map (ff, sons); // ys:Forest
t’ = branch(y,ys);
fi
}
Auch in Funktionen höherer Ordnung können wir Ausdrücke mit Lokationen versehen,
wodurch wir dann Skeletons im Sinn von Cole [Col98] und Skillikorn [Ski95] erhalten.
Skeletons sind parallele Berechnungsrahmen für bestimmte hierarchische Datenstrukturen, die universell für Funktionen einsetzbar sind, die auf Elementen dieser Datenstrukturen arbeiten.
Beispiel 3.11 Durch Annotation der map-Funktion aus Beispiel 3.10 mit einer Lokation
wird automatisch auch mapDown ein paralleles Skeleton:
function map (f:Tree->Tree; w:Forest) -> w’:Forest
{
if isempty(w) then
w’ = empty();
else
y = f(w.first) on getFreeHost();
w’ = forest (y, map(f, w.rest));
fi
}
Funktionen höherer Ordnung sind darüber hinaus für numerische Berechnungen mit
mehrdimensionalen Funktionen vorteilhaft, wenn nur der Wert für eine Dimension feststeht. Hier ein komplizierteres, aber genau nach der mathematischen Spezifikation aus
[Bon95, S. 7ff] eins zu eins übernommenes Beispiel.
3.2 Eine abstrakte Syntax
39
Beispiel 3.12 Bei der Quadratur nach Archimedes wird das Integral S2 eines zweidimensionalen Körpers unter anderem auf einen Körper mit Inhalt F1 abgestützt, der sich
aus eindimensionalen Segmenten S1 zusammensetzt. Diese Segmente wiederum setzen sich aus einbeschriebenen Dreiecken mit Integral D2 zusammen:
type fun1D = double->double
type fun2D = (double,double)->double
function D2 (f:fun2D; x,a,b:double) -> y:double
{
y = 0.5 * (b-a)
* ( f(x,(a+b)*0.5) - 0.5*(f(x,a) + f(x,b)) );
}
function F1 (f:fun1D; a2, b2:double) -> y:double
{
y = 0.5 * (f(a)+f(b)) * (b-a) + S1(f(_), a, b);
}
function S2 (f:fun2D; a1,a2,b1,b2:double) -> i:double
{
i = F1 (D2(f,_,a2,b2), a1, b1) + ...;
}
Damit haben wir bereits den Sprachumfang von FASAN festgelegt. Rein syntaktisch bietet FASAN nur durch die Lokationsannotation von Funktionsaufrufen Neues. Die wesentliche Eigenschaft der hierarchischen Datenstrukturen, dass Komponenten erst bei einem
Selektoraufruf und dann direkt von der Lokation des Konstruktoraufrufs nachgezogen
werden, ist für den Programmierer einerseits leicht informell nachvollziehbar, ohne dass
er es andererseits explizit programmieren muss. Wie dieses Kabelbaum-Prinzip genau zu
verstehen ist, legen wir erst im folgenden Kapitel über die operationelle parallele Semantik von FASAN fest. Was wir als Vorbereitung auf syntaktischer Ebene dazu beitragen
können, ist eine auf die Semantik abgestimmte abstrakte Syntax sowie die Übersetzung
der FASAN-Syntax in diese abstrakte Notation.
3.2 Eine abstrakte Syntax
Wie in Kapitel 2 erw¨ahnt, wird für funktionale Programme üblicherweise eine abstrakte
Syntax gew¨ahlt, die sich wegen syntaktischer Vereinfachungen für die Definition der Semantik besser eignet als die konkrete Syntax. Wir werden die abstrakte Syntax für FASAN
auf die im n¨achsten Kapitel eingeführte Datenfluss-Semantik ausrichten.
Unser Ziel ist, zu einem gegebenen FASAN-Programm eine Signatur
zu erzeugen, die das Programm auf abstrakterer Ebene widerspiegelt.
Die rekursiven Funktionen von FASAN beschreiben eine rekursive Programmdefinition,
die zusammen mit einer -Algebra der externen Funktion und Typen zu einem rekursiven Programm wird, für das wir durch das Regelsystem (2.1) bis (2.4) unmittelbar eine
operationelle Semantik erhalten.
( 40
Die funktionale Koordinationssprache FASAN
Wir teilen zun¨achst die Funktionssymbole in disjunkte Mengen ein:
in
die Konstruktorsymbole,
in die Selektorsymbole,
in
die durch externe Funktionsdeklarationen vereinbarten Funktionssymbole
sowie Pr¨adikate und
in die Namen der rekursiven Formeln.
Für Elemente dieser Mengen werden wir wieder feste Bezeichner verwenden, die wir
nach Bedarf mit Indizes versehen, um auf sie Bezug nehmen zu können. Es seien also
, ,
,
,
und .
( Im Einzelnen wird dann ein FASAN-Programm in folgende syntaktische Kategorien aufgeteilt, die dem Aufbau eines abstrakten Syntaxbaums entsprechen: 4
::= ::= !
::= ::= !
( (
! ( #%$ &)( &)*"+ &( / (
* & # ( ( Ein Programm
( in abstrakter Syntax unterscheidet sich syntaktisch von unserer Festlegung einer rekursiven Programmdefinition (Def. 2.9) dadurch, dass keine geschachtelten
Funktionsaufrufe (Ausdrücke) möglich sind, sondern in eine Sequenz von Zuweisungen
transformiert werden müssen. Weiterhin müssen die Zweige einer Bedingung jeweils in
eigene Funktionen und (mit denselben Argument- und Ergebnisparametern) gefasst
werden. Die Anwendung eines Abschlusses auf weitere Argumente wird explizit durch
das Funktionssymbol gekennzeichnet.
/
Zum Zweck einer einfachen Aufschreibung verwenden wir darüber hinaus einen generischen Operator , der folgende Verknüpfungen leistet:
für Sequenzen für Mengen für Tupel und
(
(
( (
(
( ( (
(
(
Für dieÜbertragung in abstrakte Syntax setzen wir noch voraus, dass Zeichenketten zu
Schlüsselwörtern und Bezeichnern zusammengefasst sind, und dass Trennzeichen und
Kommentare entfernt sind. Weiterhin muss das Programm gem¨aß der konkreten Syntax
zerteilt sein, also als abstrakter Syntaxbaum vorliegen. Schließlich müssen Wiederholungen, die sich aus Syntaxregeln der Form [ ] und [ ] ergeben, in Sequenzen
umgeformt sein, und zwar
14
Der Stern bedeutet Sequenzen, siehe Seite 19. In dieser Schreibweise ist ein Bezeichner für eine
endliche Sequenz von Variablen.
4
3.2 Eine abstrakte Syntax
41
! "
Argument- und Ergebnislisten als ,
Typlisten
als
,
Listen formaler Parameter gleichen Typs als Listen von Selektoren gleichen Typs
als
! "
"
!
! " .
und
Die Typisierung von Variablen und Funktionen vermerken wir bei Bedarf in der abstrakten
Syntax durch hochgestellte Indizes.
Wir wollen nun im Detail die Übertragung eines FASAN-Programms von konkreter Syntax in ein ¨aquivalentes Programm in abstrakter Syntax angeben.
# ! .( # ! ! ! "*! / # ! ! / falls ! # !
! wobei !
!$" # ! !/ falls
# ! ! wobei
# ! !/ falls ! .( !$"
# ! wobei
!"# !
.( falls !
!$"
# ! !/
# ! ! "# ! wobei
.( falls !
!$"
# ! ! /
# !
"# ! wobei
!
Der Übersicht halber indizieren wir die Abbildung # ! mit den syntaktischen Kate
gorien, die jeweils übersetzt werden sollen . Abgekürzte Namen wie oder
5
deuten immer auf die abstrakte Syntax hin, im Gegensatz zu den ausgeschriebenen Namen
der konkreten Syntax.
3.2.1 Rekursive Funktionen
Eine rekursive Funktion ergibt in der abstrakten Syntax eine Sequenz von Funktionen,
welche aus den Zweigen von Fallunterscheidungen entstehen:
# ! # ! function ->( ! * & # "( ( "*! / / %'
Wir sprechen bei $&%(')+*-, ,.0/ / von einer Abbildung im mathematischen Sinn, im Gegensatz zu Funktionen
aus der Menge 2143 , die genauer Funktionssymbole sind.
5
42
Die funktionale Koordinationssprache FASAN
wobei
00 / ( # ! ! /
Im Block eines Funktionsrumpfs steht eine Sequenz von Anweisungen und bedingten
Anweisungen; die aus Zweigen von bedingten Anweisungen entstehenden Funktionen
werden im zweiten Ergebnis folgender Abbildung zusammengefasst:
# !
%
%'
!$" !$" !$"
# ! ! "* / -
# ! / # !
!$" # ! -
# ! / # !
falls
falls
! Geschachtelte Funktionsaufrufe in Zuweisungen ersetzen wir durch eine Sequenz von
nichtgeschachtelten Zuweisungen:
# ! ( ! ( "
# ! # ! wobei
" Entsprechend erhalten wir aus einem Ausdruck eine Sequenz von Zuweisungen:
# ! " % # ! " !$" !$" !$" # ! " ! "* / # ! " # ! " / # !
Bei einem Ausdruck ist zwischen Variablen, Funktionsaufrufen und Funktionsaufrufen
mit Lokation zu unterscheiden.
" " % # !
!$" ! "
# ! " ! " # ! " wobei
neue Variablensymbole
Ein Funktionsaufruf mit Lokation ist ebenso einfach zu übertragen:
# ! " on / / ! ( / " # ! " wobei
/ / # ! / ! "
" neue Variablensymbole
# !
3.2 Eine abstrakte Syntax
43
Wird eine Variable auf Argumente angewandt, so enth¨alt diese Variable einen Abschluss.
Eine solcher Funktionsaufruf, bei dem die Parameter aus dem Abschluss und die aktuellen
Argumente zusammengefügt werden müssen, behandeln wir explizit durch die Funktion
.
# !
( ! # ! " neue
Variablensymbole
" wobei
( " Auch die Anwendung einer Funktionsvariable auf Argumente darf mit einer Lokation
annotiert werden:
# !
/ ! ( on /
# ! " / / # ! / ! "
" neue Variablensymbole
" wobei
( ( / " Für bedingte Zuweisungen werden in jedem Fall zwei neue rekursive Funktionen erzeugt,
die die beiden Zweige der Fallunterscheidung beinhalten:
# ! -
! % %'
# ! -
if then else / fi !( ! #% $ & ( &) *+ & / " * & # "(
* & # "(
! (# (
( ( #( / ( ( ( ! ! / " / # ! wobei
" # ! !
# ! / / ! / / () 0 0 0 0 / 0 0 + / ( Hier haben wir die Abbildungen lhs und rhs verwendet, die die linke bzw. rechte Seite
von Zuweisungen liefern:
0 !" !$"
! "* - 0 0 !$" !$"
! "* ! "* 0 44
Die funktionale Koordinationssprache FASAN
3.2.2 Hierarchische Datentypen
Die Definition eines neuen hierarchischen Datentyps führt neben dem neuen Typsymbol
auch neue Konstruktoren in
und Selektoren in sowie Pr¨adikate in
ein.
" # !
( .( %' '% # ! " # ! wobei
%'
Im Einzelnen führen wir mit der durch einen Konstruktor beschriebenen Variante des
hierarchischen Datentyps ebendieses neue Konstruktorsymbol ein. Gleichzeitig wird ein
Pr¨adikat
neu vereinbart:
# "+ %.(
# ! %' %
# ! # !" # ! # ! "* / "+ " # ! # # ! wobei
Für jede Selektorfunktion sind zus¨atzliche Attribute zu speichern, die außer der Typisierung noch angeben, für welche Konstruktorvariante sie bestimmt ist und die wievielte
Komponente der entsprechenden Datenstruktur sie ausw¨ahlt.
# !
# !
% %.( %
.( % !" !$"
! "- / # ! # !
! /" wobei
Die beiden wesentlichen Sprachelemente von FASAN — hierarchische Datentypen und
rekursive Funktionen — können wir nun in abstrakte Syntax übersetzen. Dazu wollen wir
ein Beispiel angeben.
Beispiel 3.13 Wir w¨ahlen die Funktion map aus Beispiel 3.10, die in abstrakter Syntax
folgendermaßen geschrieben wird:
! "#$% $
'&
) $* ) +,$- "#/ $
,.
10
&
2-!3* "#14 ($/
.
0
&
5 6 5 - /
&
7&
8 & ($ '& 8 & ($ '& /
($
3.2 Eine abstrakte Syntax
8 -
45
" 3 8
8 " 8 / " + - / - 3 * "!$# " - " /
"#
/
%
8 / " " 3.2.3 Externe Funktionen, externe Typen und Funktionstypen
Die drei noch nicht behandelten Varianten von Definitionen in FASAN führen nur neue
Symbole ein, für die derÜbersetzer lediglich überprüfen muss, ob sie nicht bereits anderweitig deklariert sind und ob sie ihrer Typisierung entsprechend richtig verwendet werden.
Eine externe Funktion legt also nur das neue Funktionssymbol fest, ohne weitere Effekte
in der abstrakten Syntax:
# ! function -> ( extern & 0 wobei / 0 (
# !
/
Externe Typdefinitionen erweitern unseren Vorrat an Typsymbolen:
" .( .(
# ! " type extern & # !
Für die Typprüfung muss derÜbersetzer eine partielle Ordnung auf den Typsymbolen
mitführen, die die Vererbungshierarchie angibt. Für eine Typdefinition der Form
" type extern & isa / muss dann in der darunterliegenden -Algebra der externen Typen und Funktionen gel für alle /
ten:
# !
46
Die funktionale Koordinationssprache FASAN
Für denÜbersetzer bedeutet das, dass er als ersten Schritt alle Funktionstyp-Symbole in
Parameter- und Variablenlisten durch ihre Langform ersetzt.
Der Übersetzer hat sicherzustellen, dass es keine Namenskonflikte von Funktionstypen
und anderen Symbolen gibt. In die Signatur des FASAN-Programms nehmen wir Funktionssymbole, die für Typausdrücke aus
stehen, nicht auf. Wegen der Abstützung
von FASAN auf Hostsprachen wie Java, die selbst keine Funktionstypen kennen, können
Funktionstypen höherer Ordnung nur in FASAN selbst definiert werden. Deshalb benötigen wir die Symbole nicht in der Signatur; die Semantik entsprechender Funktionen wird
komplett durch die Datenflussgraphen erfasst. Auf die Typ-Prüfung in FASAN wollen wir
jedoch nicht weiter eingehen, weil sie eine Hilfe bei der Programmentwicklung ist, aber
zu unserem Ziel der Parallelisierung keinen Beitrag leistet.
Das folgende Kapitel wird nun eine operationelle Semantik für FASAN-Programme in
abstrakter Syntax angeben. Diese Semantik dient vor allem dazu, das Verhalten von
FASAN-Programmen bei verteilter Ausführung beschreiben zu können. Die besondere
Behandlung hierarchischer Datentypen, die ihre Komponenten nicht synchronisieren und
für direkte Kommunikationswege sorgen, werden im Vordergrund stehen.
47
Kapitel 4
Operationelle Semantik von FASAN
Das Verhalten eines FASAN-Programms ist durch die informelle Sprachbeschreibung im
vorigen Kapitel intuitiv vermittelt worden. Gerade für eine neue Sprache ist es aber wichtig, formal und exakt festzulegen, wie ihre Programme ablaufen. Das bedeutet allerdings
nicht, dass eine Implementierung schon fest vorgegeben sein muss. Die Semantik einer
Sprache legt ihre wesentlichen Eigenschaften fest, also neben den determinierten Ergebnissen einer Berechnung auch mehr oder weniger feste Vorgaben für den Verlauf der Berechnung. Unser Ziel ist eine Semantik, die einerseits möglichst viel Spielraum zur Ausnutzung von Parallelit¨at l¨asst, aber andererseits festlegt, wo die (möglicherweise großen)
Datenmengen liegen und wohin sie kopiert werden.
Für dieses Ziel führen wir Datenflussgraphen als spezielle Prozessnetze ein (Abschnitt 4.1). Sodann legen wir in Abschnitt 4.2 fest, welcher Datenflussgraph einer rekursiven Funktion statisch zugeordnet werden kann. Den tats¨achlichen Berechnungsvorgang
in einem FASAN-Programm beschreiben wir in Abschnitt 4.3, wo wir Daten — in Form
von Belegungen des Datenflussgraphen — über Funktionsknoten weiterschalten und den
Datenflussgraphen dynamisch verfeinern können. Am Ende des Kapitels in Abschnitt 4.4
steht die Zusammenstellung der für den praktischen Einsatz von FASAN wichtigsten Eigenschaften der Datenfluss-Semantik; es werden die Datenlokalit¨at, die direkte Kommunikation und der Ausschluss von Laufbedingungen nachgewiesen.
4.1 Datenflussgraphen
FASAN bildet die Koordinationsschicht für die Neben- und Hintereinanderschaltung von
externen Funktionen. Diese Komposition von externen Funktionen l¨asst sich vorteilhaft
als Graph darstellen. Wir verwenden dabei Datenflussgraphen, die als Abwandlung von
Prozessnetzen, wie wir sie in Kapitel 2 eingeführt haben, gesehen werden können. Prozessnetze sind nicht nur anschaulich, sondern liefern auch unmittelbar ein paralleles
Ausführungsmodell. Kennzeichen der hier entwickelten Datenflussgraphen ist ihre Dy-
48
Operationelle Semantik von FASAN
namik, die durch Entfaltung von Knoten zu Untergraphen entsteht; das Netz wird also
dynamisch vergrößert.
Definition 4.1 Ein Datenflussgraph ist ein Prozessnetz1
, wobei zus¨atzlich für
alle auf dem Vorbereich und dem Nachbereich jeweils eine lineare Ordnung definiert ist, die die Reihenfolge der Ein- bzw. Ausg¨ange angibt. Die Menge der
Datenflussgraphen bezeichnen wir mit .
Die Pl¨atze eines Datenflussgraphen bezeichnen wir durchwegs als Puffer und die Tran
sitionen als Funktionsknoten. In Datenflussgraphen nennen wir den Vorbereich eines Funktionsknotens dessen Argumente, den Nachbereich dessen Ergebnisse. Den
Vorbereich eines Puffers nennen wir auch dessen Erzeuger, den Nachbereich dessen Verbraucher. Da in einem Datenflussgraphen der Vor- und der Nachbereich eines Puffers
und für
höchstens einelementig sind, schreiben wir speziell für auch auch
.
! "
Bisweilen verwenden wir Vor- und Nachbereiche auch als Sequenzen, z. B. als Argumente
für Funktionsaufrufe. In diesen F¨allen ist
, wobei kleiner sein muss als
bezüglich der in Definition 4.1 verlangten linearen Ordnung.
Für das dynamische Verhalten eines Datenflussgraphen wollen wir Daten und nicht nur
Zahlen aus in den Puffern speichern. Aus dem FASAN-Programm haben wir durch die
abstrakte Syntax eine algorithmische Signatur gewonnen. Für jede -Algebra über dieser Signatur lassen wir Elemente der Tr¨agermengen als Belegung für die Puffer
des Datenflussgraphen zu.
Durch partielle Funktionsanwendungen ist es außerdem möglich, dass sogenannte Abschlüsse als Belegung für Puffer vorkommen. Ein Abschluss ist ein Tupel aus einer Funktion und einer Sequenz von Argumentvariablen, wobei als Argument auch das PseudoElement vorkommmen kann, das angibt, dass der Funktionsaufruf an dieser Parameterstelle noch kein Argument bekommen hat. Die Menge der Abschlüsse bezeichnen wir
mit
% Als Markierung in den Puffern können sich also unterschiedliche Datenelemente befinden. Eine solche verallgemeinerte Markierung nennen wir Belegung 2 .
Definition 4.2 Die Belegung
, ein Fehordnet jedem Puffer entweder einen Inhalt aus , einen Abschluss aus
lerelement , ein Pseudo-Element oder, falls der Puffer leer ist, zu. Die Menge aller
Belegungen bezeichnen wir mit .
1
2
Siehe Definition 2.19 auf Seite 24.
Eine Belegung ist auch unter der Bezeichnung der individuellen Marken bekannt [Rei85].
4.2 Abbildung der abstrakten Syntax auf Datenflussgraphen
Eine Belegung definiert auch eine Markierung
flussgraphen, n¨amlich
falls sonst :
49
für die Puffer des Daten-
2 Einem Funktionsknoten wird bei seiner Erzeugung eine Funktion aus zugeordnet,
die als seine Belegung aufgefasst werden kann. Oft drücken wir die Be legung kurz durch aus. Indizes an Puffernamen dienen dagegen allein der
, und haben
Identifizierung, etwa durch eine Sequenz von Datennamen wie in keine weitere Bedeutung.
4.2 Abbildung der abstrakten Syntax auf Datenflussgraphen
Wir gehen von FASAN-Funktionen in abstrakter Syntax aus, d. h. alle rekursiven Funktionen sind normalisiert in dem Sinn, dass es keine geschachtelten Aufrufe gibt und alle
Zwischenwerte mit expliziten Variablen innerhalb von
-Ausdrücken bezeichnet sind.
Außerdem sind die Konstruktoren in der Menge
, die Selektoren in der Menge ,
die Typsymbole in und die externen Funktionen mit als bekannt vorausgesetzter Se
mantik in
zusammengefasst. Diese Mengen bilden also die Signatur des FASAN
Programms,
.(
.( *& #
In der Datenfluss-Semantik werden rekursive Funktionen aus als Datenfluss-(Teil-)
Graphen interpretiert. Sie stellen die Verbindung der Argument- und Ergebnispuffer von
Funktionsknoten, die mit rekursiven oder externen Funktionen belegt sind, her. Ziel ist
die Spezifikation einer Abbildung
('& # ! Wie wir es schon bei der Übersetzung von der konkreten in die abstrakte Syntax getan
haben, klammern wir Argumente, die syntaktische Kategorien der abstrakten Syntax bezeichnen, wieder extra mit , um hervorzuheben, dass es sich um die Zuordnung einer
Semantik zu einer Sequenz handelt und nicht um auswertbare Argumente.
Definition 4.3 Für einen Datenflussgraphen
heißen die Mengen
die Eingänge von und
die Ausgänge von .
-
Die Mengen in
und out
sind nur Teilmengen des Vorbereichs bzw. Nachbereichs
des Graphen. Wenn Funktionsknoten mit nullstelligen Funktionen belegt sind bzw. Er gebnispuffer keinen Verbraucher haben, kann tats¨achlich bzw. -
50
Operationelle Semantik von FASAN
sein. Im Folgenden setzen wir implizit voraus, dass bei der Entfaltung eines Datenfluss graphen die Mengen in
und out
des alten Graphen auch für den neuen übernommen werden, wenn wir nichts anderes vermerken.
Die folgende Abbildung gibt an, welcher Graph durch eine rekursive Funktionsdefinition festgelegt wird. Wir bauen den Graphen dabei schrittweise von oben nach un
”
ten“, Zuweisung für Zuweisung, auf, indem an schon vorhandene Puffer der durch die
Zuweisung definierte Graph angeh¨angt wird. Dadurch entstehen nicht notwendig Datenflussgraphen, sondern allgemeinere Netze, weil Puffer noch mehrere Verbraucher haben
können. Dies werden wir in einem letzten Schritt bei der Entfaltung durch die Abbildung
jedoch beheben.
/ 0
('& # ! ('& # ! * & # ( ( wobei / 0 neue Puffer für alle der mit indizierte Puffer aus
-
(&#
(
Ausgehend von einem vorhandenen Graphen werden neue Funktionsknoten an bestehende Knoten angeh¨angt und neue Ausg¨ange erzeugt.
('& # -
% ('& # -
!" ('& # -
! -" / ( & # ('& # -
/ ('& #
Der einfachste Fall der Abbildung
-
% ist die Zuweisung von Variable an Variable, die wir mit der Identit¨atsfunktion id vornehmen:
('& # -
wobei
(
neuer Funktionsknoten
neuer Puffer
Zur statischen Übersetzung werden Konstruktoren und Selektoren wie die übrigen
Funktionen aus
behandelt. Ein Aufruf dieser Funktionen ergibt einfach einen mit
den entsprechenden Ein- und Ausg¨angen in Relation gesetzten Funktionsknoten. Die besondere Semantik dieser Knoten zeigt sich erst dynamisch bei der Berechnung durch eine
4.2 Abbildung der abstrakten Syntax auf Datenflussgraphen
spezielle Art der Entfaltung.
-
('& #
(
neuer Funktionsknoten für
die mit indizierten Puffer aus
neue Puffer für alle
wobei
51
für alle ( ( Für eine bedingte Zuweisung wird zun¨achst ein neuer Funktionsknoten im Gra phen angelegt mit der speziellen Belegung "! / . Erst der Wert des Pr¨adikats (bzw.
die Belegung von ) entscheidet, welcher der beiden neuen Funktionsknoten den einzubauenden Teilgraphen bei der Entfaltung von liefert.
('& # -
wobei
( "! #%$ &)( &)*+,& / neuer Funktionsknoten
( (
die mit indizierten Puffer aus
neuer Puffer für alle
Eine rekursive Funktionsdefinition für alle eines FASAN-Programms beschreibt
immer einen Datenflussgraphen, der bei der Entfaltung für einen mit belegten Knoten
eingesetzt werden kann.
Um beim Aufbau eines Datenflussgraphen
die Bedingung der Konfliktfreiheit
aus Definition (4.1) für alle Puffer zu erfüllen, muss nach jedem Puffer mit
ein neuer Verteilerknoten eingefügt werden, dessen Belegung die
Funktion ist:
/ / / mal
/
Solche -Knoten werden folgendermaßen nach einem Puffer gefügt:
/ / %
falls sonst
in den Graphen ein-
52
Operationelle Semantik von FASAN
PSfrag replacements
p
p
addfork
fork
fork
Abbildung 4.1 Einfügen eines -Knotens für Puffer mit mehr als einem Verbraucher
wobei
neue Puffer für alle
neuer Funktionsknoten
/
/
0 auf alDie Abbildung
wendet diese Normalisierung
le Puffer ihres Arguments an. Beim Aufbau eines Datenflussgraphen muss keine Rück /
0 im
sicht auf die Konfliktfreiheit genommen werden, wenn die Bedingung durch
Nachhinein sichergestellt wird.
Die Annotation eines Funktionsaufrufs mit einer Lokation ergibt einen parallelen Funktionsknoten mit einem zus¨atzlichen Eingang für den Lokationsparameter. Der Unterschied
zu einem normalen Funktionsknoten wirkt sich erst zur Laufzeit aus, wenn geeignete Belegungen der Eing¨ange die Auswertung oder die Entfaltung des parallelen Funktionsknotens verursachen.
-
('& #
wobei
( ( (&# (
der neu in
eingefügte Funktionsknoten ist mit %
In der abstrakten Syntax können Funktionen an die Parameter einer Funktion zugewiesen
und diese Parameter wiederum auf weitere Argumente angewendet werden. Die Anwendung von Funktionsparametern auf weitere Argumente wird aber immer in einem Aufruf
der Funktion gekapselt. Eine solche Funktion höherer Ordnung kann ebenfalls im
Datenflussgraphen entfaltet werden, wie zum Beispiel die Funktion map aus Beispiel 3.13
auf Seite 44. Den dazugehörenden Datenflussgraphen zeigt Abb. 4.2. In der graphischen
Darstellung schreiben wir Belegungen von Funktionsknoten (also das Funktionssymbol)
in das Rechteck. Außerdem verzieren wir die Rechtecke von Konstruktor- und Selektorknoten mit einem Oval, das die Öffnung eines Kabelbaums symbolisieren soll.
Aber nicht nur formale Parameter können für eine Funktion stehen. Auch an lokale Variablen innerhalb des Rumpfs einer Funktionsdefinition können Funktionen zugewiesen
werden, wenn die rechte Seite ein Funktionsaufruf mit mindestens einem Unterstrich als
Argument ist. Dabei wird im Allgemeinen ein Abschluss erzeugt, wenn außer Unterstri-
4.2 Abbildung der abstrakten Syntax auf Datenflussgraphen
53
fork
PSfrag replacements
head
fork
tail
/
map
apply
Cons
Abbildung 4.2 Datenflussgraph des
-Zweigs 8
/
der Funktion map
fork
fork
node
apply
curried
fork
PSfrag replacements
sons
mapDown
map
Branch
Abbildung 4.3 Datenflussgraph des
-Zweigs der Funktion mapDown
chen auch echte Argumente in der Argumentliste auftreten (partielle Funktionsanwendung).
W¨ahrend wir für neue Puffer die Belegung (kein Datenelement) annehmen, werden die
speziellen Puffer für ausgelassene Funktionsargumente mit initialisiert. Ein solches feh-
lendes Argument wird durch die Abbildung
in einen speziellen mit belegten
Puffer curried übersetzt. Abb. 4.3 veranschaulicht dies am Beispielprogramm 3.10 von
Seite 38 für die auf B¨aumen arbeitende Funktion mapDown.
Werden die Eing¨ange des Datenflussgraphen mit einem Datenelement belegt, kann durch Schalten rechenbereiter Funktionsknoten eine Ausführung erzeugt werden. Dies ist Thema des n¨achsten Abschnitts.
(&#
54
Operationelle Semantik von FASAN
4.3 Datenflussgraphen als Berechnungsmodell
Für die Beschreibung der tats¨achlichen Berechnungen eines FASAN-Programms werden
wir zwei Abbildungen auf Datenflussgraphen definieren. Die erste Abbildung beschreibt
die Auswertung eines Funktionsknotens und damit die Änderung der Belegung eines Datenflussgraphen. Die zweite Abbildung, die sogenannte Entfaltung eines Funktionsknotens, modifiziert die Struktur des Datenflussgraphen, indem sie neue Knoten erzeugt anstelle des entfalteten Knotens in den Datenflussgraphen einsetzt.
Die Entfaltung von Datenflussgraphen ist eng verwandt mit Konzepten, die sich auch
in anderen Systemen finden. Die Entwicklungsmethodik FOCUS mit der Agentensprache ANDL [SS95], mit deren Hilfe Basiskomponenten über Kan¨ale verbunden werden können, kennt ebenfalls eine Verfeinerung ihrer Agenten [SS95, BGG 96, HS97a,
HS97b]. Datenflussgraphen enthalten aber keine Zyklen, was sie von Agentennetzen in
ANDL unterscheidet. Somit beschreiben Datenflussgraphen unmittelbar Prozesse (im
Sinne von Prozessnetzen). Zur Beschreibung der Dynamik in einem Datenflussgraphen
erweitern wir diesen um Belegungen für die Puffer und um Lokationen für die Funktionsknoten:
Definition 4.4 Ein paralleler Datenflussgraph
mit Belegung , der zus¨atzlich mit einer Funktion
&
mit
&
ist ein Datenflussgraph
versehen ist, die für jeden Funktionsknoten dessen Lokation angibt, also die Nummer des Rechners, auf dem er sich befindet.3 Die Menge dieser Funktionen nennen wir
& .
Das Kriterium zum Schalten eines Funktionsknoten ist, dass er aktiviert oder — in der
Sprache unserer Datenflussgraphen — rechenbereit ist, wenn also für alle gilt:
. Wenn ein Funktionsknoten schaltet, wendet er entweder die Funktion, mit
der er belegt ist, auf seine Argumente an; dies nennen wir Auswertung. Oder aber er wird
durch einen neuen Teilgraphen ersetzt, was wir als Entfaltung bezeichnen. Auswertung
erzeugt also nur eine neue Belegung, w¨ahrend die Entfaltung auch den Datenflussgraphen
ver¨andern kann.
%&
Die Kriterien, nach denen wir entscheiden, ob ein Knoten ausgewertet oder entfaltet wird,
sind in der Abbildung
zusammengefasst (wobei
" ):
Dabei ist schnitt 3.1.5).
3
der in der konkreten FASAN-Syntax vordefinierte Variable numHosts (vgl. Ab-
4.3 Datenflussgraphen als Berechnungsmodell
%& % % & %
%& & & * & & * & ( ! * & ( ! * ! & ( ! * & ( ! * & ( ! * & &
55
% %
&
und und falls falls und
falls und
und falls falls falls falls sonst, speziell falls für ein Alle Abbildungen, die eine Auswertung im Datenflussgraphen beschreiben, haben demnach die Funktionalit¨at
-
& * % %
wogegen Entfaltungsabbildungen die Funktionalit¨at
& (! * % %
besitzen.
%
% %
&
Durch die Fehlerabbildung wird das Schalten in einem Datenflussgraphen zu einer
strikten Abbildung:
wobei
falls sonst
-
& *
(! *
im
Wir gehen nun die Regeln zum Schalten von Funktionsknoten mit und Detail durch. Wenn nichts anderes vermerkt wird, muss zum Schalten eines Knotens immer die Voraussetzung
für alle erfüllt sein.
4.3.1 Auswertung von Funktionsknoten
In einem markierten Datenflussgraphen
wird ein rechenbereiter Funktions
knoten (
) ausgewertet, indem dem Datenflussgraphen eine neue Belegung
zugeordnet
wird:
und und (4.1)
& * 56
Operationelle Semantik von FASAN
PSfrag replacements
eval
Abbildung 4.4 Auswertung ( Schalten“) eines Funktionsknotens
”
! " ! " für alle mit ( (
5 falls ( falls falls und sonst
wobei
mit
" Beachten müssen wir bei der Auswertung, dass das Kriterium der Rechenbereitschaft für
immer erfüllt ist. Damit ein solcher argumentloser
einen Funktionsknoten mit Funktionsknoten nicht immer
schalten und dadurch die Sicherheitsbedingung
für ein mit verletzen kann, wird er nach seiner Auswertung aus dem
Graphen entfernt:
und (4.2)
(! *
wobei
& ! " !( ( " ( für und
&
sonst
Ist ein Eingang von mit belegt, erzeugt dies einen Abschluss als Belegung für den
Puffer im Datenflussgraphen. Dieser Abschluss muss zus¨atzlich zum Funktionsnamen
auch alle übergebenen Argumente mitführen. Dann ergibt sich die neue Belegung nach
dem Schalten des Knotens als
und (4.3)
& * wobei ! "
! " alle mit für
! " für sonst
4.3 Datenflussgraphen als Berechnungsmodell
57
Dies war der einfache Fall, in dem eine Funktion auf eine nicht-vollst¨andige Liste von
Argumenten angewandt wird. Es kann aber auch eine Abschluss — der also schon einige
der für die Funktion benötigten Argumente mitführt — auf weitere Argumente angewandt
werden. Wie dies im Datenflussgraphen vollzogen wird, zeigt der n¨achste Abschnitt.
4.3.2 Entfaltung von -Knoten
Ein -Knoten hat als ersten Eingang immer einen Puffer, dessen Belegung ein Abschluss ist. Wir benötigen zuerst eine Abbildung , die das Zusammenfügen von
Variablen eines Abschlusses mit den aktuell übergebenen Argumenten leistet:
falls falls und falls
und 2 % !$"
! *" ! "* $! "
! "* ! *"* ! "* Für die folgenden drei Regeln zur Entfaltung eines Funktionsknotens setzen wir:
!
!
"
! "
"
Bei der Entfaltung eines -Knoten sind drei F¨alle zu unterscheiden.
1. Fall: Nach Einsetzen der neuen Argumente fehlen der Funktion im Abschluss immer
noch Argumente. erzeugt dann einen neuen Abschluss:
(4.4)
(! *
wobei
&
falls sonst
&
(! *
&
&
wobei !( ( " ( falls für 5 sonst
2. Fall: Alle Argumente sind vorhanden, und die Funktion des Abschlusses ist ein Ele
ment der Menge
. Dann kann die Funktion ausgewertet werden:
und
(4.5)
58
Operationelle Semantik von FASAN
zs
as
PSfrag replacements (g,_,y,_) x
z
apply
x
y
unfold
Abbildung 4.5 Entfalten eines
"!$#
z
f
-Knotens durch die Regel (4.6)
3. Fall: Alle Argumente sind vorhanden, die Funktion des Abschlusses ist aber eine
rekursive Funktion, ein Konstruktor oder ein Selektor. Dieser letzte Fall führt zur Vorbereitung einer Entfaltung des Knotens , indem der Knoten mit der Funktion belegt
wird und einen neuen Vorbereich bekommt:
und
(4.6)
(! *
wobei
&
neue Puffer für alle
neuer Funktionsknoten
! " falls sonst
&
4.3.3 Entfaltung von Funktionsknoten
Ein Funktionsknoten des Datenflussgraphen
, der mit einer rekur
siven Funktion belegt ist, kann ebenfalls schalten, sofern er rechenbereit ist.
Gem¨aß der Abbildung
wird er aber nur entfaltet, wenn keiner seiner Eing¨ange mit belegt ist.
und (4.7)
('! * wobei
&
! !
%&
&
"
"
( & # 4.3 Datenflussgraphen als Berechnungsmodell
PSfrag replacements
unfold
59
! " ! " - falls mit 8
Abbildung 4.6 Entfaltung eines Funktionsknotens
5 6
&
&
&
falls falls falls Das Schalten eines Knotens für bedingte Ausdrücke führt ebenfalls zu dessen
Entfaltung, wenn alle seine Eing¨ange belegt sind:
( ! * ('! *
! wobei /
falls / - und 10. und falls &
(4.8)
"! / ! / sonst
4.3.4 Entfaltung von Konstruktor- und Selektorknoten
Der Aufbau und die Zerlegung hierarchischer Datenstrukturen orientieren sich an dem
Modell eines Kabelbaums, das schematisch in Abb. 4.7 dargestellt ist. Das Bild deutet
die spezielle Semantik an, die für die Beispiele aus der Einführung nötig ist: Durch Entfaltung wird der Kabelbaum aufgedröselt“ und die Hülle“, die die Konstruktoren und
”
”
Selektoren um die Kabel“ legen, entfernt.
”
Jede der Datenkomponenten kann den direkten Weg zu ihrem Ziel nehmen, und dies unabh¨angig und nicht synchronisiert von den anderen Komponenten.
Zu diesem Zweck erzeugt der Konstruktorknoten nur ein Datenelement mit Identifikationsnummern, die auf seine Eing¨ange, also die Komponenten der Datenstruktur, verweisen. Mit Hilfe dieser Identifikationsnummern kann dann bei Selektion einer Komponente
60
Operationelle Semantik von FASAN
T = c (A,B,C)
A
C
B
Konstruktor
A
B
C
Y
Z
Aufbau
T
Selektoren
Zerlegung
Y
X
Z
X
c(X,Y,Z) = T
Abbildung 4.7 Schematische Darstellung des Aufbaus und der Zerlegung eines Kabelbaums
der richtige Funktionsknoten gefunden werden. Daher muss der Inhalt der Komponenten
— eine unter Umst¨anden große Datenmenge — nicht den ganzen Weg der umfassenden
Datenstruktur nehmen, sondern wird auf einer direkten Verbindung von den Puffern der
Komponenten bezogen.
/ ein,
Für die Spezifikation
dieses Aspekts führen wir eine injektive Abbildung die einem Puffer eine eindeutige Nummer gibt derart, dass mit der (partiellen) Abbildung
für ein aus dem Definitionsbereich von gilt:
/
/ / / /
Ist ein Argument eines Konstruktor- oder Selektorknotens mit belegt, bedarf dies keiner
Sonderbehandlung; wird über die Kabel des Kabelbaums einfach weitergeleitet.
Ein nullstelliger Konstruktor wird dagegen wie eine sonstige nullstellige Funktion behandelt: Nachdem er einmal geschaltet hat, wird er aus dem Datenflussgraphen entfernt. Dies
haben wir auch schon bei der Abbildung
(4.3) in die Fallunterscheidungen mit einbezogen. Dort wurde festgelegt, dass ein Konstruktor als einziger Funktionsknotentyp im
Graphen nicht alle seine Eing¨ange belegt haben muss, um zu schalten. Es genügt, wenn
ein Eingang belegt ist:
mit
und es gibt ein %&
( ! * wobei
! " / für alle
falls (4.9)
sonst
Bei den drei folgenden Entfaltungsregeln (4.10) bis (4.12) für Selektorknoten bestimmen
4.3 Datenflussgraphen als Berechnungsmodell
PSfrag replacements
/ 61
/ unfold
c
c
Abbildung 4.8 Entfaltung eines Konstruktorknotens im Datenflussgraphen
wir zuerst:
neuer Funktionsknoten
/
falls
falls falls [betrifft nur (4.10)]
sonst
Bei der Entfaltung eines Selektorknotens sind drei F¨alle zu unterscheiden, je nach Belegung und Nachfolger des Puffers .
1. Fall: Der Puffer ist belegt, das Datenelement also schon verfügbar. Hier muss nur das
vorhandene Datenelement in das Ergebnis des Selektors übertragen werden.
und und (4.10)
& (! *
&
2. Fall: Der Puffer ist nicht belegt und hat auch keinen Verbraucher. Hier wird ein neuer
Replikationsknoten erzeugt, und zwar auf dem Rechner des Konstruktorknotens, da
dieser Knoten u. U. von mehreren Selektoren verwendet wird und wir sonst einen ungewollten Umweg der Daten über die Lokation des ersten entfalteten Selektors verursachen
würden.
und und (4.11)
& & (! *
62
Operationelle Semantik von FASAN
p
p
unfold
s
s
PSfrag replacements
q
q
Abbildung 4.9 Datenflussgraphen bei der Entfaltung eines Selektorknotens (1. Fall)
p
p
rep
unfold
s
s
PSfrag replacements
q
q
Abbildung 4.10 Datenflussgraphen bei der Entfaltung eines Selektorknotens (2. Fall)
p
p
rep
rep
unfold
s
s
PSfrag replacements
q
q
Abbildung 4.11 Datenflussgraphen bei der Entfaltung eines Selektorknotens (3. Fall)
wobei
&
&
&
falls sonst
3. Fall: Der Puffer ist nicht belegt, hat aber einen Replikationsknoten als Verbraucher.
Eine neue Verbindung zwischen diesem Knoten und dem Ergebnis des Selektors wird
aufgebaut.
und (4.12)
& &
('! *
4.3 Datenflussgraphen als Berechnungsmodell
63
unfold
rep
Abbildung 4.12 Entfaltung eines Replikationsknotens
/ Ein Replikationsknoten wird im Gegensatz zu einem -Knoten nicht ausgewertet, sondern bei der Entfaltung entfernt und die Belegung seines Eingangs nicht gelöscht
(Abb. 4.12):
(4.13)
& & (! *
wobei
falls sonst
4.3.5 Lokationen
Hat ein Funktionsknoten einen zus¨atzlichen Eingang für den Wert der Lokation, wird er,
bevor er tats¨achlich entfaltet wird, auf diese neue Lokation verlagert. Auch ein Knoten kann einen zus¨atzlichen Eingang für eine Lokation haben.
und (4.14)
('! *
&
0
( ! * & ( ! * & ! " falls und ! wobei sonst
falls falls &
&
In dieser Entfaltungsregel ist
0
0 !
falls für alle anderen Funktionsknoten * & # "( ( " "
64
Operationelle Semantik von FASAN
z
PSfrag replacements
z
unfold
&
Rechnergrenze
Abbildung 4.13 Entfaltung eines parallelen Funktionsknotens
Die Puffer vor und nach einem Funktionsknoten, der auf einen anderen Rechner verschoben wurde, spielen eine besondere Rolle:
Definition 4.5 Ein Puffer heißt Rechnergrenze in einem parallelen Datenfluss graphen
& , wenn & & .
('! *
zu
Die Puffer des Vor- und Nachbereichs von werden durch die Entfaltung Rechnergrenzen. Der Funktionsknoten bildet auf dem Zielrechner einen eigenst¨andigen Datenflussgraphen.
Da hier explizit Puffer zwischen Funktionsknoten mit unterschiedlicher Lokation vorhanden sind, beschreibt dieses Modell asynchrone Kommunikation. Die Rechnergrenzen
und können im Falle der Implementierung mit gemeinsamem
Speicher als Variablen angesehen werden, auf die sowohl der ursprüngliche als auch der
neu erzeugte Thread zugreifen kann. Im Fall verteilter Ausführung stellen sie gleichzeitig Kommunikationspuffer zwischen beiden Prozessen dar. Asynchrone Kommunikation
entspricht einer höheren Abstraktionsebene und ist meist leichter zu implementieren. Sie
kann aber systematisch auf synchrone Kommunikation zurückgeführt werden [Sch98].
%&
Wir haben jetzt die Spezifikation der parallelen Semantik eines FASAN-Programms fertiggestellt. Die Abbildung
(4.3) verflechtet die Entfaltungsschritte zur Erzeugung
neuer Teilgraphen mit Auswertungsschritten, in denen Berechnungen mit Funktionen der
darunterliegenden -Algebra stattfinden und Belegungen über Funktionsknoten weitergeschaltet werden. Bevor wir jedoch dieses Semantik-Kapitel schließen, wollen wir noch
einige wichtige Eigenschaften der Berechnung mit Datenflussgraphen nachweisen. Hier
wird sich die etwas ausführliche und stellenweise nicht ganz direkte Übertragung von
FASAN-Programmen über eine abstrakte Syntax in Datenflussgraphen bezahlt machen,
weil die Beweise kurz und durch die graphische Darstellbarkeit leicht nachvollziehbar
sind.
4.4 Eigenschaften der Datenfluss-Semantik
65
4.4 Eigenschaften der Datenfluss-Semantik
Für die Parallelisierung haben hierarchische Datentypen von FASAN gegenüber den gewohnten Baumkonstruktoren drei entscheidende Effizienzvorteile, die wir in den folgenden Lemmata (4.7) bis (4.9) zusammenfassen. Für die Entwicklung von FASANProgrammen ist die mit Lemma (4.10) gezeigte Eigenschaft der Verklemmungsfreiheit
wichtig.
Wir wollen aber zun¨achst noch ein paar anschauliche Begriffe einführen. Daten sind
hier immer als das Ergebnis der Belegungsfunktion zu sehen, also als Elemente aus
. Liefert die Belegung eines Puffers , so sagen wir, der Puffer
enth¨alt keine Daten. Wenn ein Funktionsknoten schaltet, so sagen wir, die Daten passieren den Knoten.
Sequenz ! " , wenn Definition 4.6 Ein Pfad von
nach
, eines Datenflussgraphen
und ist eine
5 .
sowie eine -Algebra
für
&
Gegeben sei ein paralleler Datenflussgraph
eines FASAN-Programms.
über der Signatur
Zuerst wollen wir uns vergewissern, dass Daten auf ihrem Weg von einem Konstruktorknoten zu einem Selektorknoten den direkten Weg nehmen.
Lemma 4.7 (Direkte Kommunikation) Es seien ein Konstruktorknoten,
. Der Pfad
gehöriger Selektorknoten sowie und der Entfaltung von und entsteht, enth¨alt höchstens eine Rechnergrenze.
ein zu, der nach
Beweis: Die Entfaltung eines Konstruktorknotens mit der Regel (4.9) bel¨asst zun¨achst
die Daten in den Puffern . Erst wenn die Entfaltung des Selektorknotens eine neue
Verbindung zwischen und erzeugt, können die Daten weitergeschaltet werden.
Zwischen und ist dann höchstens ein Replikationsknoten vorhanden (Regel
(4.12)), oder ein solcher wird erzeugt (Regeln (4.10) und (4.11)). Da in beiden F¨allen gilt:
& & , müssen die Daten maximal eine Rechnergrenze, n¨amlich den Puffer ,
passieren.
Liegen vor und nach diesem im Beweis genannten Zwischenknoten keine Rechner-
grenzen, so bleiben die Daten lokal auf ihrem Rechner:
Lemma 4.8 (Datenlokalität) Es seien
torknoten sowie und & & , so enth¨alt der Pfad
keine Rechnergrenze.
wieder ein Konstruktorknoten, ein Selek
auf demselben Rechner,
. Liegen und nach der Entfaltung von und Beweis: Wegen Lemma 4.7 gibt es nach der Entfaltung von Konstruktor- und Selektor
knoten maximal einen Zwischenknoten zwischen und , also ,
66
Operationelle Semantik von FASAN
den die Daten passieren müssen. Außerdem ist nach den Regeln (4.11) und (4.12)
& & . Zusammen mit der Voraussetzung & & folgt die Behauptung.
Ein großer Vorteil der Entfaltungsregeln (4.9) bis (4.12) ist die Unabh¨angigkeit der Argumente eines Konstruktorknotens und damit auch die Unabh¨angigkeit ihrer Belegungen, also der Komponenten des hierarchischen Datentyps: Auf den direkten Verbindungen können die Komponentendaten getrennt voneinander übertragen werden und stehen
sobald wie möglich den Verbraucherfunktionen zur Verfügung.
Lemma 4.9 (Höherer Parallelitätsgrad) Daten werden durch die Zusammenfassung in
einem hierarchischen Datentyp nicht synchronisiert.
Beweis: Ein Konstruktorknoten erzeugt bei seiner Entfaltung die Baumreferenz
gem¨aß Regel (4.9) bereits, sobald nur ein Eingang belegt ist, also schon
dann, wenn nur eine Datenkomponente vorhanden ist. Die Puffer, die vor der Entfaltung
die Argumente von bilden, werden also von nicht synchronisiert.
ein
Des Weiteren ist auch die Selektorseite des Datentyps zu untersuchen: Sei Selektorknoten, der ein Datenelement
vom Konstruktorknoten erhalten
hat. Seien außerdem die Puffer und . Die Unabh¨
angigkeit der
Puffer bleibt auch nach der Entfaltung des Selektorknotens erhalten: Jedes Da
tenelement in diesen Puffern kann unmittelbar zum Puffer passieren: Ist es schon als
Belegung von vorhanden, wird es gleich nach verlegt (Regel (4.10)). Ist noch
nicht belegt, wird ein direkter Pfad über einen Replikationsknoten angelegt
(Regeln (4.11) und (4.12)), über den das Datenelement sp¨ater unabh¨angig von den ande
ren Komponenten zu passieren kann.
/
!
"
Für die Korrektheit des FASAN-Programms ist weiterhin wesentlich, dass keine Laufbedingungen (race conditions) auftreten können. Laufbedingungen ¨außern sich allgemein
dadurch, dass Berechnungsergebnisse von der zeitlichen Reihenfolge gewisser Aktionen
abh¨angen, die auf gemeinsame Speicherbereiche schreiben.
In der Datenfluss-Semantik ist die Situation recht übersichtlich, weil dort die Puffer den
Speicherbereichen entsprechen. Die nach Definition (4.1) vorausgesetzte und bei der Ent
faltung von Funktionsknoten eingehaltene Konfliktfreiheit besagt, dass jeder Puffer . Laufbehöchstens einen Erzeuger und höchstens einen Verbraucher hat, dingungen können hier allenfalls entstehen, wenn ein Erzeuger mehrfach schalten kann
und vorhandene Belegungen seiner Ergebnispuffer überschreibt, bevor der Verbraucher
die erste Belegung gelesen hat. Dass aber eine solche Situation nicht auftreten kann, besagt folgendes Lemma, das impliziert, dass es keine Möglichkeit gibt, einen schon belegten Puffer erneut zu belegen (außer durch ).
Lemma 4.10 (Keine Laufbedingungen) In jedem Pfad eines Datenflussgraphen
.
gibt es maximal einen Puffer mit
4.4 Eigenschaften der Datenfluss-Semantik
67
Beweis: Wir zeigen, dass ausgehend von einem einelementigen Datenflussgraphen mit
belegten Eing¨angen nur wieder Datenflussgraphen erzeugt werden, die Pfade mit maximal einer Belegung enthalten. Dies ist also eine Induktion über die Schaltvorg¨ange von
Knoten. Für jeden Schaltvorgang bezeichnen wir den schaltenden Knoten mit und seine
Argumente und Ergebnisse mit . Außer bzw. dem bezeichnen wir einen Puffer ohne Erzeuger mit (Anfang eines maximalen Pfads)
und einen ohne Verbraucher (Ende eines maximalen Pfads).
Induktionsanfang: Ein Datenflussgraph mit einelemetigem
enth¨alt nur Pfade
, wobei belegt ist und nicht, also genau ein Puffer in jedem Pfad.
! !
"
! "
"
Induktionsannahme: Wir setzen einen Datenflussgraphen mit maximal einem belegten
Puffer pro Pfad aus.
Induktionsschluss: Für den n¨achsten Schaltvorgang müssen wir unterscheiden, welche
Schaltregel Verwendung findet:
& * ('! *
& *
('! *
mit
mit Regel (4.3), 1. Schalten durch mit Regel (4.1), -
Regel (4.4) oder
mit Regel (4.5): Hier handelt es sich um reine Auswertungen eines Funktionsknotens, die die Struktur des Datenflussgraphen unver¨andert
und nur die Belegungen auf dessen Ausg¨ange passieren lassen. Nach Induktions
voraussetzung war für jeden Pfad der Puffer der einzige
belegte Puffer in diesem Pfad. Nach dem Auswerten ist nicht mehr belegt, und
der einzige belegte Puffer des Pfads.
2. Schalten durch mit Regel (4.2): Hier schaltet ein Funktionsknoten oh-
!
" & * ne Argumentpuffer und wird dann aus dem Datenflussgraphen entfernt, sodass ein
Knoten des Nachbereichs von zu einem Anfang eines maximalen Pfades wird.
Nach einem Funktionsknoten , der mit einem anderen Puffer synchronisiert,
kann kein Puffer belegt sein. Wegen der Konfliktfreiheit des Graphen ist auch kein
Puffer zwischen und belegt. Damit ist der einzige belegte Puffer in allen
von ausgehenden Pfaden.
mit Regel (4.6): Hier findet eine Ersetzung der (be3. Schalten durch legten) Puffer mit neuen belegten Puffern als Argumente des entfalteten Knotens
statt. Den neuen Puffern werden keine Erzeuger zugeordnet, und in den
Pfaden ausgehend von entstehen keine zus¨atzlichen Belegungen im Vergleich zu
den ursprünglichen Pfaden.
mit Regel (4.7) oder Regel (4.8) oder mit Re4. Schalten durch (! *
(! * ('! *
gel (4.14): Diese Entfaltungen eines Knotens lassen die Belegung des Graphen
unver¨andert; die bleiben belegt und unabh¨angig. Die Pfade des neuen Graphen
sind dann die, die durch Ersetzung von durch Pfade des neuen Teilgraphen entstehen. Alle Puffer dieses Teilgraphen sind nicht belegt, wodurch jeder Pfad des
Gesamtgraphen weiterhin maximal einen belegten Puffer enth¨alt.
mit Regel (4.9) oder durch mit Regel (4.13):
5. Schalten durch Die Entfaltung eines Konstruktorknotens oder eines Replikationsknotens be-
(! * (! *
68
Operationelle Semantik von FASAN
legt zwar dessen Ergebnis , ohne die Belegung in dessen Argumenten zu
löschen. Es wird aber die Verbindung zu den getrennt, wodurch jeder Pfad
des ursprünglichen Graphen in zwei Teilpfade und
zerlegt wird, von denen jeder nur einen belegten Puffer ( bzw. )
enth¨alt.
mit Regel (4.10): Die Belegung des ursprünglichen Ar6. Schalten durch gumentpuffers des zugehörigen Konstruktorknotens wird zwar in kopiert. Allerdings wird dann zu einem Anfangsknoten aller Pfade, in denen er enthalten ist,
weil die Verbindung zum Selektorknoten entfernt wird. Für diese Pfade gilt wegen
der Konfliktfreiheit des Datenflussgraphen und wegen der Synchronisation durch
von abh¨angige Funktionsknoten (vgl. Schalten durch Regel (4.2)), dass sie außer
keine weiteren belegten Knoten enthalten.
mit Regel (4.10): Die Entfaltung eines Selektorknotens
7. Schalten durch ! !
"
"
! ('! *
" (! *
erzeugt eine Verbindung von dem ursprünglichen Argumentpuffers des zu
gehörigen Konstruktorknotens über einen Replikationsknoten zu . Es wird
keine neue Belegung erzeugt. Da außerdem die Pfade, die vor der Entfaltung von
ausgingen, keine belegten Puffer enthielten, kann wegen
der Induktionsvoraus
setzung maximal ein Puffer aus belegt sein.
Insgesamt ist also sichergestellt, dass keine Daten als Belegungen beim Schalten im
Datenflussgraphen überschrieben und verloren gehen können.
!
"
Neben Laufbedingungen sind Verklemmungen typische und h¨aufige Fehler, die bei der
parallelen Programmierung gemacht werden können. Eine Verklemmung liegt vor, wenn
zwei oder mehr Aktionen
wechselseitig oder zyklisch auf Ergebnisse der anderen warten, oder
unterschiedliche Sperren (z. B. für Betriebsmittel) belegt haben und vor deren Freigabe auf weitere Sperren warten, die wechselseitig schon von anderen Aktionen
belegt sind.
In einem Datenflussgraphen gibt es allerdings keine Sperren oder sonstige Synchronisationsmechanismen. Einzig die Rechenbereitschaft entscheidet über das Schalten von
Funktionsknoten. Wechselseitiges Warten auf Ergebnisse ist wegen der Zyklenfreiheit der
Datenflussgraphen ebenfalls ausgeschlossen. Bei Datenflussgraphen bekommen wir also
quasi umsonst Verklemmungsfreiheit der durch sie beschriebenen Berechnungen.
Die Zyklenfreiheit, zusammen mit Lemma 4.10, bringt auch einen Vorteil auf Implementierungsebene. Sie eröffnet die Möglichkeit einer ziemlich einfachen Speicherverwaltung:
Ein Puffer mit kann aus dem Speicher gelöscht werden, sobald bei
einem Schaltvorgang eine neue Belegung den Wert liefert.
Ein Funktionsknoten kann gelöscht werden, wenn alle seine Eing¨ange gelöscht worden sind.
4.4 Eigenschaften der Datenfluss-Semantik
69
Da die Vorbereiche von Konstruktorknoten immer belegt bleiben, wenn sie einmal von einer Marke erreicht wurden, muss hier eine Zusatzmaßnahme getroffen werden: Ein Refe renzz¨ahler in diesem Puffer z¨ahlt, wie viele Referenztupel für diesen Puffer
aktuell im Datenflussgraph existieren. Ein -Knoten erhöht den Referenzz¨ahler, wenn
er ein solches Tupel als Argument bekommt. Ein Selektorknoten, der den Referenzz¨ahler
verbraucht, erniedrigt ihn. Ist der Referenzz¨ahler 0, kann der Puffer freigegeben werden.
/ Fassen wir nochmal das bisher Erreichte zusammen: Mit FASAN haben wir eine Koordinationssprache, deren Semantik durch die parallelen Datenflussgraphen formal spezifiziert ist. Auch die Erzeugung der Datenflussgraphen aus einem FASAN-Programm über
die abstrakte Syntax haben wir vollst¨andig angegeben. Eine Implementierung von FASAN, die dieser Semantik entspricht, profitiert von den wesentlichen Sicherheitseigenschaften der Semantik, also von der Verklemmungsfreiheit, der Abwesenheit von Laufbedingungen und der im verteilten Fall richtigen Kommunikation. Sie bietet darüber hinaus die Effizienzeigenschaften der Datenlokalit¨at, der direkten Kommunikation und des
höheren Parallelit¨atsgrades. Wir besprechen im Weiteren eine solche Implementierung
von FASAN.
70
Operationelle Semantik von FASAN
71
Kapitel 5
Übersetzung von FASAN in Java
In diesem Kapitel wollen wir die konkrete Implementierung von FASAN vorstellen. Die
Übersetzung in die Zielsprache Java führen wir nicht durchgehend formal aus, was angesichts der Größe des implementierten Compilers von ca. 200 kB zu viel Raum in Anspruch
nehmen würde. Vielmehr arbeiten wir die wichtigsten Teile der Übersetzung und ihren
Bezug zur Datenfluss-Semantik sowie besondere aufgetretene Schwierigkeiten heraus.
Dementsprechend finden sich in diesem Kapitel zuerst ein Erfahrungsbericht über frühere Implementierungen von FASAN (Abschnitt 5.1) sowie die Gründe für die Auswahl
von Java als Zielsprache (Abschnitt 5.2). Sodann werden die Grundideen der Übersetzung in ein sequentielles Java-Programm (Abschnitt 5.3), in ein Programm mit ThreadUnterstützung für Rechner mit mehreren Prozessoren (Abschnitt 5.4) und die Erweiterung
für Rechnernetze mit Hilfe von RMI (remote method invocation, Abschnitt 5.5) dargestellt.
5.1 Erfahrungen aus vorhandenen Implementierungen
Das im Rahmen dieser Arbeit entwickelte FASAN-System ist aus einer Reihe von Arbeiten über funktionale Sprachkonzepte für adaptive numerische Anwendungen hervorgegangen.
Im allgemeinsten Konzept mit dem Namen HOPSA [BDS92] sind beliebige Stromfunktionen zugelassen, die als Argumente und Ergebnisse nicht einzelne Datenelemente, sondern Ströme von Daten haben. Eine Stromfunktion kann für einen Berechnungsschritt
beliebig viele Daten von den Strömen lesen und auf die Ergebnisströme schreiben. Ein
¨ahnlicher Ansatz wird auch in [Zen92] verfolgt, wo sequentielle Prozeduren in einer
funktionalen Koordinationsschicht beliebig von ihren Eingangsströmen lesen und auf ihre
Ausgangsströme schreiben können. Direkte Implementierungen existieren aber zu keinem
der beiden Konzepte.
72
Übersetzung von FASAN in Java
Das Prinzip der hinter- und nebeneinandergeschalteten Stromfunktionen wurde jedoch
in Implementierungen für die Sprache (auf Grundlage von Smalltalk, [Vei90]), für die
Sprache Gent (auf Basis von ParMod, [Rau92]) und in der ersten Version von FASAN (auf
Basis von C mit PVM, [Ebn94]) verwendet. und Gent konnten allerdings auf Grund
ihrer Implementierungsplattformen parallele und verteilte Ausführung nur simulieren.
All diese Systeme hatten einige Nachteile, die erst für große und mit FASAN/C echt
verteilt laufende Anwendungen in Erscheinung traten:
1. Umständliche elementare Operationen: Die ausschließliche Abstützung auf
Stromfunktionen als elementare Operationen verlangte die Kapselung aller Daten
in Strömen. Dies machte die Programmentwicklung, insbesondere die Anpassung
der externen eingebundenen Funktionen, ziemlich umst¨andlich und brachte zus¨atzlichen Overhead in die Systeme.
2. Hoher Speicherverbrauch: Alle genannten Systeme erzeugten zur Laufzeit direkt
den Datenflussgraphen als dynamische Datenstruktur. Das hatte zwar den Vorteil,
dass bei mehrfacher Berechnung des gleichen Funktionsaufrufs die Funktionsknoten des Datenflussgraphen immer wieder für verschiedene Daten verwendet, die
Kosten für das Erzeugen des Graphen bei den folgenden Iterationen gespart und die
direkten Kommunikationswege mehrfach genutzt werden konnten. Der gravierende
Nachteil dabei aber ist der hohe Speicherverbrauch, wenn die rekursiven Funktionsaufrufe bis in große Aufruftiefen als Datenflussgraph entfaltet werden müssen. Der
Programmierer war somit etwa bei FASAN/C immer gezwungen, die Auffaltung
des Graphen von Hand zu begrenzen.
3. Programmtechnische Nachteile: Nicht-nullstellige Kabelbaumkonstruktoren
durften beispielsweise nur in einem Zweig einer Fallunterscheidung vorkommen.
Diese Schwierigkeit wurde aber in einer neueren Version von FASAN/C (das in
[EPZ96, EP96] zu Grunde liegt) behoben. Auch in der operationellen Semantik im
vorigen Kapitel tritt diese Einschr¨ankung dank der Replikationsknoten nicht mehr
auf.
Als Parameter für externe Funktionen waren nur die einfachen Datentypen von
C sowie Arrays davon erlaubt. Höhere Datenstrukturen (structs) wurden nicht direkt unterstützt, sondern konnten nur als Byte-Felder und damit nur innerhalb einer
Rechnerarchitektur-Familie übertragen werden.
4. Problematische Fehlersuche: Ein weiterer Nachteil lag ebenfalls in der direkten
Implementierung der Datenflussgraph-Datenstrukturen begründet: Mangels Werkzeugen zum Debugging oder zur graphischen Analyse bereitete die Fehlersuche
in einem FASAN/C-Programm große Schwierigkeiten. Der Programmierer wurde
n¨amlich mit den Interna des Datenfluss-Laufzeitsystems im C-Debugger konfrontiert, welches leicht durch fehlerhaft programmierte externe Funktionen zum Absturz gebracht werden konnte.
Es stellte sich weiterhin jedoch heraus, dass für die Programmierung der im Rahmen der
5.2 Auswahl der Zielsprache
73
numerischen Simulation betrachteten Anwendungen die Funktionen vollkommen ausreichend sind, die in gewohnter Weise von jedem Eingang ein Datenelement lesen und auf
jeden Ausgang ein Datum schreiben. Dies war selbst in Anwendungen mit so komplizierten Datenabh¨angigkeits- und Kommunikationsstrukturen wie dem in [Pfa97] mit FASAN/C entwickelten Helmholtz-Löser auf Basis dünner Gitter der Fall. Schließlich sind
auch Methoden aus numerischen Bibliotheken, die als externe Funktionen eingebunden
werden können, keine Stromfunktionen, sondern benötigen für jeden Eingabeparameter
genau ein Argument und liefern für jeden Ausgabeparameter genau ein Ergebnis.
So lag es also nahe, in dem hier beschriebenen neuen FASAN-System auf Stromfunktionen zu verzichten. Dies haben wir in der operationellen Semantik auch schon dadurch berücksichtigt, dass für jeden Puffer eines Datenflussgraphen die Bedingung
gilt, dass also jeder Puffer (in der Terminologie der Netze) 1-beschr¨ankt
und der Datenflussgraph damit sicher ist.
5.2 Auswahl der Zielsprache
Obwohl es sich um eine prototypische Entwicklung handelte, hat sich das Implementieren des ersten FASAN/C-Systems als recht aufwendig sowohl in der Programmierung als
auch beim Suchen nach Fehlern herausgestellt. Neben den typischen Fehlern bei eigener
Programmierung der Speicherverwaltung trug auch die große semantische L ücke zwischen den verteilten Funktionsaufrufen (FASAN) und dem Nachrichtensystem (PVM) zu
erhöhtem Debug-Aufwand bei. Es war also ein Zielsystem gefragt, das möglichst neben
einer automatischen Speicherverwaltung auch verteilte Funktions- bzw. Prozeduraufrufe
bereitstellt.
Das neue Zielsystem sollte mindestens so gut portabel sein wie PVM und eine einfache
Installation ermöglichen. Denn dies spielt eine wesentliche Rolle für die Akzeptanz eines
Konzepts oder Werkzeugs. Erfordert ein System viel Installationsaufwand oder größere
Anpassungen an eine Hardware-Architektur, hat es kaum Aussicht auf Erfolg. Andererseits durfte für den Bereich der numerischen Simulation, in dem meist langdauernde Berechnungen auf sehr großen Datenstrukturen vorgenommen werden, die Effizienz nicht
außer Acht gelassen werden, was Skript-Sprachen wie Perl von vornherein ausschloss.
Den aufgeführten Kriterien schien uns Java [AG98, Bis98, Sun98] als Zielsystem zur
Zeit am besten zu genügen. Es vereint eine Reihe der benötigten Konzepte, die sowohl
die Implementierung als auch die Verwendung von FASAN vereinfachen.
Ein willkommener Vorteil von Java ist die erstaunlich weite Verbreitung sowohl im akademischen als auch im kommerziellen Bereich. Es ist mittlerweile ein Quasi-Standard und
in den meisten Rechnerumgebungen installiert, sodass hier gar keine zus¨atzliche Installationsarbeit anf¨allt. Ein besonderer Pluspunkt ist die Portabilität von Java-Programmen,
die sich gleich über drei Ebenen erstreckt [Rou97]:
74
Übersetzung von FASAN in Java
1. Quellcode-Portabilit¨at: Alle Grunddatentypen haben eine fest definierte L¨ange, Initialisierungswerte und Arithmetik. Damit liefert ein Java-Programm identische Ergebnisse unabh¨angig von Wortl¨angen der Hardware.
2. CPU-Portabilit¨at: Das als Byte-Code kompilierte Programm ist unver¨andert auf allen CPUs ausführbar, im Gegensatz zu Maschinencode von C, C++ etc.
3. Betriebssystem-Portabilit¨at: Systemaufrufe, sei es für Netzwerkverbindungen, für
die Ein- und Ausgabe, für die Benutzeroberfl¨ache etc., sind in Java in standardisierten und Kompatibilit¨atstests unterworfenen Klassen gekapselt und sparen bei der
Portierung und in heterogenen Umgebungen immens Programmieraufwand.
Java verfügt auch über die gewünschte automatische Speicherverwaltung, die sowohl
bei der Implementierung des FASAN-Systems als auch sp¨ater beim Entwickeln von
FASAN-Programmen und bei der Fehlersuche viel Zeit erspart.
Für das FASAN-Konzept der externen Funktionen bietet der sogenannte Serialisierungsmechanismus, den Java nicht nur zum Verschicken von einfachen Daten wie Zahlen oder
Feldern, sondern für beliebige und sogar zyklische Datenstrukturen über Rechnernetze
verwendet, große Flexibilit¨at. Weiterhin ist FASANs Sprachunabh¨angigkeit von den externen Funktionen durch die standardisierte Einbindungsmöglichkeit von C-, C++- und
damit auch FORTRAN-Code in FASAN-Programme über die JNI-API (Java Native Interface) gegeben.
Bezüglich der Unterstützung paralleler und verteilter Systeme findet man in Java gleich
beides: Unterstützung von Threads [OW97a] mit sprachintegrierten Synchronisationskonstrukten für gemeinsamen Speicher, und das Klassen-Paket für entfernte Methodenaufrufe (RMI, [Far98]) samt verteilter Speicherverwaltung für verteilte Systeme und
Rechnernetze.
Der Abstraktionsgrad der Java-Konzepte für Threads und entfernte Methodenaufrufe
reicht gerade aus, um ein Rapid Prototyping zu ermöglichen. Die FASAN-Funktionen
spiegeln sich dabei ziemlich direkt in den erzeugten Java-Funktionen wider, wodurch die
Fehlersuche in FASAN-Programmen mit dem Java-Debugger durchführbar ist. Für jeden
Funktionsaufruf in FASAN mit Lokations-Annotation kann prinzipiell ein separater Kontrollfluss als Thread erzeugt oder bei verteilter Ausführung auf einem Rechnernetz ein
entfernter Methodenaufuf verwendet werden, um die nötige Parallelit¨at zu erreichen.
Wie das Java Grande Forum Panel [Pan98] darlegt, werden ernsthafte, auch über den
akademischen Bereich hinausgehende Überlegungen angestellt, wie Java als Standardsprache für numerisches Hochleistungsrechnen verwendet bzw. dazu noch tauglich gemacht werden kann. Ein wesentliches Argument für Java ist dabei seine Ausgewogenheit zwischen vertrauten Konzepten und der Beseitigung von zeitfressenden Fehlerquellen
durch neuartige Konzepte.
Hier stellt sich natürlich die Frage, ob bei so viel Unterstützung von der Java-Seite überhaupt Bedarf für FASAN besteht. FASANs Beitrag liegt in der Vereinfachung der Koordination für parallele oder verteilte Ausführung. Denn bei der Synchronisation und dem
5.2 Auswahl der Zielsprache
75
entfernten Methodenaufruf handelt es sich um vom Java-Programmierer explizit zu verwendende Konstrukte, mit (fast) allen Fehlerquellen, die explizite verteilte Programmierung mit sich bringt. Die Datenfluss-Semantik des vorigen Kapitels aber hat gezeigt, dass
gerade solche hinterh¨altigen Fehler mit FASAN gar nicht passieren können. Abgesehen
davon sparen wir mit jedem auf die implizite Seite einer Sprache verschobenen Konstrukt Verwaltungsaufwand im Programm und gewinnen mehr Flexibilit¨at für kurzfristige
oder experimentelle Änderungen. Bei FASAN muss z. B. keinerlei Code zum Senden und
Empfangen von Nachrichten geschrieben werden, der für die direkten Verbindungen und
den Transport der hierarchischen Datenstrukturen nötig ist. Die drei wichtigsten Argumente für FASAN sind aber die im vorigen Kapitel nachgewiesene automatisch erreichte
Datenlokalit¨at, die direkte Kommunikation, sowie der erhöhte Parallelit¨atsgrad trotz komfortabler und für die Algorithmen logischer Aufschreibung.
5.2.1 Auswahl der Parallelisierungsplattform
Untersuchen wir das als Parallelisierungsplattform recht komfortabel erscheinende RMI
etwas genauer. Speziell interessieren die Kosten von RMI bei zwei einfachen Beispielen,
die aber beide für die von FASAN anvisierten Anwendungen relevant sind.
Große Arrays von Grundtypen, vor allem float[] und double[], werden in numerischen Anwendungen oft verwendet, also auch bei FASAN in den externen Funktionen.
Die Dauer des Transports mittels RMI aus dem JDK 1.2 auf einer Ethernet-Verbindung
zwischen Workstations vom Typ Sun Ultra-60 sind in Abb. 5.1 dargestellt. Für kleine Arrays ist die Übertragungszeit ann¨ahernd konstant. Auff¨allig ist das starke Ansteigen der
Zeit ab 512 Array-Eintr¨agen, ab der die Seitengröße des physikalischen Speichers der
Maschine überschritten wird. Danach steigt der Zeitbedarf etwa proportional zur ArrayGröße an.Ähnliche Beobachtungen wurden auch in der Arbeit [VvM 98] gemacht, wo
zus¨atzlich durch Modifikation im JIT Compiler (Just In Time Compiler) von Java bedeutende Effizienzsteigerungen erreicht wurden. Wenn wir das Standard-JDK verwenden,
scheint es also günstiger zu sein, große Arrays bei entfernten Methodenaufrufen in kleinere Teile zu zerlegen. Ein weiterer Effekt waren die im Experiment beobachteten sehr
starken Streuungen der Zeiten, die teilweise mit der variierenden Netzlast begründet werden können.
Eine noch wichtigere Rolle spielen hierarchische Objektstrukturen im FASAN-System,
und zwar sowohl für die von FASAN definierten hierarchischen Datentypen als auch für
Strukturen, die in den externen Funktionen verwendet werden, etwa dünnbesiedelte Matrizen. Bei den Versuchsergebnissen in Abb. 5.2 f¨allt auf, dass die Setup-Zeiten für die
Kommunikation vernachl¨assigbar sind. Die Zeiten für dieÜbertragung einer Datenstruktur scheint aber leider leicht überproportional mit ihrer Größe anzusteigen.
Des Weiteren sehen wir, dass die Zeit zur Übertragung der gleichen Datenmengen in einem Baum im Schnitt um eine Größenordnung teurer ist als in einem flachen Array. Verantwortlich sind Mechanismen bei Javas Serialisierung, also beim Packen der Baumstruk-
76
Übersetzung von FASAN in Java
262144
65536
RMI mit double[]-Argument und -Ergebnis
RMI mit double[]-Argument
RMI mit double[]-Ergebnis
16384
Dauer [msec]
4096
1024
256
64
16
4
1
2
4
8
16
32
64
128 256 512 1024 2048 4096 8192 16384
Arraylaenge
Abbildung 5.1 Dauer eines RMI-Aufrufs mit einem Array als Argument bzw. Ergebnis
tur. Die Datenstruktur wird automatisch auf Zyklen überprüft, und für jedes Teil-Objekt
muss zus¨atzlich Typinformation übertragen werden.
Hier wie in einer weiteren Quelle von Effizienzeinbußen l¨asst aber die Klassenhierarchie
von RMI Optimierungsmöglichkeiten zu:
1. Für Objekte mit bestimmten Eigenschaften, etwa Objekte ohne zyklische Referenzen wie die hierarchischen Datentypen von FASAN, können effiziente Methoden
zur Serialisierung und Deserialisierung von Hand programmiert werden. Dieser
Weg wird sich im FASAN-System selbst zur Implementierung globaler Referenzen als sehr nützlich erweisen.
2. Ein weiterer Schwachpunkt von RMI ist die aktuelle TCP/IP-basierte Implementierung des Protokolls JRMP, das standardm¨aßig für entfernte Methodenaufrufe eingesetzt wird. Kommunikationsverbindungen (die Sockets) werden nur für jeweils
einen RMI-Aufruf verwendet und dann wieder geschlossen [Pan98]. Zus¨atzliche
Kosten verursachen Sicherheits- und Ausfallprüfungen im Protokoll. Jedoch l¨asst
die Klassenhierarchie von RMI die Möglichkeit offen, mit etwas Programmieraufwand eine eigene Socket Factory zu schreiben, die Sockets für mehrfache Kommunikationen wiederverwenden kann [Sun98].
Die nur einmalige Verwendung von Sockets liegt in der Konzeption von RMI als ClientServer-System für unsichereÜbertragungskan¨ale begründet. Diese eingebaute Sicherheit
kann unter Umst¨anden auch für FASAN vorteilhaft sein, wenn weit entfernte Rechner
kooperativ ein Programm auswerten (Global Computing) und auf diese Sicherheits- und
Redundanzmechanismen angewiesen sind.
5.2 Auswahl der Zielsprache
77
262144
RMI mit Baum-Argument und -Ergebnis
RMI mit Baum-Argument
RMI mit Baum-Ergebnis
65536
Dauer [msec]
16384
4096
1024
256
64
16
4
1
2
3
4
5
6
7
8
Baumtiefe
9
10
11
12
13
14
Abbildung 5.2 Dauer eines RMI-Aufrufs mit einem Bin¨arbaum als Argument bzw. Ergebnis
Da also auf Protokoll-Ebene Schw¨achen von RMI für das Hochleistungsrechnen teilweise
beseitigt werden können, stellt sich die Frage, ob es sinnvolle Alternativen zu RMI auf
gleicher Abstraktionsebene gibt.
Eine solche Alternative w¨are JavaSpaces [Far98], eine Implementierung des TupelraumModells, das auch in Linda Verwendung findet [Wes92]. JavaSpaces bietet zwar einen
höheren Abstraktionsgrad von der parallelen Plattform als RMI, RMI jedoch eignet sich
für unsere Zwecke besser für die Auswertung eines FASAN-Programms, da ein verteilter
Aufruf in FASAN direkt einem RMI-Aufruf entspricht. Da JavaSpaces auf RMI aufsetzt,
ist durch seinen Einsatz auch keine Effizienzsteigerung, sondern eher zus¨atzlicher Overhead zu erwarten.
Eine zweite Alternative w¨are CORBA (Common Object Request Broker Architecture,
[Vin96]), das mit dem IIOP (Internet Inter ORB Protocol) eine RMI sehr ¨ahnliche API hat.
Allerdings ist zu berücksichtigen, dass CORBA mit möglichst vielen Sprachen vertr¨aglich
sein muss. Daher bietet die CORBA IDL (Interface Definition Language) im Prinzip nur
den kleinsten gemeinsamen Nenner der Sprachen. Spezielle Java-spezifische Optimierungen des Protokolls sind damit nicht möglich. Außerdem ist auch CORBA — ebenso wie
RMI — in erster Linie für Client-Server-Computing gedacht ist, mit den entsprechenden
Effizienzeinbußen. Zudem müsste der Anwender sich für CORBA einen passenden ORB
(Object Request Broker) besorgen und installieren.
Die Gründe, die letztlich für die Auswahl von RMI den Ausschlag gegeben haben, sind:
1. RMI ist fester Bestandteil des JDK. Damit ist dann auch FASAN sofort überall dort
lauff¨ahig, wo ein JDK installiert ist.
78
Übersetzung von FASAN in Java
2. RMI ist zwar nicht besonders effizient; die Klassenhierarchie bietet jedoch
Möglichkeiten, bei Bedarf stellenweise von Hand Optimierungen einzufügen, etwa die Zerteilung großer Datenobjekte für die Serialisierung.
3. Als Quasi-Standard für die verteilte Programmierung mit Java steht RMI auch im
Zentrum von Verbesserungsbestrebungen, von denen FASAN dann direkt profitieren kann. So wurden durch zahlreiche Forschungsgruppen schon Überlegungen
angestellt und Vorschl¨age unterbreitet, wie eine optimierte Implementierung von
RMI für den Bereich des technisch-wissenschaftlichen Rechnens zu gestalten ist
[Pan98].
Somit ist also RMI, gerade für prototypische Implementierungen, die zur Zeit beste Alternative.
5.3 Übersetzung in ein sequentielles Grundprogramm
Ausgangspunkt für die Übersetzung ist die abstrakte Syntax aus Kapitel 3 auf Seite 39.
Wir müssen festlegen, wie zum einen die rekursiven Funktionen aus zu übersetzen
sind, und zum anderen, wie die hierarchischen Datentypen aus mit ihren Konstrukto ren aus
und Selektoren aus zu implementieren sind, damit sie der Semantik der
Datenflussgraphen und dem Kabelbaum-Prinzip Rechnung tragen.
.(
5.3.1 Rekursive Funktionen
Alle rekursiven Funktionen werden in einer Klasse zusammengefasst, die wir den
FASAN-Server nennen. Ihre Kennzeichen sind Zuweisungen mit nicht-geschachtelten
Ausdrücken, explizite Variablen für Zwischenergebnisse sowie bedingte Ausdrücke als
einzige Kontrollstruktur. Prinzipiell lassen sie sich ziemlich direkt in Java-Methoden
übertragen. Greifen wir zur Verdeutlichung das Beispiel 3.5 von Seite 34 für die Iteration über B¨aumen auf.
Beispiel 5.1 Die Java-Methode, die aus der FASAN-Funktion iterate (ohne das
Schlüsselwort recycle) im FASAN-Server generiert wird, benennt und typisiert
alle Variablen, auch die Zwischenergebnisse:
public class TreeExample {
[...]
public Tree iterate (Tree t) {
// Typvereinbarungen aller Variablen
Tree t_TAG_;
Tree t0;
boolean $2;
Node $1;
5.3 Übersetzung in ein sequentielles Grundprogramm
79
//
$1
$2
if
Liste aller Zuweisungen
= t.val();
= javaFunctions.stop($1);
($2) {
t_TAG_ = t;
} else {
t0 = javaFunctions.dc(t);
t_TAG_ = iterate(t0);
}
return t_TAG_;
}
}
Die Typen von Variablen ergeben sich aus den Ergebnisparametern der aufgerufenen
Funktion auf der rechten Seite von Zuweisungen. Namen für Hilfsvariable können wir
einfach mittels Durchnummerieren innerhalb einer Funktion vergeben.
Die Struktur des FASAN-Programms ist im erzeugten Java-Code besser zu erkennen,
wenn eine FASAN-Funktion auch genau einer Java-Methode entspricht. Für die Zweige
von Fallunterscheidungen verzichten wir daher auf Hilfsfunktionen, die den Funktionsknoten entsprechen würden. Diese Hilfsfunktionen sind im Datenflussgraphen
im Grunde nur ein technisches Hilfsmittel, um für Fallunterscheidungen die strikte Abbildung des Schaltens von Funktionsknoten zu umgehen.
Ein programmtechnischer Vorteil entsteht dadurch auch bei endrekursiven Aufrufen im
Rumpf der Funktion. Da dieser Aufruf nicht in einer Hilfsfunktion steht, ist die Übersetzung in eine effiziente Schleife sehr leicht:
1. Der Funktionsrumpf wird in eine Endlos-Schleife eingeschlossen.
2. Alle Zweige, die nicht die endrekursiven Aufrufe enthalten, werden um ein
break; erg¨anzt, also um den Abbruch der Iteration.
3. Bei den anderen Zweigen treten an Stelle des endrekursiven Aufrufs Zuweisungen
von den Aufrufparametern an die formalen Parameter.
Beispiel 5.2 Belassen wir in der FASAN-Funktion iterate aus dem vorigen Beispiel
das Schlüsselwort recycle zur Optimierung der Endrekursion, wird eine Schleife
erzeugt.
public Tree iterate (Tree t) {
Tree t_TAG_;
Tree t0;
boolean $2;
Node $1;
while (true) {
$1 = t.val();
$2 = javaFunctions.stop($1);
80
Übersetzung von FASAN in Java
if ($2) {
t_TAG_ = t;
break;
} else {
t0 = javaFunctions.dc(t);
t = t0;
}
}
return t_TAG_;
}
Hat eine Funktion mehr als einen Ergebnisparameter, ist allerdings eine neue Klasse
FRAME__ erforderlich, die alle diese Ergebnisse zusammenfasst und deren Instanzen
in diesen F¨allen als Ergebnisse verwendet werden. Eine solche Klasse wird für jede re
kursive Funktion erzeugt.
Beispiel 5.3 Um in einem verteilten Algorithmus eine Grundlage für die Lastverteilung
zu gewinnen, muss eine Funktion, die einen Baum adaptiv erweitert, zus¨atzlich zu
dem erweiterten Baum eine Datenstruktur load mit Sch¨atzungen der Rechenlast
der einzelnen Baumknoten zurückliefern. Mit dem externen Typ LTree ergibt eine
solche Funktion
type LTree extern Java;
function ExtendTree (T:Tree; destin:int[]; opts:Options)
-> T’:Tree; load’:LTree
die Java-Klasse
public class FRAME__ExtendTree {
public transient Tree T;
public transient int[] destin;
public transient Options opts;
public Tree T_TAG_;
public LTree load_TAG_;
public FRAME__ExtendTree() { }
public FRAME__ExtendTree(Tree T_TAG_, LTree load_TAG_){
this.T_TAG_ = T_TAG_;
this.load_TAG_ = load_TAG_;
}
}
Im Vorgriff auf die verteilte Ausführung markieren wir die Eingangsparameter mit
transient, damit sie nicht kopiert und verschickt werden, wenn der FRAME als Ergebnis eines entfernten Methodenaufrufs zurückgesendet wird. Solche FRAME-Klassen
werden wir im Übrigen auch zur Parameterübergabe an Threads im Abschnitt 5.4 gebrauchen können.
5.3 Übersetzung in ein sequentielles Grundprogramm
81
5.3.2 Hierarchische Datentypen
.(
Für jeden hierarchischen Datentyp
legen wir eine Verpackungs“-Klasse mit
”
dem Namen an, die den Puffern des Datenflussgraphen entspricht. Sie enth¨alt nur eine Variable content, die den tats¨achlichen Inhalt — also die Belegung des Puffers
— repr¨asentiert. Außerdem enth¨alt sie zwei Konstruktoren: einen mit Parametern für
den Fall, dass der Inhalt schon vorliegt, und einen ohne Parameter, sodass im Sinn der
Datenflussgraph-Semantik der Puffer erzeugt werden kann, auch wenn noch kein Inhalt
verfügbar ist. Des Weiteren enth¨alt die Klasse alle Selektor- und Pr¨adikatfunktionen, die
in der Datentyp-Definition vereinbart wurden.
Beispiel 5.4
Der hierarchische Datentyp für Bin¨arb¨aume
type Tree = Leaf (leafVal:Node)
| Branch (branchVal:Node; left,right:Tree)
erzeugt die Verpackungsklasse
public class Tree {
Tree__CONTENT content;
public Tree() { }
public Tree(Tree__CONTENT c) {
content = c;
}
public boolean isLeaf() {
return (content instanceof Leaf);
}
public Node leafVal() {
return ((Leaf) content).leafVal;
}
public boolean isBranch() {
return (content instanceof Branch);
}
public Node branchVal() {
return ((Branch) content).branchVal;
}
public Tree leftTree() {
return ((Branch) content).leftTree;
}
public Tree rightTree() {
return ((Branch) content).rightTree;
}
}
.(
ist __CONTENT nur ein Java-Interface ohne
Für jeden hierarchischen Datentyp
Variablen und Methoden. Implementiert wird dieses Interface durch Klassen, die wir für
, die diesen Datentyp aufbauen, generieren.
die Konstruktoren aus
82
Übersetzung von FASAN in Java
Beispiel 5.5 Der Konstruktor Branch aus dem hierarchischen Datentyp Tree führt auf
die Java-Klasse
public class Branch implements Tree__CONTENT, Serializable {
Node branchVal;
Tree leftTree;
Tree rightTree;
public Branch() { }
public Branch(Node branchVal,
Tree leftTree, Tree rightTree) {
this.branchVal = branchVal;
this.leftTree = leftTree;
this.rightTree = rightTree;
}
}
5.3.3 Externe Funktionen und Typen
Der FASAN-Compiler erzeugt zus¨atzlich zu der Klasse für den FASAN-Server (z. B.
TreeExample) ein Java-Interface mit der Namensendung ExternFunctions (z. B.
TreeExampleExternFunctions), in dem alle externen Funktionen aufgeführt sind,
die das FASAN-Programm benötigt. Eine Instanz einer Klasse, die dieses Interface implementiert, muss dem FASAN-Server bei dessen Erzeugung übergeben werden.
Beispiel 5.6
In unserem Beispiel beinhaltet der FASAN-Server also
class TreeExample {
public static TreeExample fasanFunctions;
public static TreeExampleExternFunctions javaFunctions;
public int myHost = 0, numHosts = 1;
public TreeExample (TreeExampleExternFunctions teef) {
fasanFunctions = this;
javaFunctions = teef;
}
[...]
}
Die Klassenvariablen fasanFunctions und javaFunctions werden mitgeführt, um nicht in jeder Klasse der hierarchischen Datentypen eine Variable zum
Zugriff auf die rekursiven und externen Funktionen halten zu müssen.
Zu Zwecken der Initialisierung oder auch für optimierte sequentielle Funktionen hat
es sich als angenehm für den Programmierer erwiesen, hierarchische Datenstrukturen
von FASAN auch in externen Funktionen aufbauen und zerlegen zu können. Um nicht
immer zwei Instanzen (Verpackung und Inhalt) kreieren zu müssen, versehen wir die
Konstruktor-Klasse mit einer Factory-Methode.
5.3 Übersetzung in ein sequentielles Grundprogramm
83
Beispiel 5.7
Zur Erzeugung eines Baums in externen Funktionen ist folgende
Factory-Methode in der Klasse Branch nützlich, die die Erzeugung mittels
Branch.create(n,l,r) erlaubt:
public static Tree create (Node branchVal,
Tree leftTree, Tree rightTree) {
return
new Tree (new Branch(branchVal, leftTree, rightTree));
}
5.3.4 Funktionen h¨
oherer Ordnung
Programmierer, die schon mit funktionalen Sprachen gearbeitet haben, wissen die Ausdrucksm¨achtigkeit von Funktionen höherer Ordnung zu sch¨atzen. Auch in FASAN
können Funktionen höherer Ordnung nützlich sein, um etwa Konzepte des Bird-MeertensFormalismus [Bir88] oder der Skeletons [Col98] zu implementieren.
Eine einfache Möglichkeit derÜbersetzung von Funktionen höherer Ordnung nach Java
würde das Pizza-Paket bieten [OW97b], das als Oberklasse von Java erlaubt, Methoden
als Argumente und Ergebnisse von anderen Methoden zu verwenden. Da es aber nicht
zum Standard des JDK gehört, müsste es extra installiert werden, was wir ja gerade im
Sinn der einfachen Installation und Portabilit¨at des FASAN-Systems vermeiden wollen.
Für Funktionen höherer Ordnung in FASAN benötigen wir zwei Mechanismen:
Wir müssen Funktionen, die Parameter anderer Funktionen sind, oder allgemei ner Abschlüsse (partielle Funktionsanwendungen, wie sie als
s in der
Datenflussgraph-Semantik entstehen), handhaben können. Dafür brauchen wir ein
Java-Interface als Typ für derartige Parameter und Variablen. Wenn die Typprüfung
schon zur Übersetzungszeit stattfindet — was auf jeden Fall sinnvoll und effizienter
als Laufzeit-Prüfung ist — genügt hier ein einziges
interface FRAME extends Runnable{ }.
Diese Interface ist dann der Typ jedes Parameters und jeder Variablen, die Funktionstyp hat. Zur Laufzeit können diesen Parametern und Variablen dann beliebige
Instanzen davon abgeleiteter FRAMEs zugewiesen werden.
Um passende konkrete Abschluss-Objekte in Java erzeugen zu können und um an
einen bestehenden Abschluss weitere Argumente binden zu können, erweitern wir
die Klassen FRAME__ , die für jede rekursive Funktion aus generiert
werden.
Wir erweitern zun¨achst die FRAMEs um ein boolean-Feld names curried, dessen
L¨ange die Anzahl der Argumente der entsprechenden Funktion ist. Das -te Element dieses Feldes gibt an, ob das -te Argument der Funktion noch aussteht, also durch Currying
ausgelassen wurde. Zus¨atzlich ist eine run()-Methode notwendig, mit der die Funktion
84
Übersetzung von FASAN in Java
zum Rechnen angestoßen wird, wenn alle Argumente vorhanden sind. Dies entspricht im
Datenflussgraphen der Auswertung eines -Knotens.
Beispiel 5.8
In die Funktion mapDown aus Beispiel 3.10,
function
mapDown (f:(Object,Object)->Object; s:Object; t:Tree)
-> t’:Tree,
kann als erstes Argument z. B. eine Funktion
function
SolveDown (parent:Object; node:Object) -> newNode:Object
eingesetzt werden, deren Klasse beinhalten muss:
class FRAME__SolveDown implements FRAME {
boolean[2] curried;
Object parent;
Object node;
Object newNode;
public void run() {
if (!curried[0] && !curried[1])
newNode = SolveDown(parent,node);
}
}
Es sind also FRAME-Klassen nicht nur für rekursive Funktionen , sondern auch
für alle externen Funktionen
eines FASAN-Programms zu generieren. Zus¨atzlicher Code ist auch an folgenden Stellen der Methoden im FASAN-Server zu erzeugen:
1. dort, wo eine Funktionsanwendung partiell ist, wo also ein neues Abschluss-Objekt
vom Typ FRAME erzeugt und mit den hier schon übergebenen Argumenten initialisiert werden muss; und
2. an den Stellen, wo eine Funktionsvariable — also ein Abschluss — auf eine
Argument-Liste angewendet wird. Hier ist zu prüfen, ob nur die neuen Argumente in den FRAME einzutragen sind, oder ob die run()-Methode angestoßen wird,
falls alle Argumente zur Verfügung stehen.
Da diese Konzepte für Funktionen höherer Ordnung nur programmiertechnische Vorteile bringen, aber für die Parallelisierung nichts Neues beitragen, verzichten wir hier auf
weitere Beispiele und die detaillierte Angabe des Java-Codes.
5.4 Erweiterungen für gemeinsamen Speicher mit Threads
85
5.4 Erweiterungen für gemeinsamen Speicher mit
Threads
In diesem Abschnitt sollen Techniken vorgestellt werden, mit deren Hilfe die parallele Ausführung eines FASAN-Programms auf mehreren Prozessoren mit gemeinsamem
Speicher — also typischerweise auf SMP-Maschinen — ermöglicht wird. Wir werden
den in Java integrierten Thread-Mechanismus verwenden. Dabei ist bezüglich der Lokationsfunktionen im FASAN-Programm nur entscheidend, dass überhaupt neue Kontrollflüsse erzeugt werden. Wo ein neuer Thread ausgeführt wird, bestimmt ab Java 2 (JDK1.2)
der Scheduler des Betriebssystems, da Java ab dieser Version native POSIX-Threads unterstützt [AG98, Sun98]. Liefert der Lokationsaufruf einen Wert größer oder gleich Null,
so erzeugt der FASAN-Code einen neuen Thread. Ansonsten erfolgt der Funktionsaufruf
auf dem Stack im selben Kontrollfluss.
Zun¨achst halten wir fest, dass Funktionen höherer Ordnung bei der Implementierung als
paralleles und verteiltes Programm keine technischen Konsequenzen haben. Die FRAMEKlassen, die im vorigen Abschnitt schon erkl¨art wurden, sind zum Programmstart für alle
rekursiven und externen Funktionen gem¨aß dem SPMD-Modell (single program multiple
data) auf allen Prozessoren bzw. Rechnern verfügbar und können damit die benötigten
Abschlüsse bilden. Ein Abschluss selbst führt noch keine Berechnungen aus; er tr¨agt also
nichts zur Parallelit¨at eines Programms bei.
Auch externe Funktionen, als sequentiell vorausgesetzt, können wie bisher eingebunden
werden. In diesem und dem n¨achsten Abschnitt müssen wir uns also nur um die Anpassung der FASAN-Serverklasse und der Klassen für die hierarchischen Datentypen
kümmern.
5.4.1 Rekursive Funktionen
Thread-Systeme erledigen das Scheduling und die Verteilung der Threads (in FASAN sind
das die mit Lokationen versehenen Funktionsaufrufe, siehe Seite 34) auf die verfügbaren
Prozessoren selbst. Damit können wir uns den expliziten Aufbau des Datenflussgraphen
und die Verwaltung der Funktionsknoten zur Laufzeit sparen.
Für jede rekursive Funktion, die mindestens einmal im FASAN-Programm mit einer Lokation aufgerufen wird, erzeugen wir eine Methode, die den Funktionsaufruf in einem
eigenen Thread starten kann. Ist das Ergebnis der Lokation mindestens Null, so wird der
Funktionsaufruf dann parallel in einem neuen Thread ausgeführt, und der Aufrufer kann
unmittelbar nach dem Start des neuen Threads die n¨achsten Funktionsaufrufe ausführen.
Beispiel 5.9
Für eine Funktion
Solve (par:Node; T:Tree; destin:int[]; opts:Options)
-> T’:Tree
86
Übersetzung von FASAN in Java
erzeugen wir zus¨atzlich eine Methode im FASAN-Server, die als zus¨atzliches Argument die Lokation bekommt und entsprechend einen neuen Thread starten kann:
public Tree Solve__THREAD (Node par, Tree T, int[] destin,
Options opts, int newHost) {
if (newHost < 0)
// sequentieller Aufruf, kein Thread erwünscht
return Solve(par, T, destin, opts);
// sonst leere Verpackung für Ergebnis anlegen
FRAME__Solve $frame = new FRAME__Solve(new Tree());
// Argumente initialisieren
$frame.par = par;
$frame.T = T;
$frame.destin = destin;
$frame.opts = opts;
$frame.newHost = newHost;
// neuen Thread erzeugen und starten
new Thread($frame).start();
// warten, bis der Thread wirklich gestartet ist
boolean threadStarted = false;
while (! threadStarted) {
try {Thread.sleep(1);} catch(Exception $e) { }
synchronized ($frame) {
threadStarted = $frame.threadStarted;
}
}
return $frame.T_TAG_;
}
Nachdem der Thread gestartet ist, kann diese Methode sofort wieder zurückkehren. Im
Ergebnisparameter ist dabei nur eine Verpackung Tree ohne Inhalt vorhanden. Dieser
Inhalt wird von dem Thread erst im Laufe seiner Berechnung gefüllt.
5.4.2 Hierarchische Datentypem
Um die Parallelit¨at wie im Datenflussgraphen auszunützen, ist es wichtig, dass Ergebnisse
von Funktionen zun¨achst nur Verpackungen“ sind (analog zu den Puffern im Graphen als
”
Verpackung für Belegungen). Diese Verpackungen werden erst sp¨ater von dem Thread,
der den Funktionsaufruf abarbeitet, mit Daten gefüllt.
Gleichzeitig ist aber die Korrektheit des FASAN-Programms zu gew¨ahrleisten. Der
Selektor- oder Pr¨adikataufruf, der tats¨achlich auf den Inhalt der Verpackung zugreift,
muss warten, bis der oben genannte Thread die Daten eingetragen hat. Dies l¨asst sich
dadurch erreichen, dass die Selektor- und Pr¨adikat-Funktionen für ein Verpackungsobjekt
5.4 Erweiterungen für gemeinsamen Speicher mit Threads
87
in dem gleichen Monitor [HG89] zusammengefasst sind wie der Thread, der die Daten
bereitstellt. Als Monitor bezeichnet man ein Programmstück, das zu jedem Zeitpunkt maximal ein Thread ausführen kann. Java ordnet jedem Objekt einen Monitor zu [OW97a].
Der Code, den dieser Monitor umfasst, kann über mehrere Klassen verteilt sein und definiert sich als alle Programmstücke, die über ein und dasselbe Objekt synchronisiert sind,
die also in durch synchronized eingeleiteten Programmblöcken stehen.
Für unseren Zweck synchronisieren“ wir über die Verpackungsobjekte, die die Ergeb”
nisse paralleler Funktionsaufrufe erfassen. Der Thread, der die Funktion aufruft, die den
Inhalt für die Verpackung berechnet, stellt sicher, dass er bis zum Abschluss der Funktion
Eigentümer der betreffenden Verpackungsobjekte ist, also in dieser Zeit im Monitor der
Verpackung rechnet. In dem gleichen Monitor müssen die Selektor- und Pr¨adikatfunktionen ausgeführt werden.
Beispiel 5.10 Für die obige FASAN-Funktion Solve muss die Klasse Tree des
Ergebnis-Objekts so modifiziert werden, dass die Selektor- und Pr¨adikat-Aufrufe im
Monitor des Objekts ausgeführt werden, etwa
public class Tree {
[...]
public synchronized Tree leftTree() {
return ((Branch) content).leftTree;
}
}
Zus¨atzlich ist die FRAME-Klasse für die Funktion Solve so zu erweitern, dass sie
erstens von einem Thread ausgeführt werden kann (also die Schnittstelle Runnable
mit einer Methode run() implementiert) und zweitens w¨ahrend der Ausführung in
den Monitoren aller Ergebnisobjekte bleibt. Es wird dann folgender FRAME erzeugt:
public class FRAME__Solve implements FRAME, Runnable {
[...]
public Tree T_TAG_;
public int newHost;
public boolean threadStarted = false;
public void run() {
// Monitor der Tree-Verpackung T_TAG_ betreten...
synchronized (T_TAG_) {
// ... und dies dem Aufrufer signalisieren
synchronized (this) { threadStarted = true; }
// Funktion im Monitor ausführen
Tree $result =
fasanFunctions.Solve(par, T, destin, opts);
// Ergebnis-Verpackung mit Inhalt füllen
T_TAG_.content = $result.getContent();
}
}
}
88
Übersetzung von FASAN in Java
Wird eine Funktion im FASAN-Programm mindestens einmal mit einer Lokation aufgerufen und damit potentiell parallel ausgeführt, müssen auch Ergebnisparameter, die
von einem Grund- oder externen Typ sind, eine Verpackungsklasse haben, in die der
neue Thread das Berechnungsergebnis als Inhalt eintragen kann. Der Aufruf einer externen Funktion, die dieses Ergebnis verwendet und damit auf den Inhalt der Verpackung zugreift, muss dann solange warten, bis dieser Inhalt verfügbar ist. Das erreichen wir, indem der betreffende externe Funktionsaufruf mit einem Argument der
Form variable.getContent() erfolgt. Die betreffende Verpackungsklasse (z.B.
DOUBLE_ARRAY) des externen Typs beinhaltet dann die Methode
public synchronized double[] getContent() {
return content;
}
Der Vorteil der Verpackungsklassen ist allgemein darin zu sehen, dass die Parallelit¨at auch
bei den Verbrauchern des Inhalts erhöht wird. Dies können wir unmittelbar im Datenflussgraphen an parallelen Teilgraphen erkennen. So können die Teilberechnungen, die auf
die Ergebnisse führen, ab einer bestimmten Stelle unabh¨angig voneinander sein. Werden
Puffer für die Ergebnisse bereitgestellt, kann ein folgender Funktionsaufruf, der nur von
einem dieser Puffer abh¨angt, losrechnen, w¨ahrend noch nicht alle Ergebnisse vorhanden
sind.
Aus Sicht des FASAN-Programmierers ist es also wichtig, die passenden (und lieber zu
viele als zu wenige) Funktionsaufrufe mit Lokationen zu versehen, damit eine Funktion
das Programm nicht durch Warten an einem Monitor vor einem Selektor- oder Pr¨adikataufruf blockiert.
5.5 Erweiterungen für verteilten Speicher mit RMI
Die Verteilung eines FASAN-Programms auf mehrere Rechner geschieht dadurch, dass
auf jedem Rechner ein FASAN-Server gestartet wird. Jeder Server kann entsprechend der
Lokationsannotierungen in jedem anderen Server eine rekursive Funktion durch entfernten Methodenaufruf starten.
Hierfür ist die Server-Klasse von java.rmi.UnicastRemoteObject abzuleiten
und außerdem ein Interface mit allen entfernt aufrufbaren Funktionen zu generieren, welches sich wiederum von dem Interface java.rmi.Remote ableitet.
Beispiel 5.11
Unsere Server-Klasse TreeExample muss dann für die verteilte
Ausführung folgendermaßen ge¨andert werden:
import java.rmi.*;
import java.rmi.registry.*;
import java.rmi.server.*;
5.5 Erweiterungen für verteilten Speicher mit RMI
89
public class TreeExample
extends UnicastRemoteObject
implements TreeExample__INTERFACE {
public static ExampleTree fasanFunctions;
public static
ExampleTreeExternFunctions javaFunctions;
public int myHost, numHosts;
Registry registry;
/* die Namen aller FASAN-Server */
String[] hostNames;
/* alle FASAN-Server */
public TreeExample__INTERFACE[] servers;
public
TreeExample (TreeExampleExternFunctions javaFunctions,
int myHost, String[] hostNames)
throws RemoteException {
[...]
}
[...]
}
Es ist hier eine Menge Code für organisatorische Details vom FASAN-Compiler zu
generieren. Speziell müssen die Server ihre Namen in den Registrierungsprozessen
eintragen und ihre Referenzen untereinander austauschen. Darauf gehen wir hier aber
nicht n¨aher ein.
Die class-Dateien, die der Java-Compiler aus den Server-Klassen generiert, müssen
anschließend noch mit dem Compiler rmic in sogenannte Stub- und Skeleton-Klassen
für die verteilte Kommunikation übersetzt werden, siehe Abbildung 5.3. Diese Klassen
automatisieren die Verwaltungsarbeit für die entfernten Methodenaufrufe, insbesondere
die Serialisierung und Deserialisierung von Argumenten und Ergebnissen.
5.5.1 Rekursive Funktionen
Bei Parallelrechnern mit verteiltem Speicher und bei Workstation-Netzen muss der Programmierer durch Lokationen selbst angeben, wo der neue Kontrollfluss bzw. Prozess
gestartet werden soll; vgl. die Beispiele im Abschnitt 3.1.5. Der FASAN-Code hat hier
zus¨atzlich noch sicherzustellen, dass der Wert des Lokationsaufrufs eine gültige Rechnernummer ist. Andernfalls wird der Funktionsaufruf auf dem Rechner(knoten) des Rufenden ausgeführt.
Zus¨atzlich zu der eigentlichen Berechnungsmethode und der Thread-Startmethode werden für jede rekursive Funktion drei Methoden generiert. Die erste ist einfach eine Schnittstelle für einen entfernten Aufruf wie im folgenden Beispiel.
90
Übersetzung von FASAN in Java
TreeExample.fas
fasan2
TreeExampleExternFunctions.java
Tree.java
Leaf.java
Branch.java
FRAME__... .java
GlobalRef.java
javac
...
TreeExample.java
javac
TreeExample.class
TreeExampleInterface.java
javac
TreeExampleInterface.class
rmic
TreeExample_Stub.class
TreeExample_Skel.class
Abbildung 5.3 Vom FASAN-Compiler erzeugte Klassen und deren weitere Übersetzung
Beispiel 5.12
public Tree Solve__REMOTE (Node par, Tree T, int[] destin,
Options opts, int newHost)
throws RemoteException {
return Solve__THREAD (par, T, destin, opts, newHost);
}
Diese Methode ist aus Effizienzgründen notwendig, weil die generierten Stub- und
Skeleton-Klassen für den gewöhnlichen nicht-verteilten Aufruf zu viel Overhead produzieren und wir deshalb weiterhin die einfachen nicht-verteilten Methoden brauchen.
Die zweite generierte Methode ist eine Verzweigung in die benötigte sequentielle, parallele oder verteilte Variante.
Beispiel 5.13
public Tree Solve__SWITCH (Node par, Tree T, int[] destin,
Options opts, int newHost) {
if (newHost < 0)
// => sequentiell
return Solve(par, T, destin, opts);
else if (newHost == myHost || newHost >= numHosts
|| servers[newHost] == null) // => parallel
return Solve__THREAD(par, T, destin, opts, newHost);
else {
// => verteilt
Tree $frame = null;
try {
$frame = servers[newHost].
Solve__REMOTE (par, T, destin, opts, newHost);
}
5.5 Erweiterungen für verteilten Speicher mit RMI
91
catch (RemoteException ex) { /* Error message*/ }
return $frame;
}
}
Diese SWITCH-Methode wird an allen Stellen aufgerufen, an denen im FASANProgramm ein rekursiver Funktionsaufruf mit einer Lokation annotiert ist. Der zweite
Fall, dass der Aufruf in einem eigenen Thread, aber auf demselben Rechner wie der Aufrufer ausgeführt wird, kann auch bei verteilter Berechnung sinnvoll sein, und zwar aus
folgenden Gründen:
1. Falls der Rechner mehrere Prozessoren hat, können alle parallel genutzt werden,
sofern eine Version des JDK (ab 1.2) installiert ist, die native Threads unterstützt.
2. Hat eine Funktion mehrere Ergebnisparameter, zu deren Berechnung ab einem gewissen Schnitt durch den Datenflussgraphen die Teilgraphen unabh¨angig voneinander sind, werden die Ergebnisse quasi-parallel berechnet. Benötigt die Berechnung eines der Ergebnisse mehr Zeit, blockiert es die Verfügbarkeit der übrigen
Ergebnisse nicht, selbst wenn diese l¨angere Berechnung syntaktisch weiter oben im
Programm steht.
5.5.2 Hierarchische Datentypen
Bei der verteilten Ausführung eines FASAN-Programms stellt ein Verpackungsobjekt für
einen hierarchischen Datentyp gleichzeitig eine globale Referenz dar, die möglicherweise
über viele Zwischenstationen l¨auft, bis sie bei einem Selektor ankommt. Im Datenflussgraphen wird hierfür ein Tupel aus dem Konstruktornamen und den Referenzen auf die
Komponentenpuffer erzeugt. Bezüglich Parallelit¨at gehen wir noch einen Schritt weiter
als in der Datenfluss-Semantik: Die globalen Referenzen für die Inhalte werden schon
erzeugt und nach Start des neuen Threads gleich zurückgegeben, selbst wenn noch keine
der Komponenten des Datentyps verfügbar ist.
Die referentielle Transparanz von FASAN erlaubt eine relativ einfache Implementierung
globaler Referenzen. Denn sie garantiert, dass jede Variable den ihr einmal zugewiesenen
Wert beh¨alt. In prozeduralen Sprachen ist das gerade nicht der Fall, und dort h¨atten wir
dann mit dem Problem der Koh¨arenz von Variablenwerten zu k¨ampfen, sobald sie als
Kopien in mehr als einem Server im verteilten System gehalten werden.
Für die Implementierung von globalen Referenzen bietet Java eine scheinbar einfache
Möglichkeit: die RemoteObjects, wie wir sie schon für die FASAN-Server selbst verwendet haben. Sie bieten programmiertechnische Vorteile:
1. Bei entfernten Aufrufen haben sie die benötigte Call-by-Reference-Semantik, damit der Inhalt von Verpackungsargumenten nicht gleich mitübertragen wird wie bei
einem für die übrigen Java-Objekte üblichen entfernten Call-by-Value.
92
Übersetzung von FASAN in Java
2. Sie besitzen außerdem eine Variante der hashCode-Methode, die allen physikalischen Kopien eines Objekts, auch auf verteilten Rechnern, denselben objektspezifischen Hash-Code zuordnet. Dieser Code könnte somit unmittelbar als Schlüssel in
einer Art Cache für Verpackungsinhalte verwendet werden, um Inhalte bei mehrfacher Dereferenzierung nicht mehrmals übertragen zu müssen.
3. Der Zugriff auf ihren Inhalt — in unserem Fall durch die Selektor-Aufrufe — könnte als Remote-Methode gestaltet werden, und würde ohne zus¨atzlich zu programmierenden Code den Inhalt für eine Referenz automatisch von deren Ursprungsrechner kopieren.
Leider zeigte eine erste Implementierung auf dieser Basis mehrere gravierende Nachteile
für unseren Anwendungsbereich:
1. Gelangt eine Referenz wieder zurück auf den Ursprungsrechner, ist sie eine Kopie
der Original-Referenz, auch wenn sich der Inhalt auf demselben Rechner befindet.
Alle Methodenaufrufe (speziell die Dereferenzierung des Inhalts) geschehen dann
durch Call-by-Value, wodurch für den Inhalt unnötige Kopien angelegt werden. Bei
der Größe der Daten, die im technisch-wissenschaftlichen Umfeld entstehen, kann
dies auf keinen Fall akzeptiert werden und führt viel zu schnell zu Speichermangel.
2. Der Speicher von RemoteObjects wird durch einen in Java integrierten verteilten
Garbage Collector automatisch verwaltet. Es hat sich aber leider gezeigt, dass dieser
nur für eine sehr beschr¨ankte Anzahl von verteilten Objekten brauchbar ist und
bei der Menge an Baumknoten, die typischerweise in den rekursiven numerischen
Algorithmen vorkommen, das FASAN-Programm fast zum Erliegen bringt.
Es ist also unumg¨anglich, eine eigene Implementierung für die verteilten hierarchischen
Datenstrukturen zu finden. Kernpunkt jeder Implementierung der Datentypen von FASAN
ist die Tatsache, dass der Inhalt einer Verpackung, selbst wenn er schon verfügbar ist,
zun¨achst auf seinem Ursprungsrechner zurückbleibt, bis er durch einen Selektoraufruf
angefordert wird. Anstatt RemoteObjects erzeugt der FASAN-Compiler eine Klasse
class GlobalRef implements Serializable {
public int host;
int keyCode;
transient int refCount;
[...]
}
Die Variablen host und keyCode bilden zusammen eine globale Referenz. Jeder
FASAN-Server enth¨alt eine Hash-Tabelle, in die alle Inhalte eingetragen werden, die über
globale Referenzen auch für andere Rechner verfügbar sind. Wir nennen diese HashTabelle Referenztabelle. Die Standard-Methode hashCode(), die einen eindeutigen
Schlüssel für jedes Java-Objekt erzeugt, ist hierzu nicht erwünscht. Denn verschiedene
globale Referenzen, die auf denselben Inhalt verweisen, sollen auch den gleichen Wert als
5.5 Erweiterungen für verteilten Speicher mit RMI
93
Schlüssel in den Referenztabellen haben. Deshalb überschreiben wir die hashCode()Methode und die darauf aufbauende Vergleichsmethode für Objekte.
public int hashCode() { return keyCode; }
public boolean equals(Object obj) {
return keyCode==obj.hashCode();
}
Entsprechende Konstruktoren, die keyCode geeignet belegen, sind zus¨atzlich notwendig:
public GlobalRef() {
keyCode = super.hashCode();
host = ExampleTree.fasanFunctions.myHost;
}
public GlobalRef (int host, int keyCode) {
this.keyCode = keyCode;
this.host = host;
}
Außerdem erweitern alle Verpackungsklassen der hierarchischen Datentypen die Klasse
GlobalRef, sind also selbst globale Referenzen und können zwischen FASAN-Servern
ausgetauscht werden. In allen Verpackungsklassen muss aber die content-Variable als
transient deklariert werden, damit der Inhalt zun¨achst noch nicht mitkopiert wird.
Beispiel 5.14 Unsere Klasse Tree aus den Beispielen 5.4 und 5.10 müssen wir abermals
¨andern.
import java.io.*;
public class Tree extends GlobalRef
implements Serializable
{
transient Tree__CONTENT content;
public Tree () { }
public Tree (Tree__CONTENT c) {
content = c;
}
public Tree (int host, int keyCode) {
super (host, keyCode);
}
[...]
}
Die Analogie in der Datenfluss-Semantik sind die Datenelemente, die im Vorbereich eines
Konstruktors verweilen, und für die nur ein Tupel mit Namen bzw. Referenzen durch den
Datenflussgraphen wandert.
94
Übersetzung von FASAN in Java
Host x
Host y
GlobRefTable 2: server[x].getContentOfTree(42);
Host z
GlobRefTable
GlobRefTable
1: getContent()
3: getContent()
GlobalRef
Tree
42
x
GlobalRef
4
42
x
1
GlobalRef
42
x
1
Tree_CONTENT
GlobalRef
42
x
1
Abbildung 5.4 Ein Szenario von globalen Referenz-Objekten, die durch Remote-Aufrufe erzeugt
wurden und über die Referenztabelle auf denselben Inhalt verweisen. Gepunktete Pfeile bedeuten
nur die logische Dereferenzierung, die über den Umweg der Referenztabelle implementiert wird.
Diese explizite Implementierung von globalen Referenzen hat gegenüber der Verwendung
von RemoteObjects noch folgende weitere Vorteile:
Es müssen nicht für jede Verpackungsklasse zus¨atzlich Interfaces generiert und keine Stub- und Skeleton-Klassen mit rmic erzeugt werden.
Die Klasse ist für die Einfachzuweisung der Inhalte entworfen und entsprechend
einfacher als allgemeine Remote-Klassen.
Der entstehende Overhead f¨allt (wegen der referentiellen Transparenz von FASAN)
geringer aus.
Wird die Referenz über eine andere entfernte Funktion auf demselben Rechner weitergereicht, wird keine neue Kopie angelegt.
Was die Klassen der hierarchischen Datenstrukturen betrifft, so haben alle Methoden zur
Dereferenzierung (also getContent und alle Selektorfunktionen) dafür zu sorgen, dass
ein noch nicht verfügbarer Inhalt gegebenenfalls automatisch besorgt wird. Falls der Inhalt nicht in der Variable content verfügbar ist, muss er aus der lokalen Referenztabelle
geholt werden.
Beispiel 5.15
public synchronized Tree__CONTENT getContent() {
if (content == null)
content = ExampleTree.fasanFunctions.$refTab.
getContentOfTree(host, keyCode);
return content;
}
5.5 Erweiterungen für verteilten Speicher mit RMI
95
Die Selektor- und Pr¨adikatfunktionen stützen sich jetzt ihrerseits auf die
getContent()-Methode, um im Fall, dass die content-Variable null ist,
den Inhalt über die Referenztabelle zu bekommen. Wenn die Tabelle den Inhalt nicht
eingetragen hat, muss sie ihn ihrerseits aus der Tabelle des Ursprungsrechners der
globalen Referenz holen.
Beispiel 5.16
also
Die Pr¨adikatfunktion und die Selektoren des Branch-Konstruktors lauten
public boolean isBranch() {
return (getContent() instanceof Branch);
}
public Node branchNode() {
return ((Branch) getContent()).branchNode;
}
public Tree leftTree() {
return((Branch) getContent()).leftTree;
}
public Tree rightTree() {
return((Branch) getContent()).rightTree;
}
Wenden wir uns nochmal den Referenzz¨ahlern zu. Ein Referenzz¨ahler gibt an, wie viele Kopien der globalen Referenz auf dasselbe Objekt verweisen. Da in FASAN keine
zyklischen Datenstrukturen aufgebaut werden können, ist dieses Verfahren der direkten
Markierung durch Referenzz¨ahler zur Speicherverwaltung trotz seiner Einfachheit effizient [AMR92]. Um keine Speicherprobleme zu bekommen, müssen globale Referenzen,
auf die von keinem anderen Rechner aus mehr zugegriffen werden kann, aus der Referenztabelle genommen werden, damit die Speicherverwaltung von Java den Platz wiederverwenden kann. Da es sich um globale Referenzen handelt, müssen Rechner, die die
Referenzen nicht mehr benötigen, an den Ursprungsserver, in dessen Referenztabelle der
Inhalt eingetragen ist, eine entsprechende Meldung zur Dekrementierung dessen Z¨ahlers
schicken.
Die Java-Klasse GlobRefTable, die die Referenztabelle implementiert, kann z. B.
die Klasse java.util.Hashtable erweiteren oder verwenden. Neben der Methode
clear(), die am Ende eines FASAN-Programms alle Eintr¨age in den Referenztabellen
der FASAN-Server löscht, sind folgende zwei Methoden zur Aktualisierung der Tabelle
nötig:
synchronized int incrRefCount(GlobalRef key) muss zun¨achst
prüfen, ob eine Referenz auf einen anderen Rechner verweist. Wenn ja, wird die
gleiche Methode in der Referenztabelle des entsprechenden Rechners aufgefufen.
Ist andernfalls eine Referenz mit dem Hash-Code von key in der lokalen Tabelle noch nicht eingetragen, wird key eingetragen. Anschließend wird der Referenzz¨ahler des jetzt sicher vorhandenen Eintrags inkrementiert und als Ergebnis
zurückgegeben.
96
Übersetzung von FASAN in Java
synchronized int decrRefCount(GlobalRef key) prüft ebenfalls
zuerst, ob es sich bei key um eine Referenz auf einen anderen Rechner handelt und
delegiert den Aufruf entsprechend dorthin. Ist key lokal, wird der Referenzz¨ahler
des Eintrags in der lokalen Tabelle dekrementiert und die Referenz ggf. aus der
Tabelle gelöscht, wenn der Referenzz¨ahler 0 ist.
int _CONTENT getContentOf (int host,int keyCode) wird für
jeden hierarchischen Datentyp aus der Menge erzeugt. Diese Methode liefert den Inhalt zur Referenz, entweder aus der lokalen Tabelle oder aus der Tabelle
auf dem Rechner host.
(
In die Referenztabelle tragen wir dann alle Referenzen ein, die mindestens einmal auf
andere Rechner kopiert wurden. Dies kann in drei F¨allen vorkommen
beim Aufruf einer verteilten Funktion für die Argumentparameter,
bei der Rückkehr einer verteilten Funktion für die Ergebnisparameter,
bei Anforderung des Inhalts für eine globale Referenz von einem anderen Rechner
aus, die aus dem Aufruf einer Selektor- oder Pr¨adikatsfunktion resultiert.
Mit Hilfe der GlobalRef-Klasse kann eine sehr einfache Implementierung dieser
Verwaltungsstrategie erfolgen. Erstens immer dann, wenn eine Objekt r vom Typ
GlobalRef auf einen anderen Rechner geschickt wird, denn dann wird ein neues Objekt erzeugt und der Referenzz¨ahler auf dem Ursprungsrechner r.host ist zu erhöhen.
Zweitens ist der Referenzz¨ahler auf dem Ursprungsrechner zu dekrementieren, wenn ein
Objekt vom Typ GlobalRef von der Speicherverwaltung entfernt wird. Dem JavaProgrammierer steht hierfür eine Kontrollmöglichkeit zur Verfügung, wann ein Objekt kopiert (serialisiert) und wann es vernichtet (finalisiert) wird. Indem wir in die
GlobalRef-Klasse die beiden folgenden Methoden aufnehmen, erhalten wir genau den
gewünschten Effekt. Den ersten Fall, die Inkrementierung des Ursprungs-Referenzz¨ahlers
bei Anlegen einer Kopie der Referenz, erreichen wir durch Überschreiben der Methode
private void writeObject (ObjectOutputStream out)
throws IOException {
ExampleTree.fasanFunctions.$refTab.incrRefCount(this);
out.defaultWriteObject ();
}
Vor der Speicherfreigabe eines Objekts ruft Java dessen Finalisierungsmethode auf, sofern diese vorhanden ist. Dies nützen wir hier zum Dekrementieren des Ursprungsreferenzz¨ahlers aus:
public void finalize() {
ExampleTree.fasanFunctions.$refTab.decrRefCount(this);
}
Dabei sind noch Optimierungen möglich; zum Beispiel kann die Inkrementierung der
Referenzz¨ahler verzögert und erst in der Methode readObject() von GlobalRef
geschehen, falls der Funktionsaufruf auf dem Ursprungsrechner der Referenz erfolgt.
5.6 Praktische Verwendung von FASAN-Programmen
97
5.6 Praktische Verwendung von FASAN-Programmen
Die Berechnung mit einem FASAN-Programm erfolgt ausschließlich durch Aufruf einer rekursiven Funktion eines FASAN-Servers mit aktuellen Parametern. In der Implementierung werden numHosts FASAN-Server erzeugt, deren Klassen die definierten
FASAN-Funktionen beinhalten und die gegenseitig diese Funktionen gem¨aß den Lokationen aufrufen. Prinzipiell kann an jeder Stelle eines Java-Programm ein Aufruf einer
FASAN-Funktion stehen.
Beispiel 5.17 Ein einfaches Java-Programm kann für Berechnungen auf einem Baum die
automatisierte Verteilung durch ein FASAN-Programm nutzen, indem es folgende
FASAN-Funktion aufruft:
function Main (t:Tree; opts:Options) -> t’:Tree.
Das Java-Programm könnte so aussehen:
import java.rmi.*;
import java.rmi.registry.*;
public class TreeExampleCaller
implements TreeExampleExternFunctions {
public static void main (String[] args) {
int numHosts = Integer.parseInt(args[0]);
String[] hostNames = ...;
Options opts = ...;
Node startNode = ...;
try {
TreeExample server =
new TreeExample(new TreeExampleCaller(),
0, hostNames);
Tree t = Leaf.create (startNode);
Tree result = server.Main(t, opts);
postprocess(result.nd_TAG_);
}
catch (Exception e) { e.printStackTrace(); }
System.exit(0);
}
// hier muss die Implementierung der
// externen Funktionen folgen...
}
Wir haben nun die wichtigsten Techniken beschrieben, um die Datenfluss-Semantik in
Java zu implementieren und ein übersetztes FASAN-Programm auf mehreren Rechnern
verteilt ablaufen zu lassen. Damit können wir uns dem zweiten Teil der Aufgabenstellung
98
Übersetzung von FASAN in Java
zuwenden, n¨amlich der FASAN-Implementierung einer größen Anwendung, die über den
Charakter einfacher Beispiele hinausgeht. Dieser Anwendung für die numerische Simulation ist das ganze folgende Kapitel gewidmet.
99
Kapitel 6
Anwendung von FASAN: Die rekursive
Substrukturierung in der numerischen
Simulation
Wir werden uns nun mit einem wichtigen Anwendungsgebiet für FASAN im Bereich
der numerischen Simulation genauer besch¨aftigen. Es handelt sich um ein rekursives Gebietszerlegungsverfahren zur numerischen Berechnung von Lösungen elliptischer linearer partieller Differentialgleichungen mit konstanten Koeffizienten, wie sie beispielsweise in Problemen der Struktur- und der Strömungsmechanik auftreten. Zu deren numerischer Lösung wird oft die Finite-Element-Methode [Bat96, Bra97, Bur91] eingesetzt, die
bei dem betrachteten Differentialgleichungstyp ein lineares Gleichungssystem liefert. Bei
dem hier verwendeten Verfahren entsteht — entsprechend der rekursiven Zerlegung des
Gebiets, auf dem eine diskrete N¨aherungslösung für die Differentialgleichung berechnet
werden soll — ein Abhängigkeitsbaum von linearen Gleichungssystemen.
Das Verfahren bietet eine Reihe von Vorteilen bei der Lösung von Simulationsaufgaben:
Es ist modular aufgebaut: Bei richtiger Implementierung können unterschiedliche
Diskretisierungstechniken und unterschiedliche Löser für die Gleichungssysteme
eingesetzt werden.
Die sich natürlicherweise ergebenden Baumstrukturen bieten sich für die Zerteilung
und verteilte Bearbeitung an.
Durch die hierarchische Strukturierung ist die adaptive Vertiefung des Baums, die
einer Verfeinerung des Gitters entspricht, möglich.
Es eignet sich nicht nur als eigenst¨andiges Verfahren, es kann auch auf einfache
Weise andere Programme einbinden und aufrufen, welche Lösungen für bestimmte
Teilgebiete liefern. Der Zusammenbau der Lösungen wird dann vollautomatisch
über die hierarchische Baumstruktur geleistet.
Obgleich sich das Verfahren prinzipiell für die verteilte Bearbeitung sehr gut eignet, ist
die tats¨achliche Erstellung einer Implementierung nicht trivial. Denn die rekursive Baum-
100
Die rekursive Substrukturierung in der numerischen Simulation
struktur, die Datenstrukturen für die Gleichungssysteme und für ihre wechselseitigen Beziehungen sowie die Adaptivit¨at des Verfahrens machen die Programmierung der benötigten Kommunikationsroutinen, etwa mit einer Message-Passing-Bibliothek wie PVM oder
MPI, aufwendig und anf¨allig für Programmierfehler. Erschwerend kommt dabei hinzu,
dass verschiedene Lösungsmethoden zwar vom Prinzip her leicht zu integrieren sind, aber
jede Methode auf den Austausch unterschiedlicher Daten zwischen den Gleichungssystemen angewiesen ist.
Genau hier setzt FASAN an: Es eignet sich zur Formulierung rekursiver Algorithmen, wobei das Versenden der entstehenden hierarchischen Datenstrukturen automatisch gem¨aß
der Verteilung der rekursiven Funktionsaufrufe erfolgt, und zwar, wie wir in Kapitel 4
gesehen haben, unter größtmöglicher Erhaltung der Datenlokalit¨at. Damit die entsprechenden Datenstrukturen kommuniziert werden, sind nur die entsprechenden formalen
Parameter bei den Funktionen nötig, die auf dem Baum arbeiten.
Um dieses vielseitig einsetzbare Verfahren verstehen zu können, benötigen wir noch
einige Grundlagen, bevor wir uns der Programmierung in FASAN wieder zuwenden.
Wir wollen also zun¨achst in Abschnitt 6.1 grundlegende Techniken zur Beschreibung
der adaptiven rekursiven Gebietszerlegung vorzeigen. Es schließt sich Abschnitt 6.2 an,
der beschreibt, wie die Gleichungssysteme in den Knoten des Substrukturierungsbaums
aufgebaut werden. Dabei gehen wir etwas genauer auf die Technik der Transformation
der Gleichungssysteme auf die hierarchische Basisdarstellung ein. Abschnitt 6.3 erw¨ahnt
kurz existierende Lösungsverfahren, w¨ahrend Abschnitt 6.4 drei neue Lösungsvarianten
für den Baum von Gleichungssystemen vorstellt. Schließlich werden Möglichkeiten zur
statischen und dynamischen Lastverteilung bei paralleler Ausführung in Abschnitt 6.6
diskutiert.
6.1 Gebietszerlegung
Die Grundlagen von Gebietszerlegungsverfahren sind in [Bur91] und vor allem [SBG96]
nachzulesen. Die spezielleren Details des hier verwendeten rekursiven Verfahrens sowie
die ersten beiden weiter unten beschriebenen Lösungsverfahren stammen darüber hinaus
aus den Arbeiten von Hüttl und Schneider [HS94, Sch95, Hüt96], die wiederum auf den
origin¨aren Ideen von George und Reid [Geo73, GL78, Rei84] basieren.
Bei dem Verfahren der rekursiven Substrukturierung wird das Gesamtproblem analog zur
Schurkomplementmethode [Saa96, SBG96] in Teilprobleme zerlegt, so wie es am Beispiel einer an der linken Seite eingespannten Kragscheibe in Abb. 6.1 links oben dargestellt ist. Die Zerlegung geschieht aber stufenweise: Jedes Teilgebiet kann rekursiv wieder
in Teile zerlegt werden (Abb. 6.1, links). Neben der synthetischen Zerlegung zur adaptiven lokalen Verfeinerung der Gitterweite nach Maßgabe eines Fehlersch¨atzers kann die
Zerlegung für die oberen Baumebenen auch auf natürliche Weise erfolgen, z. B. wenn das
Bauteil aus mehreren Komponenten zusammengesetzt ist.
6.1 Gebietszerlegung
101
f
Freier Rand
Separator
Fester Rand
Inkompat. Punkte
1
" " 2
3
" 1
3
2
7
5
6
5
7
9
8
10 11 14 15
Sfrag replacements
3
2
4
6
4
8 9
10 11
4
5
14 15
6
16
17
18
19
20
21
22
23
8
9
10
11
17
18 19
7
14
15
16 18
17 19
20 22
21 23
16
20
21
22
23
Abbildung 6.1 Rekursive Substrukturierung: Links ist die Gebietszerlegung einer an der linken
Seite eingespannten Kragscheibe dargestellt. Dick eingezeichnet sind jeweils die Separatorlinien,
die die Gebiete rekursiv für die n¨achsttiefere Ebene aufteilen. Die Nummern sind Identifikatoren
Rechts
ist der Bottom-up-Schritt skizziert, in dem jeweils das Gleichungsfür die Teilgebiete.
" " " 3 für die Freiheitsgrade im
system
6 Vaterknoten aus den Gleichungssystemen
der Sohnknoten assembliert wird. Bei der Assemblierung der
Knotengleichungssysteme ist die Rolle der Freiheitsgrade der Lösung zu beachten: = Freiheitsgrade auf dem Separator; = Freiheitsgrade auf dem Teilgebietsrand; = inkompatible (h¨angende) Punkte auf dem Separator; = durch Dirichlet-Randbedingungen festgelegte Werte.
Ist die Zerlegung festgelegt, werden die linearen Gleichungssysteme, die die Lösung
auf dem jeweiligen Teilgebiet beschreiben, schrittweise von unten nach oben aufgebaut (Abb. 6.1, rechts). Wir nennen sie Knotengleichungssysteme und den entstehenden Abh¨angigkeitsbaum von Knotengleichungssystemen den Substrukturierungsbaum.
Der (gesuchte) Lösungswert von an einem Punkt des Gitters eines Baumknotens heißt
Freiheitsgrad. Ist für einen Freiheitsgrad der Lösungswert schon vorgegeben wie an der
linken Seite der Kragscheibe, handelt es sich um eine Dirichlet-Randbedingung.
102
Die rekursive Substrukturierung in der numerischen Simulation
Start
Top-down
Gitter bzw. Baum
verfeinern
Baumdurchlauf
des Lösers
Gleichungssysteme
erstellen
nein
Bottom-up
Lösungsgenauigkeit
erreicht?
nein
ja
Diskretisierungsfehler
klein genug?
ja
Ende
Abbildung 6.2 Ablauf der rekursiven Substrukturierung. Die mit Ovalen dargestellten Schritte
erforden komplette Durchl¨aufe des Substrukturierungsbaums.
Der algorithmische Rahmen setzt sich aus drei Durchl¨aufen des Substrukturierungsbaums
zusammen:
1. Verfeinerung des Substrukturierungsbaums bis zur benötigten Tiefe durch Gebietszerlegung,
2. Erstellen der Gleichungssysteme in den Baumknoten und
3. Lösen der Gleichungssysteme.
Da die Gleichungssysteme bei realistischen Anwendungen insgesamt sehr viele Freiheitsgrade (in der Größenordnung von und mehr) enthalten, werden iterative Löser eingesetzt, die bei sehr großen Gleichungssystemen bezüglich Rechenzeit und Speicher effizienter arbeiten. Stellt sich mit der Lösung heraus, dass die Diskretisierung in einem Teilgebiet nicht fein genug war, um die physikalische Realit¨at gut genug anzun¨ahern, werden die
oben genannten drei Baumdurchl¨aufe wiederholt. Ein grobes Ablaufdiagramm entspricht
somit Abb. 6.2.
6.1.1 Beschreibung der Substrukturierung
Zum Aufbau des Substrukturierungsbaums ist die Identifizierung von Punkten im Gebiet
eines Baumknotens mit den Punkten in den Teilgebieten seiner Söhne nötig. Zun¨achst
müssen wir beschreiben können, wie und an welchen Kanten Teilgebiete aus den Sohnknoten zum Gebiet des Vaters zusammengesetzt werden.
Um das Gleichungssystem eines Baumknotens zu erstellen, werden von den Sohnknoten
jeweils nur Punkte von deren R¨andern verwendet. Zum Zusammenbau werden, wie wir in
Abb. 6.3 sehen, die wesentlichen Berandungen des Gebiets des Knotens nummeriert, unterteilt nach Punkten und Linien. Nur die Punkte an den Ecken eines Knotens (P1 bis P4)
werden einzeln nummeriert; die übrigen Punkte auf den Linien sind als Bin¨arb¨aume (T1
bis T5, im Folgenden als Linienbäume bezeichnet) organisiert. Die Zusammensetzung
6.1 Gebietszerlegung
P1
103
P2
T1
T5
T4
I
P4
T3
T4
P4
P3
T1
P2
I
II
T5
T2
T6
T7 III
IV
T2
II P1
T3
P3
Abbildung 6.3 Substrukturierungsschemata bei rekursiver Zerteilung in zwei Teilgebiete (links)
und in vier Teilgebiete (rechts)
eines Gebiets wird dann durch Auflisten der R¨ander der Teilgebiete in der Nummerierung
des Gesamtgebiets festgelegt. Manche Linien sind auf zwei Teilgebiete aufgeteilt. Der
Anteil einer Linie wird durch die Funktionen left (linke bzw. obere Teillinie), right
(rechte bzw. untere Teillinie) und node (Mittelpunkt) zum Ausdruck gebracht.
Mit der schematischen graphischen Darstellung aus Abb. 6.3 (links) folgt die Beschreibung
I = (P1, left(T1), node(T1), T5, node(T3), left(T3), 4, T4),
II = (node(T1), right(T1), P2, T2, P3, right(T3), node(T3), T5).
Dabei sind die Namen für Punkte und Linienb¨aume beliebig; wichtig ist der Bezug von
Teilgebiet zu Gesamtgebiet, von Sohn- zu Vaterknoten. Eine Teilung des Vaters in vier
Sohnknoten können wir mit folgender Substrukturierungsbeschreibung ausdrücken:
I
= (P1,
node(T5),
II = (node(T1),
node(T2),
III = (node(T4),
node(T3),
IV = (node(T5),
P3,
left(T1),
T6,
right(T1),
T7,
T6,
left(T3),
T7,
right(T3),
node(T1),
node(T4),
P2,
node(T5),
node(T5),
P4,
node(T2),
node(T3),
left(T5),
left(T4)),
left(T2),
left(T5)),
right(T5),
right(T4)),
right(T2),
right(T5)).
6.1.2 Klassifikation der Punkte
Die Organisation der Punkte auf den Linien in Bin¨arb¨aumen hat nicht nur für die Substrukturierungsbeschreibung Vorteile. Sie ermöglicht es auch, bequem einzelne Teilgebiete adaptiv zu verfeinern.
Die Eintr¨age in den Knoten eines Linienbaums sind als Indizes im jeweiligen Knotengleichungssystem zu verstehen. Diese Technik kombiniert die Möglichkeit der Adaptivit¨at der
Gebietszerlegung, was auf nicht-balancierte B¨aume führt, und die mögliche Verwendung
104
Die rekursive Substrukturierung in der numerischen Simulation
von Standardroutinen für die Lösung der Gleichungssysteme. Deren Vektoren können daher im Speicher in zusammenh¨angenden Feldern abgelegt und für deren Systemmatrizen
beliebige Datenstrukturen für dünnbesiedelte Matrizen verwendet werden.
Wir bezeichnen die Matrix
des Gleichungssystems eines Baumknotens als System
matrix, als Lösungsvektor und als Lastvektor. Bei der Vergabe der Indizes für die
Punkte ist zu beachten, dass für eine möglichst effiziente Gleichungssystemlösung die
Freiheitsgrade zun¨achst in vier Blöcke eingeteilt werden:
Externe Freiheitsgrade (Indexmenge , “ ” in Abb. 6.1) auf dem Gebietsrand des
Baumknotens. Die Werte dieser Freiheitsgrade werden vom Vaterknoten festgesetzt. In der Wurzel des Substrukturierungbaums ist diese Menge leer.
Interne Freiheitsgrade (Indexmenge , “ ” in Abb. 6.1) auf dem Separator des Baumknotens. Dies sind die Freiheitsgrade, deren Werte im Baumknoten selbst durch
Relaxation oder Rücksubstitution (je nach Lösungsverfahren) bestimmt werden. In
der Wurzel können auch auf den R¨andern interne Freiheitsgrade liegen, wenn sie
nicht durch Dirichlet-Randbedingungen festgelegt sind.
%
Feste Randwerte für den Lösungsvektor (Indexmenge , “ ” in Abb. 6.1) durch
Dirichlet-Randbedingungen aus dem Gleichungssystem entkoppelte Freiheitsgrade. Solche Freiheitsgrade werden mit ihrem festgelegten Wert ganz aus dem Gleichungssystem entkoppelt, was wir in Abschnitt 6.2.3 genauer beschreiben. Dazu
ist es wichtig, dass für Systemmatrix und Lastvektor alle Beitr¨age aus den Söhnen
aufgesammelt sind. Dies ist am Gebietsrand jeweils am Endpunkt eines Separators
der Fall, und im Gebietsinneren auf den kompletten Separatoren.
Hängende (inkompatible) Punkte (Indexmenge , “ ” in Abb. 6.1) auf dem Separator. Es handelt sich hier um Punkte, die beim Zusammensetzen des Gebiets am
Separator nicht in beiden Teilgebieten vorkommen. Gem¨aß den linearen Ansatzfunktionen bei der Diskretisierung werden die Werte dieser Punkte durch lineare
Interpolation der Werte der Nachbarknoten bestimmt. Die Freiheitsgrade können
(wegen dieser Interpolationsvorschrift) dann ebenfalls aus dem Gleichungssystem
entkoppelt werden (genauer in Abschnitt 6.2.3).
Daneben kann noch der Index –1 vorkommen, der angibt, dass der betreffende Freiheitsgrad schon in einer tieferen Baumebene entkoppelt wurde und im Gleichungssystem des
aktuellen Baumknotens nicht mehr vorkommt.
Ist die Klassifikation abgeschlossen, werden den Punkten Indizes zugeteilt, die den Indizes im Knotengleichungssystem entsprechen. Die Zuteilung geschieht am besten in hierarchischer (breitenrekursiver) Reihenfolge, siehe Abb. 6.4.
Für das Zusammenfügen zweier Teilgebiete ist noch die Richtung festzulegen, in der in
jeder Ebene des Linienbaums die Punktnummerierung erfolgt. Am allgemeinsten verwendbar ist die Festlegung von links nach rechts“ und ggf. von oben nach unten“. Ein
”
”
Rundherumlaufen“ auf dem Rand l¨asst sich dagegen schwieriger auf die Separatoren
”
übertragen, ist aber auch möglich.
6.2 Aufbau der Gleichungssysteme
105
15
PSfrag replacements
6
3
18
17
13
7
8
12
14
19
16
1
4
9
10
5
11
2
Abbildung 6.4 Die Nummerierung der Punkte eines Knotens des Substrukturierungsbaums geschieht blockweise hierarchisch. Punkte einer Hierarchiestufe sind jeweils durch gleich dicke
Kreisr¨ander angedeutet. Zuerst werden die externen Freiheitsgrade auf dem Rand nummeriert,
dann die internen Freiheitsgrade im Gebietsinnern und schließlich (feste) Dirichlet-Randwerte
und h¨angende Punkte (alle jeweils in hierarchischer Reihenfolge).
Wenn wir die oberen Baumebenen von Hand substrukturieren, sollten die resultierenden
Separatoren möglichst wenig Knoten aufweisen, denn deren Anzahl bestimmt die Dimension des in jedem Baumknoten zu lösenden linearen Gleichungssystems. Hat das Gebiet
Löcher“, wirkt sich das auch unmittelbar in einer geringeren Anzahl von Punkten aus,
”
an denen die diskrete Lösung berechnet werden muss.
6.2 Aufbau der Gleichungssysteme
Nach den Vorarbeiten für den Aufbau des Substrukturierungsbaums über dem Gebiet des
zu berechnenden Problems können jetzt die Knotengleichungssysteme erstellt werden.
Konvention: Wird ein Vektor mit einer Indexmenge indiziert, z.B. , so ist die Projektion dieses Vektors auf einen Vektor der Dimension dieser eingeschr¨ankten Indexmenge
gemeint. Das Gleiche gilt auch bei Matrizen für die Zeilen- und Spaltenindizes.
Die nicht weiter unterteilten Teilgebiete bilden die Bl¨atter des Substrukturierungsbaums. In ihnen haben nach Anwendung einer Diskretisierungstechnik — z. B. der
Finite-Element-Methode [Bat96] — die beiden Teilprobleme jeweils die Gestalt eines
blockstrukturierten linearen Gleichungssystems, bei dem alle Freiheitsgrade auf dem Gebietsrand des Blattknotens von den Freiheitsgraden im Gebietsinnern getrennt sind:
(6.1)
Die gleiche Blockstrukturierung haben auch die Gleichungssysteme, die in Nicht-BlattKnoten des Substrukturierungsbaums jeweils aus den Gleichungssystemen der Sohnknoten aufgebaut werden. Es wird also immer unterschieden zwischen Punkten auf dem Rand
(Indexmenge ), deren Freiheitsgrade, Matrixeintr¨age und Eintr¨age im Lastvektor an den
Vaterknoten weitergegeben werden, und den Punkten auf den Separatoren im Gebietsinneren (Indexmenge ).
106
Die rekursive Substrukturierung in der numerischen Simulation
6.2.1 Assemblierung der Gleichungssysteme aus den Sohnknoten
Für jeden Knoten (parent) des Substrukturierungsbaums ist die Systemmatrix
und
der Lastvekor zu assemblieren. Die Sohnknoten
(child,
) tragen dazu
jeweils ihre (bereits erstellte) Systemmatrix
und ihren Lastvektor bei.
Zun¨achst müssen die Gleichungssysteme der Söhne in einem einzigen Gleichungssystem für den Vater untergebracht werden. Hierzu ist die Akkumulation der Eintr¨age der
Systemmatrizen und der
, durch geeignete Assem Lastvektoren der Söhne blierungsmatrizen
nötig.
1 1 Die Anzahl der Spalten einer Assemblierungsmatrix
stimmt mit der Vektordimensi
on des Sohnknotens
überein, die Zeilenanzahl mit der Vektordimension des Vaterknotens. Jede Zeile für einen Punkt aus des Sohns hat genau eine 1:
falls der Punkt im Vater auf Index und im Sohn auf liegt
sonst
Zeilen sind, kann diese Matrix
Nachdem die Nicht-Null-Zeilen genau die ersten
wesentlich speichereffizienter
in
einem
Feld
der
L¨
ange
gespeichert werden mit
, falls .
Werden Werte im Substrukturierungsbaum nach unten transportiert,
also vom Vater zu ei
nem Sohn , geschieht dies einfach mit der Transponierten
der Assemblierungs
bzw. das
matrix. Praktisch kann die Assemblierungsmatrix
Feld
durch gleich
zeitigen Durchlauf entsprechender Linienb¨aume von Vater- und Sohnknoten aufgestellt
werden.
6.2.2 Hierarchisierung des Gleichungssystems
Eine allgemeine Möglichkeit zur substantiellen Verbesserung des Konvergenzverhaltens iterativer Löser für Finite-Element-Probleme ist durch den Wechsel von den standardm¨aßig verwendeten Knotenpunktbasen auf hierarchische Basen gegeben [Gri94,
JL91, Yse86]. Im Folgenden führen wir die Technik der Hierarchisierung von ein- und
zweidimensionalen Funktionen kurz ein (vgl. [Bun92, Gri87, Hüt96, Zen90]) und zeigen, wie sie für unsere Beispielprobleme in Kapitel 7 auf die Knotengleichungssysteme
angewandt werden.
6.2 Aufbau der Gleichungssysteme
107
1
1
0 3 2 3 1 3 2 3 0 =l
!! "
"#"#"# $
#
$$%$%
0 0 x
&
(links) und hierarchische Basis '
Abbildung 6.5 Knotenpunktbasis
x
(rechts) auf eindi-
mensionalem Gebiet
*)
Gegeben sei ein quadratisches Grundgebiet (
sowie
ein diskretes Gitter (
(des
alt.
Levels ) auf ( , das in jeder Kooridinatenrichtung +
¨aquidistante Punkte enth¨
)
Zur n¨aherungsweisen Darstellung einer Funktion
(
mittels eines bzgl. ( , multilinearen Interpolanten verwendet man üblicherweise die Knotenpunktbasis
(siehe für den eindimensionalen Fall Abb. 6.5, links). Sie besteht aus den +
bzgl.
+ , die jeweils an genau einem
( stückweise multilinearen Funktionen - ,
Stützpunkt den Wert 1 und an allen
anderen
Stützpunkten
den Wert 0 annehmen. Entschei aufgespannte Raum auch mit Hilfe einer hierarchischen
dend ist nun,
dass der von ,
Basis .
erzeugt werden kann (Abb. 6.5, rechts). Diese ist wie folgt rekursiv definiert:
.
.
0/ , 1/ 2 / . / 3 , Dabei ist
die Menge der Basisfunktionen, die auf den zu
Gitterpunkten verschwinden.
,
gehörenden
/
Zweidimensionale Basisfunktionen, wie sie für die in dieser Arbeit beschriebenen An4)
wendungen auf (
benötigt werden, ergeben sich durch Bildung des Tensorprodukts aus jeweils einer eindimensionalen Basisfunktion in jeder Koordinatenrichtung
[Gri87, Bun92]. In Abb. 6.6 sind Beispiele solcher Basisfunktionen dargestellt.
4
/ Raum kann nun als Linearkombination
Eine Funktion aus dem von ,
aufgespannten
/ der Knotenbasisfunktionen -
/65
( ,
,
/65 - ( 7)
oder gleichwertig als Linearkombination von hierarchischen Basisfunktionen
/65
( /65 8 8- ( 8 7)
8-
.
/ ,
108
Die rekursive Substrukturierung in der numerischen Simulation
1
1
0.8
0.8
0.6
0.6
0.4
0.4
0.2
0.2
1
1
0.8
00
0.8
00
0.6
0.2
0.4
0.4
0.6
0.6
0.2
0.2
0.8
0.4
0.4
0.6
1 0
1
1
0.8
0.8
0.6
0.6
0.4
0.8
0.2
1 0
0.4
0.2
0.2
1
1
0.8
00
0.8
00
0.6
0.2
0.4
0.4
0.6
0.2
0.8
1 0
0.6
0.2
0.4
0.4
0.6
0.8
0.2
1 0
/ &
mit Spitze über den Ecken des
Abbildung 6.6 Statt der 4 Funktionen der Knotenpunktbasis
/ Funktionen aus / mit dem gesamten
Gebiets (links oben) enth¨alt die hierarchische Basis '
/ Gebiet als Tr¨ ager,
von denen eine rechts oben dargestellt ist. Unten links und rechts: Basisfunk/
&
/ Knotenpunktbasis
tionen aus
; w¨ahrend diese Funktionen bei Verwendung der
nur in
3
vorkommen, sind sie bei hierarchischer Basis in allen R¨aumen '
mit
enthalten.
eindeutig dargestellt werden. Die 8 werden auch als hierarchische Überschüsse bezeich net, weil sie nicht den Funktionswert an der Stelle beinhalten, sondern jeweils nur
die Differenz des Funktionswertes zum Mittelwert der hierarchischen Nachbarpunkte. In
iterativen Lösungsverfahren für die rekursive Substrukturierung sind dann die hierarchischen Transformationen Hierarchisierung und Dehierarchisierung erforderlich, d. h. also
die Transformationen von nach 8 und umgekehrt. Wie diese Transformation geschehen muss, leiten wir aus den Transformationen für die Basisfunktionen ab.
(
Die Hierarchisierung erfolgt im Substrukturierungsbaum stufenweise. Die Gleichungssysteme in den Teilgebieten sind dabei schon hierarchisiert. Es ist also nur der letzte
Schritt, die Bildung der Basisfunktionen mit Spitze an den Ecken des neuen Gebiets und
Tr¨ager über das ganze Gebiet notwendig, siehe Abb. 6.6, rechts oben. Im eindimensionalen Fall ist diese neue Basisfunktion der hierarchischen Basis die Summe aus der an
diesem Eckpunkt vorhandenen Funktion und der Basisfunktion im Mittelpunkt der Linie, siehe Abb. 6.7 links. Wegen des Tensorprodukt-Ansatzes für die zweidimensionalen
Basisfunktion gehen für die neue hierarchische Basisfunktion in einem Eckpunkt die vorhandenen Funktionen mit den Gewichten
/ /
/
/ 6.2 Aufbau der Gleichungssysteme
1/2
109
1/2
1/2
T1
P1
1/2
P2
1/4
P1
T1
P2
1/4
1/2
H
T4
T5
T6
T7
1/2
T2
1/4
1/4
1/2
P1
T1
1/2
P2
P4
P3
T3
1/2
1/2
Abbildung 6.7 Hierarchisierungsschritt in einem Baumknoten. Links ist der Schnitt durch die
Basisfunktionen an der Linie T1 dargestellt, vor und nach der Hierarchisierung H. Rechts sind die
Faktoren für den kompletten Hierarchisierungsschritt für einen Baumknoten über diesem Gebiet
eingetragen. Diese Faktoren sind gleichzeitig die Eintr¨age in der Hierarchisierungstabelle.
ein, also die Funktionen mit Spitze über den Mittelpunkten der beiden anliegenden Kanten
mit Faktor 1/2 und die Funktion über dem Mittelpunkt des Gebiets mit Faktor 1/4, siehe
Abb. 6.7, rechts.
Die Hierarchisierungsmatrix
setzt sich dann aus den in Abb. 6.7 (rechts) angedeuteten Eintr¨agen zusammen, wobei die Bezeichnungen für Punkte und Linien aus Abschnitt 6.2.2 übernommen sind. Dazu werden Matrix, Lastvektor auf die HierarchischeBasis-Darstellung transformiert durch
/
/ 1 1
Allgemeiner können wir bei der Hierarchisierung des Knotengleichungssystems auch von
einer Vorkonditionierung sprechen [SBG96], weil sie die Konditionszahl der Systemmatrix und damit das Konvergenzverhalten von iterativen Lösern des Gleichungssystems verbessert. Diese Vorkonditionierung kann alle Punkte des Gebiets miteinbeziehen, insbesondere solche, für die Dirichlet-Randwerte vorgegeben werden. Deshalb wird die Vorkonditionierung vorgenommen, bevor diese Punkte in den folgenden Schritten aus dem
Gleichungssystem entkoppelt werden.
Der Hierarchisierungsschritt selbst kann bei balancierten B¨aumen und hierarchischer
Nummerierung der Punkte allein durch Indexrechnung erfolgen, siehe dazu [BES97].
Eine allgemeinere und bew¨ahrte Technik für diese Transformation ist eine Hierarchisie
rungstabelle, die eine explizite Speicherung von vermeidet und die sich aus der Anordnung der Punkte in den Linienb¨aumen und Ecken des Gebiets ergibt. Sie enth¨alt Tupel der
Form . Jeder solche Eintrag gibt den Einfluss an, den ein hierarchischer Nachbar auf den Knoten mit dem Faktor hat. Bei der Hierarchisierung eines Vektors
muss also für jeden Tabelleneintrag die Operation "
"
" ausgeführt
werden (siehe Abschnitt 6.3.1).
110
Die rekursive Substrukturierung in der numerischen Simulation
Wir erhalten somit folgende Hierarchisierungstabelle:
node(T1)
P1
1/2
node(T4)
P4
1/2
node(T1)
P2
1/2
node(T4)
P1
1/2
node(T2)
P2
1/2
node(T5)
P1
1/4
node(T2)
P3
1/2
node(T5)
P2
1/4
node(T3)
P3
1/2
node(T5)
P3
1/4
node(T3)
P4
1/2
node(T5)
P4
1/4
Die Verwendung von Hierarchisierungstabellen hat neben dem offensichtlich massiv ein
gesparten Speicherplatz für die Hierarchisierungsmatrix
den Vorteil, dass sie programmtechnisch generisch ist: Mit einer einfachen Iteration über die Tabelle kann die
Hierarchisierung für beliebige Elemente (also auch z.B. bei Triangulierungen) vollzogen
werden, wenn für den entsprechenden Elementtyp eine Hierarchisierungstabelle vorliegt.
Hierarchisierungstabellen sind auch vorteilhaft, um bei den Baumdurchl¨aufen nicht zwischen zwei- und viergeteilten Knoten unterscheiden zu müssen. Denn hierarchisiert wird
bei Zweiteilung nur in jeder zweiten Ebene des Substrukturierungsbaums, also erst wenn
vier Teilgebiete zusammengesetzt sind. In den übrigen Ebenen wird eine leere Tabelle für
den Hierarchisierungsschritt verwendet.
6.2.3 H¨
angende Punkte und Dirichlet-Randwerte
Beim Zusammensetzen zweier Teilgebiete kann der Fall eintreten, dass manche Punkte
auf dem Separator nur in einem (dem st¨arker verfeinerten) Teilgebiet vorkommen (siehe
Abb. 6.8). Ein solcher Punkt heißt h¨angend und ist aus dem Gleichungssystem zu entkoppeln. Dazu benötigen wir Matrizen, die Reihenoperationen auf der Matrix beschreiben:
Definition 6.1 Die Elemente der Matrix
falls
falls
sonst
und
sind festgelegt als
Bei der Darstellung in Knotenpunktbasis muss für jeden inkompatiblen Punkt der Wert
aus den Werten und seiner Nachbarpunkte interpoliert werden:
/ Dies ist n¨amlich auch der Wert, der in dem weniger verfeinerten Gebiet für implizit (wegen der multilinearen Basisfunktionen) angenommen wird. Insgesamt muss die
6.2 Aufbau der Gleichungssysteme
111
PSfrag replacements
/
Abbildung 6.8 H¨angende Punkte auf dem Separator des Knotens sind mit “ ” markiert. Die
Freiheitsgrade der h¨angenden Punkte müssen aus dem Gleichungssystem entkoppelt und bei der
Berechnung der Lösung interpoliert werden.
Lösung in jedem Punkt stetig sein. Diese Interpolation bedeutet für das Gleichungssys
tem, dass sich der Einfluss des Punktes zu gleichen Teilen auf und verteilt:
/ / / / Bei Verwendung der hierarchischen multilinearen Basisfunktionen ist diese Transforma
tion nicht notwendig,
, da der interpolierte Wert — der hierarchische Überschuss
. Lediglich
— Null sein muss und entsprechend für jeden h¨angenden Punkt gilt:
die betreffenden Zeilen und Spalten sind aus der Systemmatrix zu streichen. Dies wird
schon durch die Projektion
auf die Indizes der internen und externen Punkte im
folgenden Transformationsschritt für die Dirichlet-Randwerte geleistet.
Wenn für einen Freiheitsgrad die Dirichlet-Randwertbedingung
vorgeschrieben
ist, wird folgende Transformation notwendig: Die -te Spalte
der Matrix wird mit
diesem Wert multipliziert und vom Lastvektor abgezogen:
Alle Zeilen und Spalten, die den festen Randwerten zugeordnet sind, werden aus der
Matrix gestrichen. Ebenso auch die Zeilen und Spalten der Freiheitsgrade der h¨angenden
Punkte, deren Einfluss durch die Transformation
auf die Nachbarpunkte
übertragen
worden ist. Übrig bleibt also das Gleichungssystem
für die Indexmenge
.
112
Die rekursive Substrukturierung in der numerischen Simulation
6.2.4 Algebraische Vorkonditionierung
Hier werden nun Transformationen des Gleichungssystems vorgenommen, die nur noch
die nicht-entkoppelten Punkte betreffen und der Lösung des Gesamtsystems dienen. Hierzu gehört die vollst¨andig direkte Lösung mittels statischer Kondensation (Abschnitt 6.3),
aber auch Verbesserungen der Kondition der Systemmatrix durch partielle Elimination der
st¨arksten Kopplungen (Abschnitt 6.4.1) oder durch Vorkonditionierungen, die nicht von
der geometrischen Lage der Punkte Gebrauch machen, z. B. ILU-Vorkonditionierungen
[Hac93]. Wir beschreiben dies allgemein durch Transformationsmatrizen und , also
Diese Verfahren sind löserspezifisch und werden deshalb weiter unten bei dem entsprechenden Lösungsverfahren besprochen. In effizienten Vorkonditionierungsverfahren ist
die Transformationsoperation symmetrisch,
, und erh¨alt damit die Symmetrie
des Knotengleichungssystems.
6.2.5 Auswirkung auf die L¨
osungsiterationen
Die bisher beschriebenen Transformationen des Gleichungssystem haben nicht nur Auswirkungen auf den Gleichungssystemaufbau (den Build-Schritt), sondern auch auf die
Werte, die w¨ahrend des iterativen Lösungsvorgangs (in Top-down- und Bottom-upSchritten) zwischen Vater- und Sohnknoten ausgetauscht werden. Bei den verschiede
nen Lösungsvarianten werden typischerweise -Werte vom Vater zu den Söhnen gereicht,
w¨ahrendÄnderungen des Lastvektors und Residuenwerte von den Söhnen zum Vater
übertragen werden. Die notwendigen Transformationen sehen wir in Abb. 6.9. Das obere Gleichungssystem (V) haben wir aus den Gleichungssystemen (I) und (II) der Söhne
erhalten. Werden Werte von für die externen Freiheitsgrade der Söhne vom Vater nach
unten gereicht, ist Transformation (T1) nötig.Änderungen, die den Lastvektor betreffen
und nach oben wandern, werden von Transformation (T2) behandelt.
6.3 Bisherige Lösungsvarianten
Die gesuchte Lösung
ergibt sich durch Kombination der Gleichungssysteme aller
Baumknoten. Der Ansatz für einen direkten Löser eliminiert die Freiheitsgrade schrittweise von der untersten Baumebene zur Wurzel hinauf. Die ursprüngliche Idee der Nested Dissection [GL78] lieferte eine Nummerierung aller Freiheitsgrade gem¨aß der Hierarchie der Separatoren, um den Fill-in des Gesamtgleichungssystems gegenüber geometrischer Nummerierung zu vermindern. Diese hierarchische Nummerierung wird in
[Sch95, Hüt96] explizit zum Aufbau eines Baums von Gleichungssystemen, des Substrukturierungsbaums, verwendet.
6.3 Bisherige Lösungsvarianten
113
2 7 )
4
3 53 63
PSfrag replacements !#"%$&'%#"!(
(
,
*
.
+
0
/
,
*
.
+
1
1
%8":9;!<"(=*>+7
(V)
? %<"@!#" (BADCE(=*>+7- F 9 %#"G
(T1)
? %#" (
H $ 'ML" 9 ML!"
(I)
K
J
?
%8" @ !#" (
@ I7" G
1 9 I."
? @
ML"
$ '!N" 9 '!N"
? @
'!N"
(II)
(T2)
Abbildung 6.9 Top-down-Transformation (T1) von Lösungswerten
und Bottom-upTransformation (T2) von Werten des Lastvektors
zum Austausch zwischen VaterGleichungssystem (V) und Sohn-Gleichungssystemen (I) bzw. (II)
Um trotz Wegfalls der Punkte aus dem Gebietsinneren der Söhne ein korrektes Gleichungssystem im Vaterknoten zu erhalten, müssen die Söhne vor der Assemblierung
den
Einfluss ihres Gebietsinneren auf ihren Rand übertragen. Dazu addiert man zum oberen
Block von Gleichung (6.1)
den zweiten Zeilenblock, multipliziert
mit
:
OBQSP R Q
(6.2)
Da die durch Finite-Element-Diskretisierung entstehenden Matrizen symmetrisch und positiv definit sind, kann für die implizite Berechnung von das Cholesky-Verfahren
[Stö89] verwendet und durch Vorw¨arts- und Rückw¨artssubstitutionen ermittelt werden. Dies ist die Hauptarbeit, die bei einem direkten Löser anf¨allt.
Der Vorteil diese Verfahrens ist in seiner Robustheit und der genauen Absch¨atzbarkeit
seines Aufwands zu sehen, weil zur Lösung nur ein einziger Top-down-Durchlauf durch
den Baum zur Rücksubstitution erforderlich ist.
Ein Problem dieses Verfahrens liegt aber in seiner nicht-optimalen Laufzeit von T
bei Gitterpunkten, die die Berechnung wirklich großer, durch feine Gitter entstandener
Gleichungssysteme verhindert. Deshalb wird im Folgenden auch das für große Probleme
und für die Parallelisierung wesentlich besser geeignete iterative Verfahren von [Hüt96]
kurz angegeben.
114
Die rekursive Substrukturierung in der numerischen Simulation
(B1)
(B2)
(D2)
8 , 8
, (D5)
(U0)
(U1)
(U2)
(U3)
(U4)
" 8
Hierarchisierung von Matrix und Lastvektor
Aufbringung der Dirichlet-Randwerte
GS-HB, Top-down-Schritt
(D3)
(D4)
8 GS-HB, Build-Schritt
Zusammenfassung von Eintr¨agen in Ma
trix und Lastvektor aus den Söhnen ,
8
(B3)
(D1)
[Falls nicht Wurzel:] Neue Lösungswerte
vom Vaterknoten
Beschr¨ankung des Relaxations-Aufwands
von (D4) auf die internen Freiheitsgrade
, weil die externen vom Vater schon
festgelegt sind.
Gauß-Seidel-Gl¨attungsschritte
Speichern der Änderung
von der internen Punkte für den Bottom-up-Schritt
Dehierarchisierung
8 Bottom-up-Schritt
GS-HB,
[Falls Blatt.]
[Falls nicht Blatt:] Zusammenfassung der
Änderungen aus Söhnen für den Lastvek
tor
8 Hierarchisierung
8 Anpassung des Lastvektors
8 Änderungen für den Lastvektor des Vater
knotens
Abbildung 6.10 Berechnungsschritte beim Gauß-Seidel-Verfahren auf hierarchischer Basis
6.3.1 Ein Gauß-Seidel-L¨
oser auf hierarchischer Basis
Grunds¨atzlich ist bei großen Problemen den iterativen Lösern der Vorzug vor direkten
Lösern zu geben. Denn direkte Löser verursachen einen Fill-in in den ursprünglich dünnbesiedelten Matrizen und verteuern damit gerade den Wurzelknoten. Dies begrenzt sowohl die Parallelit¨at — ohne Berücksichtigung der Kommunikationskosten ist ein Speedup von höchstens 6 möglich [Hüt96, S. 129] — als auch die maximale Größe der Probleme bei vorgegebenem Speicher. Die wesentliche Rechenzeitersparnis bei einem iterativen
Löser wird dadurch erreicht, dass man auf Eliminationsschritte beim Gleichungssystem-
6.3 Bisherige Lösungsvarianten
115
aufbau g¨anzlich verzichtet, also
Damit der dann verwendete iterative Löser schneller die gewünschte Lösungsgenauigkeit
liefert, ist es wichtig, dass die Kondition der Systemmatrizen durch Transformation auf
hierarchische Basis verbessert wird. Als Nebeneffekt der Hierarchisierung ist auch die
Entkopplung h¨angender Punkte bei Verwendung hierarchischer multilinearer Basisfunk
wie in Abschnitt 6.2.3 dargestellt.
tionen sehr günstig, n¨amlich
Nach dem Zusammenbau der Gleichungssysteme sind mehrere Baumdurchl¨aufe
notwen
dig, bis die gewünschte Genauigkeit erreicht ist. In einem Baumdurchlauf wird, ausge
hend von der Wurzel des Substrukturierungsbaums und einer N¨aherung
für die
Lösung, eine neue Iterierte
berechnet. Dabei sind die externen Freiheitsgrade
schon festgelegt, weil auf Grund der Stetigkeitsforderung der Lösungswert eines Punktes
in jedem Baumknoten, der ihn enth¨alt, gleich sein muss. Die N¨aherung für die Werte
der internen, nicht durch Dirichlet-Randbedingungen festgelegten Freiheitsgrade werden
durch Gauß-Seidel-Iterationsschritte verbessert [Stö89].
auf einem Gleichungssystem
Definition 6.2 Ein Gauß-Seidel-Schritt mit
-Matrix
zur Bestimmung einer neuen Iterierten ist die Abbildung
, wobei für alle gilt:
% Nachdem die Werte der neuen Iterierten
bestimmt sind, müssen derenÄnderungen für
die internen Freiheitsgrade auf den Lastvektor des jeweiligen Vaters übertragen werden.
Diese Anpassung ist notwendig, weil die internen Freiheitsgrade im Gleichungssystem
des Vaters nicht vorkommen.
Die einzelnen Schritte des resultierenden Lösungsverfahrens, inklusive Gleichungssystem-Aufbau im Build-Schritt und der Transformationen von Größen für den Vater- bzw.
Sohnknoten sind in Abb. 6.10 zusammengefasst. Es sind jeweils die nötigen Operationen
" Baumknoten w¨ahrend des angegebenen Schritts aufgelistet.
Ein hochgestellter
in einem
Index bedeutet eine vom Vaterknoten übernommene Größe, mit sind Werte aus den
Söhnen des aktuell bearbeiteten Knotens markiert. Dies werden wir auch in den Lösungsverfahren beibehalten, die wir in den n¨achsten Abschnitten darstellen.
/
In unserem Anwendungsbereich verbessert sich durch die hierarchische Transforma T
tion die Konditionszahl der Systemmatrizen von
auf
T bei
Unbekannten [Yse86, JL91, SBG96]. Damit verringert sich die
Anzahl der nötigen Iterationsschritte bis zum Erreichen der gleichen Lösungsgenauigkeit. Das iterative Lösungsverfahren GS-HB hat die Zeitkomplexit¨at von nur mehr
[Hüt96], im Vergleich zu T
des direkten Verfahrens mit statischer KonT
densation.
/
/
116
Die rekursive Substrukturierung in der numerischen Simulation
6.4 Neue Lösungsvarianten
Wie im vorigen Abschnitt dargestellt, ist eine Vorkonditionierung der Systemmatrizen
— wie die Transformation auf eine hierarchische Basis — notwendig, damit iterative
Lösungsverfahren auch in der Praxis schneller sind als direkte Lösungsverfahren. Kombinieren wir diese Vorkonditionierung mit der Elimination weniger, aber betragsm¨aßig
großer Eintr¨age in der Systemmatrix, so erhalten wir ein nochmals bezüglich Laufzeit
leicht verbessertes Verfahren, das wir in Abschnitt 6.4.1 vorstellen. Es handelt sich dabei um ein basisabh¨angiges Eliminationsverfahren, bei dem nur die st¨arksten Kopplungen
vorab eliminiert werden.
Nachdem bisher lediglich ein Gauß-Seidel-Löser bei der rekursiven Substrukturierung
verwendet wurde, liegt es nahe, die Idee der Transformation auf hierarchische Basis auch
mit anderen Standard-Lösungsmethoden für lineare Gleichungssysteme zu kombinieren
und auf die rekursive Substrukturierung anzuwenden. Wir pr¨asentieren in Abschnitt 6.4.2
eine Anpassung der Jacobi-Relaxation an die rekursive Substrukturierung, die gegenüber
dem Verfahren GS-HB weniger Speicher benötigt und sich deshalb für sehr große Gleichungssysteme eignet.
Ähnliche Speichervorteile, aber eine wesentlich schnellere Lösungsmethode als die
Jacobi-Variante liefert eine Kombination mit dem Verfahren der konjugierten Gradienten in Abschnitt 6.4.3, falls die Systemmatrizen definit sind.
6.4.1 Ein Gauß-Seidel-L¨
oser mit teilweiser Elimination von starken
Kopplungen
Der Grund dafür, dass bei Verzicht auf die Elimination der Kopplungen zwischen internen und externen Freiheitsgraden mehrere Durchl¨aufe durch den Substrukturierungsbaum
nötig sind, ist die Assemblierung von nicht-vollst¨andigen Gleichungssystemen, denn der
Einfluss
der internen Freiheitsgrade wird im n¨achsthöheren Knoten weggelassen.
Diese Situation können wir verbessern und zum Erreichen der gleiche Lösungsgenauigkeit Iterationen sparen, wenn wenigstens die st¨arksten Einflüsse gleich bei der Assemblierung der Gleichungssysteme berücksichtigt werden. Dies kann dadurch erreicht werden,
dass diese st¨arksten Kopplungen aus den Gleichungen der externen Freiheitsgrade eliminiert werden.
Betrachten wir Abb. 6.6 auf Seite 108, so stellen wir fest, dass für das Gebiet eines Kno
tens sowohl die .
-Basisfunktionen mit Spitze über den Eckpunkten des Gebiets als
auch die Basisfunktion mit Spitze auf dem Mittelpunkt des Gebiets Tr¨ager besitzen, die
sich über dieses gesamte Gebiet erstrecken. Deswegen f¨allt die Kopplung dieser Punkte in der Matrix besonders stark aus. Ein weiterer Grund für die betragsm¨aßig großen
Matrixeintr¨age ist, dass die zugehörigen
Basisfunktionen zu den Teilr¨aumen der hierar/ chischen Basisfunktionen
,
gehören und damit ganz oben in der Hierarchie
/
/ 6.4 Neue Lösungsvarianten
117
1
4
0
0
0
0
1
4
s
s
0 0 0 0
PSfrag replacements
Abbildung 6.11 Links: Auf dem Gebiet sind die beim Verfahren GS-HB-E zu eliminerenden
Kopplungen zwischen den betreffenden Punkten mit dicken Pfeilen eingezeichnet. Rechts: In der
Matrix sind die zu eliminierende Eintr¨age mit 0 bezeichnet. Die Elimination wird durch geeignete
Subtraktion der Zeile und Spalte für den Separator von den übrigen dunkel hinterlegten Zeilen und
Spalten bewerkstelligt.
der Basisfunktionen angesiedelt sind. Deshalb ist es sinnvoll, diese starken Kopplungen
zu eliminieren. In Abb. 6.11 sind die entsprechenden Matrixeintr¨age durch 0 markiert.
Algebraisch beschreiben wir die partielle Elimination der vier st¨arksten Kopplungen mit
Hilfe von Reihenoperationen der Matrix (vgl. S. 110). Um den Eintrag zu eliminie
ren, brauchen wir die Operation Mit dem Index für den Gebietsmittelpunkt ergibt sich dann die Eliminationsmatrix zu
Wir
und wegen der Symmetrie des ursprünglichen Gleichungssystems gilt:
fügen diesen Eliminationsschritt nach der Hierarchisierung des Gleichungssystems in das
Verfahren GS-HB aus Abschnitt 6.3.1 ein und transformieren in der Lösungsphase beim
Top-down-Schritt mit und im Bottom-up-Schritt mit in jedem Baumknoten. So
erhalten wir das Verfahren GS-HB-E, das nochmals in Abb. 6.12 zusammengefasst ist.
Das Verfahren GS-HB-E zeichnet sich zwar nicht durch eine Verbesserung der Zeitkomplexit¨at gegenüber dem Verfahren GS-HB aus, eignet sich aber in der Praxis noch besser
für die verteilte Bearbeitung. Denn w¨ahrend die Anzahl der Rechenoperationen durch die
zus¨atzlichen Eliminationsschritte leicht zunimmt, bleiben die Kosten der Kommunikation
in jeder Iteration gleich. Und insgesamt werden Iterationen eingespart, wie sich bei den
Tests in Kapitel 7 best¨atigt. Der durch die Elimination entstehende Fill-in des Gleichungssystems und der damit verbundene Speicherbedarf f¨allt dagegen kaum ins Gewicht, wie
die experimentellen Versuche ebenfalls zeigen werden.
118
Die rekursive Substrukturierung in der numerischen Simulation
GS-HB-E, Build-Schritt
(B1) bis (B3) wie Gauß-Seidel
(BE)
,
partielle Elimination starker Kopplungen
wie Verfahren
GS-HBGS-HB-E, Top-down-Schritt
Transformation wegen partieller Elimina-
(D1)
(DE)
tion
(D2) bis (D5) wie Verfahren GS-HB
GS-HB-E, Bottom-up-Schritt
(U0) bis (U2) wie Verfahren GS-HB
8 (UE) 8
Transformation wegen partieller Elimina
tion
(U3) bis (U4) wie Verfahren GS-HB
Abbildung 6.12 Berechnungsschritte beim Gauß-Seidel-Verfahren mit partieller Elimination
auf hierarchischer Basis
6.4.2 Ein Jacobi-L¨
oser auf hierarchischer Basis
Wenn das Gitter für eine Problemlösung besonders fein und damit der Substrukturierungsbaum sehr tief sind, und wenn es nicht so sehr auf die Rechenzeit ankommt, ist
eine Lösungsvariante auf Basis des Jacobi-Verfahrens nützlich. Sie zeichnet sich dadurch aus, dass in jedem Baumknoten außer einer 4 4-Matrix nur die Diagonalelemente
der Systemmatrix gehalten werden müssen. Dies beruht auf der Eigenschaft der JacobiRelaxation, dass für alle Multiplikationen mit der Systemmatrix nur Werte aus dem vorigen Iterationsschritt verwendet werden.
auf einem Gleichungssystem ist
Definition 6.3 Ein Jacobi-Schritt
, wobei für alle und einen Relaxadie Abbildung tionsfaktor mit
gilt:
%
Für die Bestimmung eines Wertes in der neuen Iterierten
werden nur Werte der
vorigen Iterierten
verwendet. Damit können in den Bl¨attern schon die Beitr¨age
gebildet werden. Diese Beitr¨age werden zum Vaterknoten gereicht. Werden sie im
Bottom-up-Schritt aufgesammelt,
so bleibt im Top-down-Schritt
nur
noch
für
6.4 Neue Lösungsvarianten
119
1-4
5-8
s
1
4
5
8
s
PSfrag replacements
Abbildung 6.13 Für das Verfahren J-HB sind die benötigte Matrix-Eintr¨age alle grau unterlegt.
Davon können die hellgrauen Eintr¨age nach der Hierarchisierung der Matrix gelöscht werden.
zu berechnen.
Akzeptable Konvergenz erzielen wir jedoch nur, wenn wir wieder unter Verwendung
hierarchischer Basen rechnen. Deshalb genügt es nicht, ausschließlich die Diagonalelemente zu den Vaterknoten zu reichen, sondern es sind alle Eintr¨age notwendig, die an
den n¨achsthöheren Hierarchisierungsschritten beteiligt sind. In dem rechten Schema von
Abb. 6.7 auf Seite 109 sehen wir, dass dies die Eintr¨age für die Eckpunkte sowie für die
Mittelpunkte der Randlinien und des Separators sind. Aus den vier Sohnknoten werden
dazu jeweils nur die vier Eckpunkte benötigt. Beim Aufbau können also nach der Trans formation von
auf 8
alle Nicht-Diagonal-Elemente 8 mit
und
oder
gelöscht werden (Abb. 6.13).
Der Lastvektor wird nur in den Bl¨attern gebraucht, muss also nicht nach oben weitergereicht und transformiert werden. Dagegen müssen wir im Bottom-up-Schritt die Residu
enanteile
mit
und im Top-down-Schritt die Lösungsvektoren
mit transformieren, genauso wie beim Verfahren GS-HB. Zur Übersicht fasst Abb. 6.14 die in einem
Baumknoten anfallenden Operationen bei dem eben beschriebenen Jacobi-Löser zusammen, den wir Verfahren J-HB nennen.
Auf einem Gleichungssystem hat das Jacobi-Verfahren die gleiche Zeitkomplexit¨at wie
das Gauß-Seidel-Verfahren [Stö89]. Mit dem Beweis aus [Hüt96, S.113] für das Verfahren GS-HB folgt somit für das Verfahren J-HB ebenfalls die gleiche Zeitkomplexit¨at
von T
. J-HB benötigt aber in der Praxis (um einen konstanten Faktor) mehr
Iterationen. Zweck des Verfahrens J-HB ist die Einsparung von Speicherplatz für die Systemmatrizen in Nicht-Blatt-Knoten, so dass bei gleicher Anzahl verfügbarer Rechner mit
feineren Gittern und dadurch mit größerer Genauigkeit der Diskretisierung gerechnet werden kann.
/
120
Die rekursive Substrukturierung in der numerischen Simulation
(B0)
(B1)
(B2)
(B3)
(D1)
(D2)
(D3)
(U0)
(U1)
(U2)
8
J-HB, Build-Schritt
Initialisierung
Zusammenfassung von Eintr¨agen der Ma trix aus den Söhnen
Hierarchisierung der Matrix
Ausdünnung bis auf 4 4-Matrix und Diagonale
8 " %
J-HB, Top-down-Schritt
[falls nicht Wurzel:] Neue Lösungswerte
vom Vaterknoten
neue Iterierte aus Residuum und Diago für
nalelementen
Dehierarchisierung
J-HB,
Bottom-up-Schritt
[falls Blatt:] neue Residuenanteile
8 [falls nicht Blatt:] Zusammenfassung der
Residuenanteile aus den Söhnen für den
Lastvektor
8 Hierarchisierung
Abbildung 6.14 Berechnungsschritte beim Jacobi-Verfahren auf hierarchischer Basis
6.4.3 Ein CG-L¨
oser auf hierarchischer Basis
Wenn die bei der elementaren Diskretisierung in den Bl¨attern entstehenden linearen Gleichungssysteme symmetrisch und positiv definit sind — was bei vielen Finite-ElementDiskretisierungen für lineare elliptische partielle Differentialgleichungen der Fall ist —,
können wir die Vorteile des Verfahrens GS-HB (Konvergenzgeschwindigkeit) und des
Verfahrens J-HB (Speichereinsparung) für die rekursive Substrukturierung kombinieren. Wir verwenden hierfür das Verfahren der konjugierten Gradienten (CG-Verfahren
[BBC 94]) zur Lösung der Knotengleichungssysteme. Der sequentielle Grundalgorith mus des CG-Verfahrens mit einer Anfangssch¨atzung
des Lösungsvektors ist in
Abb. 6.15 angegeben.
Bei der rekursiven Substrukturierung können wir allerdings nicht alle Eintr¨age der Vek toren !
und in einem Speicherblock halten, sondern müssen die Werte über die
Knoten des Baums verteilen, entsprechend den jeweils vorhandenen Punkten auf dem
Rand und dem Separator eines Knotens. Die Skalarprodukte in den Schritten (4) und (8)
werden deshalb stufenweise über die internen Punkte aller Baumknoten gebildet. Damit
ist dann in Schritt (5) der Wert (bzw. in Schritt (1) der Wert ) verfügbar. In der
Wurzel des Baums sind schließlich alle Beitr¨age zu (bzw. zu ) aufgesammelt und
(bzw.
) kann berechnet werden. Der Wert
(bzw.
) wird im anschließenden
6.4 Neue Lösungsvarianten
121
für (1) + und
/ solange
! !
(2)
! (3)
! (4)
(5)
(6)
! (7)
(8)
Abbildung 6.15 Der sequentielle Grundalgorithmus des CG-Verfahrens
Schritt (5) (bzw. (2)) in jedem Baumknoten für die weiteren Berechnungen benötigt, d. h.
der Wert muss von oben nach unten an alle Baumknoten verteilt werden.
Somit ergeben sich für eine Iteration des CG-Verfahrens zwei Zyklen: Der erste Zyklus
umfasst Schritt (1) bis (4), der zweite Zyklus Schritt (5) bis Schritt (8). Die Bildung der
Beitr¨age ! kann dagegen vollst¨andig in den Bl¨attern des Baums geschehen; eine Verteilung der Matrix in höhere Baumknoten ist also nicht erforderlich.
Auch das CG-Verfahren muss zum Erreichen akzeptabler Konvergenz vorkonditioniert
werden [BBC 94, Hac93], bei uns wieder durch Transformation auf hierarchische Basisdarstellung. Wir verwenden die gleichen Basisfunktionen in jedem Baumknoten wie
bei den vorigen Verfahren. Deshalb sind Werte, die zu den Sohnknoten gereicht werden, zu dehierarchisieren, und Werte, die von den Sohnknoten übernommen werden, zu
hierarchisieren. Die Hierarchsierung ist in jedem Baumknoten nach Schritt (2) zum Herunterreichen des Vektoranteils für die neue Suchrichtung ! und nach Schritt (3) zum Heraufreichen des Residuumanteils notwendig. Im Detail gibt Abb. 6.16 Auskunft über den
Ablauf der Berechnung in jedem Baumknoten bei diesem neuen Verfahren CG-HB.
Bei der Laufzeit erreichen wir eine asymptotische Verbesserung gegenüber dem ursprünglichen Verfahren GS-HB. Da wir für die Ermittelung der Suchrichtung und für die Bil
dung der Faktoren und immer hierarchisierte Vektoren verwenden, ist die Anzahl der
Iterationen in der Größenordnung T , im Vergleich zu T
der Verfahren
GS-HB und J-HB [Yse86]. Wie schon in [Hüt96] beobachtet wurde, steigt in der Praxis
jedoch die Anzahl der Iterationen beim Verfahren GS-HB langsamer als in der theoretischen Absch¨atzung. Bei Verfahren CG-HB steigt, wie wir bei den Beispielrechnung in
Kapitel 7 sehen werden, die Zahl der Iterationen mit dem erwarteten logarithmischen
Faktor.
/
Bezüglich des Speicherbedarfs hat ist Verfahren CG-HB im Vorteil: In jedem Knoten des
Baums müssen zwar vier Vektoren gehalten werden ( !
und ). Dies ist aber weniger
als der Speicher, den die Systemmatrizen im Verfahren GS-HB benötigen, da dort jede
Matrixzeile außer dem Diagonalelement noch mindestens zwei weitere Eintr¨age hat.
122
Die rekursive Substrukturierung in der numerischen Simulation
(B0)
(B1)
8 (B2)
(B3)
CG-HB, Build-Schritt
!8
(D1.2)
!8
(D2.0)
(D2.1)
8 (U1.0)
(U1.1)
CG-HB,
1.Zyklus, Top-down-Schritt
/
[falls Wurzel:] Bestimmung von Faktor
" " [falls nicht Wurzel:] Neuer Anteil an
!
;
Suchrichtung und vom Vaterknoten
8
neue Iterierte aus Residuum und Dia! gonalelementen für interne Freiheits
grade
8 !
Dehierarchisierung
CG-HB, 1.Zyklus, Bottom-up-Schritt
[falls Blatt:] neue Residuenanteile
! 8 [falls nicht Blatt:] Zusammenfassung
der Residuenanteile aus den Söhnen für
den Lastvektor
Hierarchisierung
Bestimmung des Anteils für Faktor !8
8
CG-HB, 2.Zyklus, Top-down-Schritt
[falls Wurzel:] Bestimmung des Fak
tors
" [falls nicht Wurzel:] vom Vaterkno ten
neue Iterierte für Lösungswerte neue Iterierte für !
(D1.3)
[falls Blatt:] Bestimmung des StartResiduums
[falls nicht Blatt:] Zusammenfassung der
Residuenanteile der Söhne
Summand für Hierarchisierung
(D1.1)
(U2.0)
(U2.1)
(D1.0)
(D2.2)
(D2.3)
(U1.2)
(U1.3)
CG-HB, 2.Zyklus, Bottom-up-Schritt
[falls Blatt:] Anteil an Faktor [falls nicht Blatt:] Bestimmung des An
teils für Faktor
Abbildung 6.16 Berechnungsschritte beim CG-Verfahren auf hierarchischer Basis
6.5 Das Koordinationsprogramm in FASAN
123
Wir haben nun drei neue Lösungsvarianten für das Verfahren der rekursiven Substrukturierung vorgestellt, die sich alle in das Ablaufschema von Abb. 6.2 einfügen lassen.
6.5 Das Koordinationsprogramm in FASAN
Wünschenswert ist jetzt ein Rahmenprogramm für die rekursive Substrukturierung,
in dem diese vier Löser einfach integriert werden können. Das Ablaufdiagramm von
Abb. 6.2 auf Seite 102 liefert einen groben Anhaltspunkt für die Koordinationsschicht
des zu erstellenden FASAN-Programms ARESO (Arbeiten mit rekursiv substrukturierten
Objekten)
function Areso (T:Tree; d:int[]) -> T’:Tree;
{
// Baum erweitern oder aus Startknoten aufbauen
T_new, W_new = ExtendTree (T, d);
// neuen und alten Baum vergleichen und ggf. abbrechen
if stopAreso (selectNode(T_new), selectNode(T)) then
T’ = T;
else
// Verteilung berechnen
d_ = balance (W_new, numHosts);
// Gleichungssysteme aufbauen
T_ = Build (nonode(), T_new, d_);
// Löser-Iterationen
T_sol = IterSolve (T_, d_);
// neue äußere Schleife
T’ = recycle Areso (T_sol, d_);
fi
}
Zur Lastverteilung, auf die wir erst im folgenden Abschnitt genauer eingehen, liefert
die Funktion ExtendTree einen WeightTree, der die vorausgesagte Rechenlast für
jeden Knoten enth¨alt. Mit dem Ergebnis der Funktion balance kann die Funktion
Build die Gleichungssysteme bereits auf dem richtigen Zielrechner erstellen, damit keine unnötige Migration von Node-Objekten anf¨allt. Aber zun¨achst zur Verfeinerung des
Substrukturierungsbaums durch Bisektion:
type Tree = Leaf (leafNode:Node)
| Branch (branchNode:Node; leftTree,rightTree:Tree)
function selectNode (T:Tree) -> nd:Node
{ if isLeaf(T) then nd = T.leafNode; else nd = T.branchNode; fi
}
124
Die rekursive Substrukturierung in der numerischen Simulation
function ExtendTree (T:Tree; d:int[]) -> T’:Tree; W’:WeightTree
{
nd = selectNode(T);
if stopExtend (nd) then
// Abbruch der Verfeinerung
T’ = T;
W’ = weightLeaf(nd);
else
if isLeaf (T) then
// neue Blätter anlegen
T1 = Leaf (extendTreeDown (nd, 0));
T2 = Leaf (extendTreeDown (nd, 1));
else
T1 = T.leftTree; // bestehende Unterbäume verwenden
T2 = T.rightTree;
fi
T1’, W1’ = ExtendTree (T1, d)
on dest_left (nd, myHost, numHosts, d);
T2’, W2’ = ExtendTree (T2, d)
on dest_right(nd, myHost, numHosts, d);
// Punkte in neuem Knoten klassifizeren:
nd’ = extendTreeUp (nd, selectNode(T1’),
selectNode(T2’));
T’ = Branch (nd’,T1’,T2’);
W’ = weightTree (nd’, W1’.getLTree, W2’.getLTree));
fi
}
Für eine effiziente Ausführung ist es wichtig, dass die Argument- und Ergebnisparameter von Funktionsaufrufen mit Lokationsattributen möglichst wenig Kommunikationsaufwand verursachen. Dazu bietet es sich z. B. an, die Variable für die Systemmatrix in der
Java-Klasse Node als transient zu markieren, denn dann wird sie von RMI standardm¨aßig nicht zusammen mit dem Node-Objekt serialisiert und verschickt. Die Matrix
wird n¨amlich nur im Bottom-up-Schritt von der Funktion Build zwischen Knoten transferiert und kann hier explizit als Parameter angegeben werden.
function Build (par:Node; T:Tree; d:int[])
-> T’:Tree; S’:SparseMat
{
nd = selectNode(T);
if isRoot(nd) then nd_ = buildRoot (nd);
else nd_ = buildDown (par, nd); fi
if isLeaf (T) then
T’ = Leaf (buildLeaf (nd_));
S’ = getMatrix(nd_);
else
T1’,S1’ = Build (nd_, T.leftTree, d)
6.5 Das Koordinationsprogramm in FASAN
125
on dest_left(nd, myHost, numHosts, d);
T2’,S2’ = Build (nd_, T.rightTree, d)
on dest_right(nd, myHost, numHosts, d);
nd’ = buildUp (nd_, selectNode(T1’), S1’
selectNode(T2’), S2’);
T’ = Branch (nd’,T1’,T2’);
S’ = getMatrix(nd’);
fi
}
Die Berechnungsschritte in den Knoten des Substrukturierungsbaums, die sich aus den
Beschreibungen der Lösungsverfahren GS-HB (Abb. 6.10), GS-HB-E(6.12), J-HB(6.14)
und CG-HB(6.16) ergeben, sind in externen Funktion zusammengefasst:
function buildRoot (nd:Node) -> nd’:Node extern Java
function buildDown (par:Node; nd:Node)
-> nd’:Node extern Java
function buildLeaf (nd:Node;) -> nd’:Node extern Java
function buildUp (nd:Node; n1:Node; s1:SparseMat;
n2:Node; s2:SparseMat)
-> nd’:Node; s’:SparseMat extern Java
function solveRoot (nd:Node) -> nd’:Node extern Java
function solveDown (par:Node; nd:Node)
-> nd’:Node extern Java
function solveLeaf (nd:Node) -> nd’:Node extern Java
funvtion solveUp (nd:Node; n1:Node; n2:Node)
-> nd’:Node extern Java
Für jeden Baumknoten werden jeweils entweder die Root- oder die Down-Funktion aufgerufen und entweder die Leaf- oder die Up-Funktion. Die Programmierung dieser externen Funktionen in Java bereitet keine größeren Schwierigkeiten, deshalb verzichten wir
auf einen vollst¨andigen Abdruck des Quelltextes.
Die Funktion Solve ist — bis auf die Parameter für die Matrizen und Ersetzung des
Funktionsnamens — identisch mit Build. Die Funktion IterSolve zur Konstruktion
einer Schleife über die Solve-Funktion haben wir im Prinzip schon in Beispiel 3.5 auf
Seite 34 angegeben.
Falls ein Knoten im Substrukturierungsbaum vier Söhne haben soll, oder allgemein eine flexiblere Gebietszerlegung implementiert wird, ist eine Formulierung für beliebigen
Verzweigungsgrad durch den hierarchischen Datentyp
type Forest = forest (first:Tree; rest:Forest) | empty()
type Tree = branch (node:Node; children:Forest) | leaf (nd:Node)
126
Die rekursive Substrukturierung in der numerischen Simulation
nötig. Anstatt zu den rekursiven Aufrufen für jede Funktion einen eigenen Iterator über
Forest zu schreiben (also buildForst, solveForest etc.), verwenden wir dann
kürzer eine Funktion höherer Ordnung, der wir die benötigten Down- und Up-Funktionen
mit folgenden Funktionstypen übergeben:
type NodeFun = Node->Node
type DownFun = (Node,Node)->Node
type UpFun = (Node,Forest)->Node
Die Funktion für den Abstieg im Baum, mapDown, haben wir bereits in Beispiel 3.10 auf
Seite 38 behandelt. Die Funktion für den Aufstieg lautet:
function mapUp (leaf_f:NodeFun; up_f:UpFun; t:Tree) -> t’:Tree
{
if isleaf(t) then
t’ = leaf (leaf_f(t.leafVal));
else
branch(x, sons) = t;
sons’ = map (mapUp(leaf_f, up_f, _), sons);
t’ = branch (Node(up_f(x, sons’), sons’);
fi
}
Dass die externe Funktion up_f die Sohnknoten in einem Parameter vom Typ Forest
bekommt, ist weiter kein Problem. Denn Konstruktor- und Selektorfunktionen für hierarchische Datentypen sind auch in den externen Funktionen als Methoden der Verpackungsklasse Tree verwendbar (vgl. hierzu die Übersetzung von FASAN nach Java,
Abschnitt 5.3.2). Somit wird die Funktion Solve sehr kurz:
function Solve (par:Node; t:Tree) -> t’:Tree
{
t_ = mapDown (solveRoot(_), solveDown(_,_), t);
t’ = mapUp (solveLeaf(_), solveUp(_,_), t_);
}
Die Kommunikationsbeziehungen zwischen den Baumknoten folgen den Kanten des
Baums zwischen Vater- und Sohnknoten. Trotzdem ist der Aufwand für die zeitlich und
inhaltlich korrekte Übertragung der Knoteninhalte eines unbalancierten Baums bei verteilter Berechnung nicht zu untersch¨atzen, wenn diese von Hand programmiert werden
muss. Je komplizierter die betrachteten Gebiete und die verwendeten Löser werden, desto komplizierter werden auch die verwendeten Datenstrukturen und die Migration der
entstehenden Baumstrukturen.
Hier erleichtert der Einsatz von FASAN die Programmierung erheblich, weil speziell
Kommunikation und Synchronisation der parallelen Kontrollflüsse, die die Unterb¨aume
bearbeiten, automatisch generiert werden.
6.6 Lastverteilung
127
Das folgende Kapitel wird experimentelle Ergebnisse liefern, die mit diesem Rahmenprogramm (für Bisektion) gewonnen wurden und die demonstrieren, wie effizient sich FASAN für diese Anwendung in der Praxis verh¨alt. Zun¨achst fehlen uns aber noch Konzepte
und die Implementierung der Lastverteilung, die dieses Anwendungskapitel abschließen.
6.6 Lastverteilung
Dieser Abschnitt stellt Verfahren vor, die für die Lastverteilung bei der verteilten rekursiven Substrukturierung zum Einsatz kommen. Nach einer allgemeinen Beschreibung geben wir auch die Lokationsfunktionen an, die zur Lastverteilung im FASAN-Programm
eingesetzt wurden. Grunds¨atzlich ist zu unterscheiden, ob der Substrukturierungsbaum
balanciert ist oder nicht. Im ersten Fall genügt ein statisches Lastverteilungsverfahren
(Abschnitt 6.6.1). Im adaptiven Fall ist es wichtig, die Kostenabsch¨atzungen für die
zu verteilenden Einheiten zu bestimmen. In Abschnitt 6.6.2 überlegen wir uns, welche
Berechnungs- und Kommunikationskosten in den Knoten des Substrukturierungsbaums
anfallen. Das dynamische Lastverteilungsverfahren (Abschnitt 6.6.3) beruht darauf, nach
jeder Erweiterung des Baums in jedem Knoten die zukünftigen Kosten zu vermerken und
danach die Knoten noch vor der Bildung der Knotengleichungssysteme neu auf die Rechner zu verteilen.
6.6.1 Ein statisches Verfahren
Eine einfache statische Verteilungsstrategie für balancierte B¨aume nutzt die mögliche Parallelit¨at in den oberen Baumebenen, angefangen von der Wurzel. Jeder Knoten einer
Baumebene wird einem neuen Rechner zugeteilt, solange noch Rechner ohne Baumknoten sind. Wenn alle Rechner mit parallelen Funktionsaufrufen besch¨aftigt sind, werden
Knoten in tieferen Baumebenen dem Rechner zugeteilt, auf dem ihr Vaterknoten liegt.
Eine Verteilungsstrategie aus [Hüt96], die auch jeweils den Vaterknoten einem eigenen
Rechner zuweist, ist nur bei Hauptspeichermangel sinnvoll.
Die zugehörige Lokationsfunktion lautet dann für den linken Sohn eines Knotens in Tiefe
depth
int static_left (int depth, int myHost, int numHosts) {
return myHost;
}
und für den rechten Sohn, der gegebenenfalls auf einen neuen Rechner migriert wird
int static_right (int depth, int myHost, int numHosts) {
int newHost = myHost + numHosts / pow (NUM_SONS, depth+1);
return (newHost > numHosts) ? myHost : newHost;
}
128
Die rekursive Substrukturierung in der numerischen Simulation
0
0
8
0
0
0
0
2
1
8
4
2 3
4
4
6
5
6
8
7
8
12
10
12
0
14
9 10 11 12 13 14 15
0
1
4
2
4
3
5
8
6
7
8
12
9 10 11 12 13 14 15
Abbildung 6.17 Verteilung der Knoten balancierter Substrukturierungsbäume auf 16 Rechner:
links bei rekursiver Gebietszerlegung in jeweils zwei Teilgebiete, rechts bei Zerlegung in vier
Teilgebiete. Die Nummern bei den Knoten geben den Zielrechner an.
In Abb. 6.17 ist die Verteilung der Knoten auf 16 Rechner dargestellt, bei rekursiver Zweiteilung des Gebiets (Bisektion) und bei rekursiver Vierteilung.
6.6.2 Kostenabsch¨
atzung der L¨
osungsverfahren
Sowohl der Build-Schritt zum Aufbau der Knotengleichungssysteme als auch die anschließenden Solve-Schritte vollziehen in der Iteration zur Lösung der Gleichungssysteme
komplette Baumdurchl¨aufe. Deshalb ist es sinnvoll, bei nutzbaren Rechnern disjunkte Teilb¨aume zu finden, die möglichst gleiche Rechenlast erzeugen und zusammen alle
Knoten enthalten.
Dazu ist aber eine Vorhersage der Rechenlast nötig, die jeder Baumknoten verursacht.
Den größten Anteil an den Berechnungskosten haben
der Build-Schritt zur Erstellung der Gleichungssysteme und
die Top-down- und Bottom-up-Schritte für die iterative Lösung der Gleichungssysteme.
In modernen numerischen Verfahren ist allerdings die Zahl der Gleitpunktoperationen
allein kein ausreichendes Maß für die Berechnungskosten, weil etwa bei dünnbesiedelten
Matrizen für die Verfolgung von Zeigerstrukturen oft mehr Aufwand entsteht als durch
die arithmetischen Operationen. Hinzu kommt noch das dynamisch adaptive Verhalten
vieler Algorithmen (so auch der rekursiven Substrukturierung), das selten statisch genau
bestimmt werden kann.
Erleichternd für unsere Aufgabe der Lastverteilung ist aber, dass wir nicht die absoluten
Rechenkosten benötigen, sondern nur die Kosten-Verhältnisse der einzelnen Knoten. Bei
effizienter Implementierung vor allem der Datenstruktur für die dünnbesiedelten Matrizen
im Substrukturierungsbaum ist dann der Gesamtaufwand etwa proportional zu der Anzahl
der Gleitpunktoperationen. Deshalb genügt es, wenn wir uns hier auf die Gleitpunktoperationen beschr¨anken.
6.6 Lastverteilung
129
In den folgenden Kostenabsch¨atzungen benutzen wir die Größen:
Anzahl der externen Freiheitsgrade eines Knotens
Anzahl der internen Freiheitsgrade eines Knotens
Anzahl der Söhne eines Knotens (= Verzweigungsgrad )
die durchschnittliche Anzahl von Matrixeintr¨agen in einer Matrixzeile
Die Anzahl aller Matrixeintr¨age beim Verfahren GS-HB bei gleichm¨aßiger Verfeinerung
ist nach [Hüt96, S.103ff]
/
+
/ mit
Baumtiefe bei Bisektion
(6.3)
wobei ’ ’ nur in den F¨allen gilt, in denen der Knoten einen Teil des Gebietsrands oder
(bei adaptiver Verfeinerung) h¨angende Punkte auf dem Separator enth¨alt.
der Gleitpunktoperationen in den einzelnen Schritten der
Für die nötige Anzahl
Lösungsverfahren überlegen wir uns zun¨achst die Kosten folgender elementarer Operationen:
+ .
Skalarprodukt zweier Vektoren der L¨ange :
Pro Vektorkomponente ist eine Addition und eine Multiplikation erforderlich.
Multiplikation einer Matrix mit einem Vektor der L¨ange :
+ .
Pro Vektorkomponente sind für alle Eintr¨age der entsprechenden Matrixzeile
eine Addition und eine Multiplikation nötig.
Hierarchisierung eines Vektors:
+ .
Für jeden der 12 Eintr¨age in der Hierarchisierungstabelle muss eine Addition und
eine Multiplikation erfolgen.
Hierarchisierung einer Marix:
.
Für jeden Eintrag der Hierarchisierungstabelle ist eine Zeilenoperation auf Zei
leneintr¨agen auszuführen. Dieselben Operationen sind wegen der Multiplikati
on der Hierarchisierungsmatrix von links und von rechts (
) nochmals nötig.
Assemblierung eines Vektors mit Komponenten:
.
Jeder Vektoreintrag wird aus den Summanden der Sohnknoten zusammengesetzt.
Assemblierung einer Matrix mit Zeilen:
.
In jeder Matrixzeile werden für jeden der Eintr¨age aus jedem der Sohnknoten
Eintr¨age kombiniert.
Da sich die einzelnen Schritte der Lösungsverfahren aus diesen elementaren Operationen zusammensetzen, können wir sofort die gen¨aherten Kosten pro Baumknoten und pro
Durchlauf bei den beschriebenen iterativen Verfahren angeben.
130
Die rekursive Substrukturierung in der numerischen Simulation
Verfahren GS-HB
Build-Schritt:
(B1)
(Abb. 6.10)
(B2)
+
+
(B3)
Top-down- und Bottom-up-Schritt:
(D1)
(D2)
(D3)
+
+ + +
Verfahren GS-HB-E
(U1)
(D4) (D5)
+
(U2)
+
(U3)
(U4)
(Abb. 6.12)
Die Kosten für den Gauß-Seidel-Löser mit partieller Elimination umfassen die obigen
Tabellen, und zus¨atzlich die Kosten der Multiplikation mit der Eliminationsmatrix bzw.
:
(BE)
+
(DE)
(UE)
Verfahren J-HB (Abb. 6.14)
%
Beim Jacobi-Verfahren wird (bis auf einen 4 4-Block) nur die Diagonale der Systemmatrix zum Vaterknoten gereicht, daher ist hier außer in den Bl¨attern
= 1. Der
Ausdünnungsschritt (B3) erfordert keine Gleitkomma-Operationen, löscht 60 Eintr¨age
(vgl. Abb. 6.11) und kann bei geeigneter Datenstruktur der Matrix mit diesem konstanten
Aufwand erfolgen.
Build-Schritt:
(B1)
(B2)
(B3)
+
Top-down- und Bottom-up-Schritt:
(D1) (D2) (D3)
(U0)
(U1)
+
(U2)
+
Verfahren CG-HB (Abb. 6.16)
Hier kommt die Systemmatrix (und damit die Zahl
Build-Schritt:
(B0)
(B1)
(B2)
+
) nur noch in den Bl¨attern vor:
(B3)
+
Top-down- und Bottom-up-Schritt im 1. Zyklus:
(D1.0) (D1.1) (D1.2) (D1.3)
(U1.0)
(U1.1)
+
+
(U1.2)
+
(U1.3)
+
6.6 Lastverteilung
131
Top-down-und Bottom-up-Schritt im 2. Zyklus:
(D2.0) (D2.1) (D2.2) (D2.3) (U2.0) (U2.1)
+
+
+
Mit diesen Tabellen haben wir jetzt die Anzahl der nötigen Gleitpunktoperationen pro
Baumknoten erfasst, und in guter N¨aherung proportional dazu den Gesamtaufwand an
Rechenoperationen.
Bei parallelen Berechnungen übersteigen die Kosten einer Kommunikationsoperation die
einer Rechenoperation um mehrere Größenordnungen, und selbst bei Berechnungen auf
gemeinsamem Speicher macht die Erzeugung und Verwaltung neuer Tasks oder Threads
einen nicht unerheblichen Teil der Laufzeit aus. Bei verteilten Berechnungen kommt
noch der Unsicherheitsfaktor dazu, dass die Verzögerungen auf dem Verbindungsnetzwerk schwanken.
Allerdings können diese Kosten bei den drei neuen Lösungsvarianten ebenfalls leicht abgesch¨atzt werden. In der Beschreibung der Algorithmen (Abb. 6.12, 6.14 und 6.16 in
Abschnitt 6.4) haben
wir immer Werte, die vom Vaterknoten
bezogen werden, mit einem
" oberen Index versehen, und Werte aus den Söhnen mit . Dort ist also unmittelbar ersichtlich, welche Werte, Vektoren und Matrizen im Fall, dass Vater- und Sohnknoten auf
verschiedenen Rechnern liegen, verschickt werden müssen. Bei Vektoren und Matrizen
müssen dabei nur die Werte versandt werden, die zu externen Punkten gehören, die in beiden Knoten vorkommen. Bei der RMI-Implementierung verteilter FASAN-Programme ist
diese Beschr¨ankung besonders wichtig, da ab einer Feldl¨ange von etwa 500 Eintr¨agen die
Übertragungszeiten der Argumente von RMI-Aufrufen sprunghaft und dann leicht überproportional zur Feldl¨ange ansteigen (vgl. Abschnitt 5.2.1).
6.6.3 Ein dynamisches Verfahren
Ein unausgeglichener Substrukturierungsbaum kann entstehen, wenn auf Basis eines Fehlersch¨atzers das zugrundeliegende Gitter und damit der Baum nur lokal verfeinert und
nicht alle Bl¨atter weiter unterteilt werden. Um in diesen F¨allen mehrere Rechner effizient
für die Berechnung nutzen zu können, ist es wichtig, die Zuteilung der Baumknoten an die
Rechner ebenso adaptiv vornehmen zu können. Ein einfaches, aber wirkungsvolles Verfahren basiert auf der Anwendung des HF-Algorithmus [BEE98b, BEE98a]. Dazu muss
zun¨achst jedem Baumknoten eine Last zugewiesen werden, die sich aus der Kostenabsch¨atzung aus Abschnitt 6.6.2 ergibt. Zus¨atzlich wird in jedem Baumknoten auch
das Gewicht des gesamten Unterbaums, dessen Wurzel ist, akkumuliert:
/ falls Blatt ist,
falls Knoten mit Söhnen und
/
ist.
132
Die rekursive Substrukturierung in der numerischen Simulation
Algorithmus HF, als Grundlage von Lokationsfunktionen1 angewandt auf diesen Substrukturierungsbaum, lautet dann:
import java.util.TreeSet;
import areso.WeightTree;
TreeSet hf (WeightTree t, int numHosts) {
{
TreeSet subproblems = new TreeSet();
subproblems.add(t);
while (subproblems.size() < numHosts) {
Tree q = (Tree) getElemWithMaxWeight(subproblems);
subproblems.remove(q);
subproblems.add(q.left());
subproblems.add(q.right());
}
return subproblems;
}
Damit erhalten wir numHosts Baumknoten, die wir auf die numHosts Rechner verteilen können. Knoten in Unterb¨aumen dieser Knoten werden demselben Rechner wie die
Teilbaum-Wurzel zugeteilt. Für die höheren Knoten kann jeweils einer der Rechner der
Sohnknoten ausgew¨ahlt werden, am besten der mit dem kleineren Gewicht, um den Speicherverbrauch besser auszugleichen. Für einen Baum mit hypothetischen Gewichten ist
in Abb. 6.18 angegeben, welche Knoten Algorithmus HF bei Verteilung auf 8 und auf 16
Rechner liefert.
Ein Vorteil dieser Verteilungsstrategie ist seine theoretisch nachweisbare PerformanceGarantie bezüglich einer (praktisch meist nicht erreichbaren) optimalen Verteilung.
Lemma 6.4 Algorithmus HF liefert einen Schnitt von
strukturierungsbaum mit Wurzel , so dass gilt:
/
/ " Dabei ist
/ Knoten in einem Sub-
aus allen Nicht-Bl¨attern p des Substruk-
turierungsbaums.
Für den Beweis siehe [BEE98b].
Für den Einsatz im FASAN-Programm verwenden wir die Nummern der Wurzeln der
ermittelten disjunkten Teilb¨aume als Indizes in einem int[]-Array. Eine Methode
1
für die Dokumentation der Set-Klasse siehe z.B. http://java.sun.com/products/jdk/1.2/docs/api/
6.6 Lastverteilung
133
153
70
64
48
27
8
15
12
4
0
8
5
1
48
9
2
4
10
3
3
2
16
2
4
3
11
22
4
10
4
4
3
12
3
5
5
6
6
7
Abbildung 6.18 Dynamische Lastverteilung durch Algorithmus HF für 8 Rechner (die grau hinterlegten Knoten) und 16 Rechner (die Knoten unmittelbar über den
schematisierten Unterb¨aumen). Die Zahlen in den Knoten geben hypothetische Gewichte an.
distribute hat dann dafür zu sorgen, dass jeder dieser Indizes eine andere Rechnernummer zugewiesen bekommt. Damit ist die in Abschnitt 6.5 benötigte Funktion die
folgende:
int[] balance (WeightTree w) {
return distribute(hf(w));
}
Die passenden Lokationsfunktionen sehen so aus:
int hf_left (Node nd, int actHost, int[] dest) {
int nodeId = 2 * nd.ident;
return idx = (nodeId < dest[0]) ? dest[nodeId] : actHost;
}
int hf_right (Node nd, int actHost, int[] dest) {
int nodeId = 2 * nd.ident + 1;
return idx = (nodeId < dest[0]) ? dest[nodeId] : actHost;
}
Wir haben jetzt also alle Teile beisammen, die für unsere FASAN/Java-Anwendung notwendig sind: den sequentiellen Grundalgorithmus der rekursiven Substrukturierung mit
verschiedenen Lösungsvarianten, das FASAN-Programm zur Koordination des parallelen
Ablaufs sowie zwei Strategien zur Lastverteilung, die wir für die Lokationsfunktionen
dest_left und dest_right im FASAN-Programm verwenden können. Bleibt uns
noch, das entstandene Programm zu testen.
134
Die rekursive Substrukturierung in der numerischen Simulation
135
Kapitel 7
Beispielrechnungen und
Leistungsmessungen
Als Abschluss der Arbeit demonstrieren wir in diesem Kapitel den praktischen Einsatz
von FASAN auf Grundlage der Java-Implementierung der rekursiven Substrukturierung.
Wir geben zum einen numerische Resultate an, die vorführen, dass die neuen Lösungsvarianten für den praktischen Einsatz geeignet sind. Zum anderen belegen Laufzeitmessungen auf einer SMP-Maschine und in einem Rechnernetz, dass sich FASAN als Werkzeug
eignet, um mit wenig Programmieraufwand effiziente verteilte numerische Programme zu
erstellen.
7.1 Testbeispiele für die Simulation
Wir betrachten zun¨achst einfache Randwertaufgaben, die zwar vom Standpunkt der numerischen Simulation leicht zu behandeln sind, dafür sich aber bei verteilter Ausführung
zum Testen von FASAN besonders eignen, weil durch die kurzen Rechenzeit in den Knoten des Substrukturierungsbaums h¨aufig Datenaustausch zwischen den Knoten stattfindet.
Dass die mit FASAN und Java implementierte Version der rekursiven Substrukturierung
auch für praktische Probleme einsetzbar ist, soll an einem Beispiel aus der Strukturmechanik vorgeführt werden.
% In allen Beispielen w¨ahlen wir als Gebiet, auf dem das Problem gelöst werden soll, das
Einheitsquadrat (
.
136
Beispielrechnungen und Leistungsmessungen
8
0.12
7
0.1
6
0.08
5
0.06
4
0.04
3
1
2
0.02
0.8
10
0
0.6
0.2
0.4
0.4
0.6
0.8
0.5
0
0.5
0.2
0
1 0
Abbildung 7.1 f(x,y) (links) und die numerisch berechnete Lösung (rechts) im Beispiel
POISSON auf dem Einheitsquadrat. Bei der Lösung ist die Verschiebung des Maximums aus dem
Mittelpunkt in Richtung positiver x- und y-Achse zu erkennen.
/ / / (/
Poisson-Gleichung Die zweidimensionale Poisson-Gleichung ist die Randwertaufgabe
im Gebiet (
auf dem Rand ( (7.1)
(
(
(
Dabei bezeichnet die gesuchte Lösungsfunktion . Zur Diskretisierung des Differentialoperators verwenden wir den 5-Punkte-Stern
%
bzw. einen 3-Punkte-Stern am Rand des Gebiets und in den Bl¨attern mit einem 2 2 Gitter,
+
/ beispielsweise für den linken oberen Punkt .
Für unsere Beispielrechnungen verwenden wir speziell die Funktionen
(
(
(POISSON)
(
Die Funktion sowie die mit dem FASAN/Java-Programm berechnete Lösung
sind in Abb. 7.1 dargestellt.
(
Stationäre Wärmeleitung Mit der Gleichung (7.1) lassen sich auch station¨are W¨armeleitprobleme beschreiben [Hüt96]. Hierzu w¨ahlen wir als konkretes Beispiel
(HEAT1)
(
(
0
0
0 (
7.1 Testbeispiele für die Simulation
137
1
0.8
0.6
0.4
0.2
1
0.8
00
0.6
0.2
0.4
0.4
0.6
0.8
0.2
1 0
Abbildung 7.2 Lösung von Beispiel HEAT1 auf dem Einheitsquadrat
1
0.8
0.6
0.4
0.2
0
0
0
0.5
0.5
Abbildung 7.3 Lösung
von
HEAT2 auf dem Einheitsquadrat. Die Sprungstellen der
Beispiel
und führen zu einer stark adaptiven Verfeinerung des Gitters.
Lösung bei (
bei dem gleichzeitig auch die analytisch ermittelbare Lösung im Gebietsinneren
ist (Abb. 7.2).
Vor allem zum Test paralleler Berechnungen mit adaptiver Verfeinerung verwenden wir
weiterhin das Beispiel mit
(
(
falls falls (
und
oder
(
(HEAT2)
In den vorgegebenen Randwerten sind zwei Sprungstellen (Abb. 7.3), in deren Umgebung das Gitter für die numerische Berechnung besonders fein aufgelöst werden muss,
wenn man den Diskretisierungsfehler verkleinern will.
Ebene Elastizität Als Beispielanwendung aus der Strukturmechanik betrachten wir eine kurze Kragscheibe unter gleichm¨aßiger Belastung, also ein ebenes Elastizit¨atsproblem.
138
Beispielrechnungen und Leistungsmessungen
Die quadratische Scheibe wird auf ihrer Oberseite gleichm¨aßig belastet. Die linke Seite
der Scheibe ist fixiert.
Die physikalischen Eigenschaften des Materials sind durch den E-Modul
sowie die
Querdehnzahl gegeben. Die folgenden Differentialgleichungen beschreiben dann die
Reaktion des Objekts auf die ¨außere Last:
+
+
(PANEL)
/ / / /
/
/
(
+
/ / (
(
/
(/ /
/ +
4
wobei und die unbekannten Verschiebungen und
und
die externen Kr¨afte
in - bzw. -Richtung sind. Die Diskretisierung erfolgt hier mittels des Ritz-GalerkinVerfahrens für Finite Elemente und der Verschiebungsmethode [Bat90], bei der für die
gesuchten Verschiebungen und in jedem Element die Ansatzfunktionen
(
(
/ ( / ( (
gew¨ahlt werden. Mit den Verzerrungen die Matrix durch
(
(
(
, und bilden wir
. Wenn wir für unser Beiund damit die Verzerrungs-Verschiebungs-Matrix
spiel mit isotropem linear-elastischen Material in der Matrix
/
/
und
w¨ahlen, erhalten wir schließlich mit
für ein Blatt
des Substrukturierungsbaums mit Teilgebiet der Größe +
+ die Eintr¨age der Steifigkeitsmatrix als
! !(
%
7.2 Numerische Ergebnisse der rekursiven Substrukturierung
139
7.2 Numerische Ergebnisse der rekursiven Substrukturierung
Bevor wir Laufzeiten der parallelen Ausführung messen, vergewissern wir uns, dass die
entwickelten numerischen Verfahren effizient sind. Denn wir wollen kein Verfahren verwenden, das zwar gute Parallelisierungseigenschaften hat, aber praktisch durch zu langsame Konvergenz keinen Sinn machen würde.
Mit jeder Iteration eines Lösungsverfahrens erhalten wir eine verbesserte N¨aherung
an die tats¨achliche Lösung . Da — außer in speziellen F¨allen wie dem Beispiel
(HEAT1) — nicht bekannt ist, wird stattdessen das Residuum
/
/
berechnet. Als Maß für die Güte eines Iterationsverfahren wird die Konvergenzrate
mit der euklidischen Norm /
/
/
verwendet, für die wir die experimentelle N¨aherung bei Abbruch mit
angeben. Weiterhin bezeichnen in den Tabellen
M
FHG
die Tiefe des Substrukturierungsbaums (bei Bisektion),
die Anzahl der Freiheitsgrade,
die N¨aherung der Konvergenzrate (die wir bei CG-HB auslassen, weil
sie naturgem¨aß bei Konjugierten-Gradienten-Lösern st¨arkeren Schwankungen unterworfen ist),
die Anzahl der Iterationen (Baumtraversierungen) des Lösers bis zum
Erreichen der Genauigkeit ,
MFlop die Anzahl der benötigten Gleitpunkt-Operationen. Fehlen für diesen
Wert die Eintr¨age in der Tabelle, liegt das an der Berechnung mit dem
verteilten Programm, das dafür nicht instrumentiert war.
Alle Versuche wurden mit Transformation auf hierarchische Basis durchgeführt, so wie
sie bei den einzelnen Lösungsverfahren in Kapitel 6 beschrieben wurde. Als Abbruch schranke für die Norm
des Residuums des Gesamtsystems wurde durchwegs
verwendet, für die Anzahl der Gauß-Seidel-Iterationen bei den Verfahren GS
, und für das Vefahren J-HB der Relaxationsfaktor
HB und GS-HB-E
,
.
außer bei der Kragscheibe
/
140
Beispielrechnungen und Leistungsmessungen
GS-HB
GS-HB-E
1
2
3
1
2
3
0.11 0.15 0.15 0.11 0.18 0.18
0.08 0.15 0.09 0.09 0.15 0.12
Knoten
HEAT1/POISSON
PANEL
Tabelle 7.1 Füllungsgrad der Systemmatrizen beim Lösungsverfahren GS-HB-E
M
4
6
8
10
12
14
16
FHG
25
81
289
1089
4225
16641
66049
M
4
6
8
10
12
14
16
GS-HB
MFlop
11
0.024225
16
0.194424
19
1.154745
24
6.697520
31 37.641437
38 194.385290
45
0.333
0.515
0.598
0.689
0.760
0.820
0.867
FHG
25
81
289
1089
4225
16641
66049
0.139
0.182
0.553
0.578
0.672
0.748
0.816
J-HB
0.719 30
0.785 36
0.848 46
0.889 59
0.915 74
0.934 90
0.947 107
MFlop
0.009808
0.078580
0.505456
2.884306
15.195640
75.540768
11
14
20
25
31
43
51
GS-HB-E
MFlop
6
0.015610
8
0.117400
12
0.878348
18
6.079950
23 34.226693
29 182.819437
35
CG-HB
MFlop
0.009615
0.068940
0.474762
2.628993
13.774611
78.633231
Tabelle 7.2 Berechnungen des Beispiels POISSON
7.2.1 Gleichm¨
aßige Verfeinerung
Vor der Betrachtung der Konvergenzraten und Iterationszahlen wollen wir untersuchen,
ob die evtl. eingesparte Rechenzeit durch das neue Verfahren GS-HB-E gegenüber GSHB nicht etwa durch stark erhöhten Speicherbedarf erkauft wurde. Da die Systemmatrizen den Hauptteil des Speicherbedarfs bei beiden Verfahren ausmachen, betrachten wir
in Tabelle 7.1 ihren Füllgrad (= Anzahl Nicht-Null-Eintr¨age geteilt durch Matrixgröße)
für Tiefe
+ in den obersten Baumknoten (1=Wurzel, 2=linker Sohn der Wurzel,
3=rechter Sohn der Wurzel). Der durch die Elimination verursachte Fill-in ist also nur
geringfügig und hat praktisch keine Auswirkung.
Poisson-Gleichung:
(Beispiel POISSON, Tabelle 7.2)
In dieser Tabelle erkennen wir das logarithmische Ansteigen der Iterationszahlen, sowohl
beim bisherigen Verfahren GS-HB als auch bei GS-HB-E mit partieller Elimination star-
7.2 Numerische Ergebnisse der rekursiven Substrukturierung
M
4
6
8
10
12
14
FHG
25
81
289
1089
4225
16641
M
4
6
8
10
12
14
GS-HB
MFlop
8
0.018576
13
0.160959
16
0.983544
21
5.898215
28 34.121900
35 179.453777
0.333
0.522
0.512
0.575
0.657
0.732
FHG
25
81
289
1089
4225
16641
0.719
0.780
0.845
0.887
0.914
0.932
0.148
0.264
0.395
0.504
0.608
0.690
J-HB
MFlop
34 0.011032
41 0.088670
54 0.586624
71 3.429610
90 18.276376
111 92.202378
141
GS-HB-E
MFlop
6
0.015610
9
0.129499
13
0.943543
18
6.079950
23 34.226693
29 182.819437
12
15
24
34
50
70
CG-HB
MFlop
0.010458
0.073755
0.568374
3.565200
22.145004
127.256074
Tabelle 7.3 Berechnungen des Beispiels HEAT1
ker Kopplungen in der Systemmatrix. W¨ahrend die Anzahl der Gleitpunktoperationen
bei GS-HB-E nicht wesentlich geringer ist als bei GS-HB, ist die eingesparte Zahl von
Iterationen für die Praxis bedeutsam.
Bei den beiden speichersparenden Lösungsvarianten J-HB und CG-HB stellen wir für
CG-HB eine etwas höhere, bei J-HB eine bedeutend höhere Anzahl von Iterationen bis
zum Erreichen der gewünschten Genauigkeit fest. Allerdings ist bei beiden Verfahren
die Zahl der Gleitpunktoperationen deutlich niedriger als bei GS-HB und GS-HB-E. Bei
sequentieller Berechnung ist CG-HB dadurch effektiv schneller.
Stationäre Wärmeleitung:
(Beispiel HEAT1, Tabelle 7.3)
Mit den Verfahren GS-HB und GS-HB-E erfordert dieses Beispiel bei den meisten Baumtiefen etwas weniger Iterationen und Gleitpunktoperationen als im Beispiel POISSON.
Die Löser J-HB und CG-HB benötigen dagegen etwas mehr Iterationen als im vorigen
Beispiel. Dennoch w¨achst bei ihnen die Zahl der Iterationen auch hier logarithmisch mit
der Anzahl der Freiheitsgrade.
Wärmeleitung mit unstetigen Randwerten:
(Beispiel HEAT2, Tabelle 7.4)
Unstetige Randbedingungen haben kaum Einfluss auf die Konvergenzgeschwindigkeit
des Lösers, die theoretisch nur von der Systemmatrix abh¨angt. So l¨asst sich auch dieses
Beispiel problemlos mit allen Lösern durchrechnen.
142
Beispielrechnungen und Leistungsmessungen
M FHG
4
25
6
81
8 289
10 1089
12 4225
GS-HB
MFlop
11 0.024225
17 0.205579
21 1.268879
27 7.496825
34 41.160974
0.333
0.519
0.599
0.683
0.761
M FHG
4
25
6
81
8 289
10 1089
12 4225
0.136
0.223
0.583
0.602
0.673
J-HB
0.719
0.780
0.845
0.887
0.914
34
42
55
73
93
MFlop
0.011032
0.090688
0.596770
3.520494
18.854014
12
16
23
32
48
GS-HB-E
MFlop
7
0.017521
9
0.129499
13 0.943543
19 6.394105
25 37.045131
CG-HB
MFlop
0.010458
0.078570
0.544971
3.357154
21.263910
Tabelle 7.4 Berechnungen des Beispiels HEAT2
Die Unstetigkeitsstellen machen sich in einem starken Diskretisierungsfehler bemerkbar,
sodass in ihrer N¨ahe die numerische N¨aherungslösung große Abweichungen von der analytischen Lösung aufweist. Diese Ungenauigkeit kann nur mit einem feineren Gitter bzw.
größerer Baumtiefe reduziert werden. Deshalb greifen wir dieses Beispiel weiter unten
bei der adaptiven Verfeinerung nochmals auf.
Kragscheibe:
(Beispiel PANEL, Tabelle 7.5)
Für die Rechnung der Kragscheibe bringt die partielle Elimination (GS-HB-E) noch mehr
Gewinn als bei den vorigen Beispielen: Zwischen 25% und 30% der Iterationen werden
eingespart, um die gleiche Lösungsgenauigkeit zu erreichen.
Die anderen beiden Verfahren schneiden bei den Iterationszahlen dagegen wesentlich
schlechter ab. Die Konvergenzraten für das Verfahren J-HB scheinen schlecht zu sein.
Allerdings muss beachtet werden, dass die Verfahren GS-HB und GS-HB-E in jedem
Relaxationsschritte vornehmen, J-HB dagegen
Baumdurchlauf pro Freiheitsgrad
nur einen. Dadurch erkl¨art sich auch die Tatsache, dass die Anzahl der Gleitpunktoperationen von J-HB gegenüber GS-HB nicht bedeutend schlechter ist. Deshalb ist J-HB
durchaus eine Alternative zu GS-HB und sogar die bessere Wahl, wenn es auf maximal möglich Genauigkeit der diskreten Lösung bezüglich der physikalischen Realit¨at ankommt. Denn dann soll der Substrukturierungsbaum möglichst tief sein, und J-HB spart
wesentlich Speicherplatz, weil nur die Diagonale der Systemmatrix in den Nicht-BlattKnoten gehalten werden muss.
Das CG-HB-Verfahren scheidet hier als Lösungsmöglichkeit aus, denn die Systemmatri zen sind in diesem Beispiel nicht definit, und damit konvergiert
nicht mehr gegen die
Lösung . Hier müssten also Verfahren eingesetzt werden wie beispielsweise die Verfah-
7.2 Numerische Ergebnisse der rekursiven Substrukturierung
M
4
6
8
10
12
FHG
50
162
578
2168
8450
0.551
0.704
0.792
0.845
0.880
GS-HB
MFlop
21
0.567638
35
4.476298
48 26.881052
64 150.718268
81 784.879558
M FHG
4
50
6 162
8 578
10 2168
12 8450
0.213
0.536
0.686
0.775
0.831
J-HB
143
GS-HB-E
MFlop
9
0.277466
21
3.071590
32 20.743748
44 120.950068
59 670.448900
MFlop
0.980 596
1.362608
0.984 748
7.719184
0.987 901 39.396192
0.990 1107 199.113728
0.992 1356 989.090960
Tabelle 7.5 Berechnungen des Beispiels PANEL
ren BiCG und QMR [BBC 94, S.21ff], die nicht nur auf einer orthogonalen Sequenz von
Residuenvektoren aufbauen, sondern zwei wechselweise orthogonale Sequenzen von Residuenvektoren verwenden. Solche Verfahren w¨aren angesichts der Speicherersparnis in
den Nicht-Blatt-Knoten des Substrukturierungsbaums lohnend. Mit indefiniten Systemen
ist man h¨aufig bei numerischen Simulationen konfrontiert, z. B. mit gewissen Parametern
der Konvektions-Diffusions-Gleichung [Fun97] oder der Helmholtz-Gleichung [BSZ99].
7.2.2 Adaptive Verfeinerung
Das Beispiel (HEAT2) benötigt wegen der Unstetigkeit seiner Randbedingungen in der
Umgebung der Sprungstellen eine st¨arkere Verfeinerung des Gitters. Deshalb eignet es
sich, um Berechnungen der Löser auf adaptiven, unbalancierten Substrukturierungsb¨aumen zu testen.
Wir haben die vier Lösungsverfahren auf unbalancierte B¨aume der Tiefe 14, 20 und 26
(was bei uniform verfeinertem Gitter 16 641, 1 050 625 bzw. 67 125 249 Freiheitsgraden
entsprechen würde) ausprobiert (Tabelle 7.6). Dabei f¨allt bei allen vier Lösern auf, dass
die Konvergenzraten und die Anzahl der Iterationen mit zunehmender Tiefe kaum ansteigen. Dies kommt u. a. daher, dass h¨angende Punkte des Gitters bei Rechnung in hierarchischer Basis auf die Lösung keinen Einfluss haben, da sie den konstanten hierarchischen
Überschuss 0 beitragen.
Somit haben wir uns überzeugt, dass auch die neuen Lösungsvarianten praxistauglich sind
und es sich lohnt, sie für die verteilte Berechnung zu nutzen.
144
Beispielrechnungen und Leistungsmessungen
M
14
20
26
FHG
2191 0.631
4327 0.641
6259 0.644
M
14
20
26
GS-HB
GS-HB-E
MFlop
MFlop
24 34.787538 0.568 20 35.915592
25 86.402437 0.592 21 89.721049
26 145.528414 0.597 22 154.360172
FHG
2191 0.861
4327 0.857
6259 0.856
J-HB
60
60
60
MFlop
8.961530
21.659811
CG-HB
MFlop
35 11.199667
38 29.365765
39 47.662939
Tabelle 7.6 Adaptive Berechnungen des Beispiels HEAT2
7.3 Paralleler Ablauf mit FASAN
Jetzt wollen wir noch prüfen, wie effizient das Substrukturierungsverfahren mit der in
Abschnitt 6.5 eingeführten FASAN-Koordinationsschicht auf einem Rechnernetz arbeitet.
Wir haben oben erw¨ahnt, dass speziell die Poisson-Gleichung wegen der einfachen Differentialoperatoren wenig Rechenzeit in den Knoten des Substrukturierungsbaums erfordert. Wir beschr¨anken uns deshalb in den folgenden Abschnitten, wo wir die Effizienz der
FASAN-Koordinationsschicht testen wollen und nicht die Berechnungen in den Lösern,
auf das Beispielproblem POISSON bzw. für die adaptiven Berechnungen auf Beispiel
HEAT2.
Zur Zeit der Fertigstellung dieser Arbeit war das aktuelle JDK 1.2 (Java 2, [Sun98]), das
u. a. Just-in-Time-Übersetzung, Threads des Betriebssystems und etwas effizientere Serialisierungsmechanismen unterstützt, nur für Solaris- und Windows-Plattformen verfügbar
[Nef99]. Deshalb fiel die Wahl der Testplattform auf ein Netz von 16 Workstations vom
Typ Sun Ultra-60 mit 384 MB Hauptspeicher und je 2 Prozessoren.
Alle Messungen wurden mindestens dreimal durchgeführt und das Minimum der Laufzeiten verwendet. Dies rechtfertigt sich mit der schwankenden Last der Rechner und des Verbindungsnetzes durch andere Benutzer. Denn diese Last ist in den beschriebenen Lastverteilungsmechanismen noch nicht berücksichtigt und soll deshalb in die Messungen nicht
mitgerechnet werden. Außerdem wurde die Setup-Zeit zum Start und zur Vernetzung der
FASAN-Server nicht mit einbezogen, da diese erstens in der aktuellen Implementierung
großen Schwankungen unterworfen ist und zweitens bei mehreren Berechnungen nur einmal anf¨allt.
7.3 Paralleler Ablauf mit FASAN
145
Verfahren GS-HB, Beispiel POISSON, M=12, it=20
140
Thread-Programm auf 1 Prozessor
Sequentielles Programm
Thread-Programm auf 2 Prozessoren
120
Dauer [sec]
100
80
60
40
20
0
8 / 289
10 / 1089
12 / 4225
Baumtiefe / Anzahl Freiheitsgrade
14 / 16641
Abbildung 7.4 Laufzeiten auf einer 2-Prozessor-Maschine
7.3.1 Einsatz von Threads
Wir wollen zun¨achst beobachten, wieviel Overhead die Ausnutzung der vom JDK 1.2 unterstützten POSIX-Threads auf einer 2-Prozessor-Maschine erfordert, und inwieweit sich
die Nutzung beider Prozessoren lohnt. Hierzu übersetzen wir das FASAN-Programm in
ein rein sequentielles Programm, wie es in Abschnitt 5.3 beschrieben wurde. Wir vergleichen dasselbe FASAN-Programm, aber übersetzt mit der Instrumentierung für Threads
liefern, so(Abschnitt 5.4) und ¨andern die Lokationsfunktionen so ab, dass sie immer
dass nur ein Prozessor genutzt werden kann. Abb. 7.4 zeigt, dass der Overhead, den das
Thread-Programm durch Ausführung aller Selektor-Aufrufe in einem Monitor erzeugt,
etwa 20% mehr Laufzeit kostet. Die Entscheidung, jeden Knoten einer hierarchischen
Datenstruktur (der als Verpackungsobjekt übersetzt wird) einzeln mit seinem Monitor zu
synchronisieren, bis sein Inhalt vorhanden ist, führt also mit dem JDK 1.2 zu leichten
Effizienzeinbußen.
Der massive Einsatz von Monitoren zur Synchronisation ist aber vertretbar, weil stark
optimierte virtuelle Maschinen für Java wie z. B. HotSpot [Sun99] bedeutend effizienter bezüglich der Synchronisationsimplementierung sein werden. Außerdem l¨auft das
Programm auf zwei Prozessoren schneller: Die untere Kurve in Abb. 7.4 gibt die
Ausführungszeit an, die wir bei Nutzung beider Prozessoren und Erzeugung von Threads
für die Berechnung von Baumknoten bis in Tiefe 3 erhalten. Wir kommen auf einen
Speed-up von 1.625 gegenüber dem sequentiellen Programm, der zum einen durch den
sequentiellen Berechnungsteil in der Wurzel des Substrukturierungsbaums und zum anderen durch den Overhead, den die Synchronisationsmechanismen verursachen, begrenzt
ist.
146
Beispielrechnungen und Leistungsmessungen
Verfahren GS-HB-E, Beispiel POISSON
1800
1600
Dauer [sec]
1400
1200
1 Rechner
2 Rechner
4 Rechner
8 Rechner
16 Rechner
1000
800
600
400
200
10 / 1089 / 18
12 / 4225 / 23
14 / 16641 / 29
16 / 66049 / 35
Baumtiefe / Anzahl Freiheitsgrade / Anzahl Iterationen
Abbildung 7.5 Laufzeiten des verteilten FASAN-Programms mit Verfahren GS-HB-E
7.3.2 Verteilte Berechnung bei gleichm¨
aßiger Verfeinerung
Für die Laufzeitmessungen beschr¨anken wir uns auf die Lösungsvarianten GS-HB-E und
CG-HB. Das Verfahren GS-HB unterscheidet sich nur insofern von GS-HB-E, als es den
Schritt der partiellen Elimination nicht enth¨alt. Diese Elimination verursacht aber nur
Kosten in der Größenordnung eines Hierarchisierungsschritts, wie wir in Abschnitt 6.6.2
angegeben haben; bei gleicher Iterationszahl unterscheiden sich die Laufzeiten der beiden
Verfahren daher so gut wie nicht. Ähnlich sieht es mit den Verfahren J-HB und CGHB aus. Sie erfordern beide sehr wenig Rechenaufwand pro Iterationsschritt, und auch
die Speicheranforderungen sind sehr ¨ahnlich: W¨ahrend J-HB die Diagonalelemente der
Matrix in jedem Knoten halten muss, braucht CG-HB in jedem Knoten zus¨atzlich einen
Vektor für die Suchrichtung. Weiterhin können Dirichlet-Randwerte bei beiden Verfahren
bereits in den Bl¨attern eliminiert werden.
Betrachten wir die Laufzeiten der beiden Verfahren GS-HB-E (Abb. 7.5) und CG-HB
(Abb. 7.6), so f¨allt auf, dass CG-HB nicht schneller ist als GS-HB-E, obwohl es etwa nur
halb so viele Gleitpunkt-Operationen ausführen muss. Andererseits hat CG-HB mehr Iterationen auszuführen, und mit jeder Iteration den vollen Kommunikationsaufwand eines
Baumdurchlaufs.
Ein Vorteil von CG-HB gegenüber GS-HB-E liegt aber klar im Speicherbedarf: Mit der
verwendeten prototypischen Implementierung des Java-Teils für das Substrukturierungsprogramm konnte Tiefe 18 (263 169 Freiheitsgrade) bei uniformem Gitter nur mit dem
CG-HB-Löser auf 16 Rechnern erreicht werden.
7.3 Paralleler Ablauf mit FASAN
147
Verfahren CG_HB, Beispiel POISSON
3827
//////
1800
1600
Dauer [sec]
1400
1200
1 Rechner
2 Rechner
4 Rechner
8 Rechner
16 Rechner
1000
800
600
400
200
10 / 1089 / 25
12 / 4225 / 31
14 / 16641 / 43 16 / 66049 / 51 18 / 263169 / 60
Baumtiefe / Anzahl Freiheitsgrade / Anzahl Iterationen
Abbildung 7.6 Laufzeiten des verteilten FASAN-Programms mit Verfahren CG-HB
Erfreulich ist, dass gerade bei großen Berechnungen eine deutliche Beschleunigung eintritt. Dies haben wir unter anderem zur Ermittlung der Iterationszahlen und Konvergenzraten in Abschnitt 7.2 ausgenützt. Ein Vorteil der rekursiven Substrukturierung ist, dass
sie ganz automatisch eine Partitionierung der Freiheitsgrade, Separator für Separator, vornimmt, und dadurch nie wirklich lange Vektoren verschickt werden müssen, für die dann
die Serialisierung von RMI unertr¨aglich lang dauern würde (vgl. Abschnitt 5.2.1).
7.3.3 Verteilte Berechnung bei adaptiver Verfeinerung
Mit dem Lastverteilungsalgorithmus HF haben wir die Möglichkeit, auch adaptive Berechnungen mit besserer Effizienz verteilt durchzuführen. Wir betrachten das Beispielproblem HEAT2, bei dem der Substrukturierungsbaum wegen der Sprungstellen in den
Randbedingungen eine größere Tiefe aufweist und unbalanciert ist. Wir w¨ahlen das Verfahren CG-HB, weil es die speichersparendste unserer Lösungsvarianten ist und deshalb
für adaptive Rechnungen, wo man an besonders feiner Auflösung interessiert ist, sich am
besten eignet. Mit der Speicherersparnis geht auch die Reduzierung der Kommunikationskosten einher.
Für die Versuchsrechnungen haben wir die Baumtiefe auf maximal
+ begrenzt;
die Laufzeitergebnisse zeigt Abb. 7.7. Bei zwei Rechnern ist die Verteilung durch Algorithmus HF mit der statischen Verteilung noch identisch: Es wird nur der Wurzelknoten
geteilt. Ab drei Rechnern ergeben sich dann Unterschiede: Wie schon bei früheren Untersuchungen mit Algorithmus HF [BEE98b] auf Basis des GS-HB-Verfahrens festgestellt
148
Beispielrechnungen und Leistungsmessungen
Verfahren CG-HB, Beispiel HEAT2, M=20, it=10
350
statische Lastverteilung
dynamische Lastverteilung (HF)
300
Dauer [sec]
250
200
150
100
50
0
1
2
3
4
5
6
7
8
9 10
Anzahl Prozessoren
11
12
13
14
15
16
Abbildung 7.7 Laufzeiten mit CG-HB für das adaptive Beispiel HEAT2
wurde, erlaubt diese Art der Lastverteilung die bessere Ausnutzung der verfügbaren Rechner. W¨ahrend die Laufzeiten bei statischer Lastverteilung mit einem zus¨atzlichen Rechner
nicht oder nur geringfügig verbessert werden, wenn ein Knoten aufgeteilt wird, der nicht
die größte Rechenlast verursacht hat, bekommt man mit Algorithmus HF einen kontinuierlicheren Abfall der Rechenzeiten. Denn Algorithmus HF teilt ja gerade immer den
Knoten mit der größten Last auf, wenn ein zus¨atzlicher Rechner hinzugenommen wird.
Die Laufzeiten zeigen aber auch eine gewisse S¨attigung“, hier etwa bei acht Rechnern.
”
Dies ist auf den kritischen Pfad des Substrukturierungsbaums zurückzuführen, also desjenigen Pfades zwischen Wurzel und einem Blatt, der die größte Last verursacht und nicht
weiter parallelisiert werden kann. Dieser kritische Pfad w¨achst natürlich mit der Tiefe des
Baums und macht sich umso st¨arker bemerkbar, je unbalancierter der Baum ist, d. h. je
größer die Lastdifferenz je zweier Teilb¨aume einer Ebene ist.
Insgesamt können wir jedoch feststellen, dass sich der Einsatz dieser sehr einfachen Methode der dynamischen Lastverteilung lohnt: Bei einer Verteilung auf mindestens drei
Rechner sind die Laufzeiten immer etwas niedriger als bei statischer Verteilung; in unserem Beispiel bei 3 und 7 Rechnern sogar deutlich niedriger.
149
Kapitel 8
Schlussbetrachtungen und Ausblick
Ausgangspunkt für die Entwicklung der funktionalen Koordinationssprache FASAN war
die Erkenntnis, dass sich funktionale Programme zum Erstellen paralleler und verteilter
Anwendungen wegen des Fehlens von Seiteneffekten gut eignen, wie die Vielzahl von
Forschungsarbeiten auf diesem Gebiet dokumentiert.
Speziell im Bereich des technisch-wissenschaftlichen Rechnens ist man bei numerischen
Simulationen, die erhebliche Berechnungszeiten erfordern, sehr an deren Parallelisierung
interessiert. Zudem gewinnen hier rekursive, auf hierarchischen Datenstrukturen aufbauende Verfahren zunehmend an Bedeutung, weil sie den einfachen Übergang zu adaptiven
Berechnungen ermöglichen. Allerdings ergeben sich in diesem Anwendungsbereich wegen der Größe der Datenstrukturen und der verwendeten Algorithmen besondere Anforderungen: Es genügt nicht, nur mögliche parallel ausführbare Teilberechnungen aus einem
Algorithmus herauszufinden. Mindestens ebenso wichtig ist, dass die großen Datenmengen der Simulation so weit wie möglich lokal gehalten werden. Zus¨atzlich enthalten die
hier üblichen, an den Dimensionsrichtungen der Problemstellungen orientierten Algorithmen oft komplexe Datenabh¨angigkeiten, die für eine effiziente pipeline-artige parallele
Ausführung erst umorganisiert werden müssen.
Die Konzeption von FASAN greift genau diese Forderungen auf. Sie ermöglicht mit den
bekannten Techniken funktionaler Programmierung — Einfach-Zuweisungen, Rekursion, hierarchische Datenstrukturen und Funktionen höherer Ordnung — die Formulierung
der Koordinationsschicht für diese Algorithmen. Dabei wird dem Programmierer oder
auch einem Lastbalancierungssystem die Möglichkeit gelassen, das grobe parallele Ablaufverhalten zu bestimmen. Die Feinheiten der verteilten Ausführung — Datenlokalit¨at,
direkte Kommunikation und Abschw¨achung von Synchronisationspunkten —, die für die
Effizienz entscheidend sind, werden von FASAN automatisch organisiert.
Die beiden wesentlichen Beitr¨age dieser Arbeit zu dem schon l¨anger existierenden
FASAN-Konzept (z. B. [EP96, Pfa97]) sind die Entwicklung einer parallelen Semantik
und die neue programmierfreundliche Implementierung in Java.
Zur Angabe einer parallelen operationellen Semantik haben wir Datenflussgraphen, ei-
150
Schlussbetrachtungen und Ausblick
ne besondere Art von Prozessnetzen, verwendet. Mit ihrer Hilfe konnte das KabelbaumPrinzip der hierarchischen Datenstrukturen in FASAN anschaulich dargestellt werden.
Außerdem wurden auf recht einfache Weise wesentliche Eigenschaften der Programme
gezeigt: FASAN-Programme sind verklemmungsfrei und enthalten keine Laufbedingungen. Darüber hinaus wurden auch die geforderten Effizienz-Aspekte der hierarchischen
Datenstrukturen — Datenlokalit¨at, direkte Kommunikation, verringerte Synchronisation
und höherer Parallelit¨atsgrad — nachgewiesen. Diese Eigenschaften können durch explizite prozedurale parallele Programmierung oft nicht oder nur sehr mühsam erreicht
werden.
Für die Implementierung haben wir uns für die Sprache Java entschieden, weil sie einige
Konzepte vereint, die in dieser Zusammenstellung sonst nicht verfügbar sind. Außerdem
stellt Java einen guten Mittelweg zwischen effizienter Ausführung und Portabilit¨at dar.
Bei der Übersetzung eines FASAN-Programms sind wir stufenweise vorgegangen: Ausgehend von einem sequentiellen Grundprogramm haben wir Erweiterungen eingefügt,
mit deren Hilfe das Thread-System von Java genutzt werden kann. Die n¨achste Stufe
brachte dann die Erg¨anzung um Mechanismen für die verteilte Ausführung auf Basis von
Java-RMI sowie eine auf FASAN angepasste Implementierung globaler Referenzen und
verteilter Speicherverwaltung.
Den praktischen Nutzen von FASAN konnten wir mit einer größeren Anwendung aus dem
Bereich der numerischen Simulation unter Beweis stellen. Wir haben das Verfahren der
rekursiven Substrukturierung gew¨ahlt, weil es ein wichtiges und universelles Verfahren
zur numerischen Lösung großer linearer Gleichungssysteme ist, wie sie beispielsweise
bei der Modellierung technischer und physikalischer Probleme mit der Finite-ElementMethode entstehen [Sch95, Hüt96]. Für die Lösung der Gleichungssysteme haben wir
in dieser Arbeit drei neue Varianten vorgeschlagen und praktisch getestet. Hauptvorteil
dieser Löser ist der reduzierte Speicherbedarf, wodurch genauere Simulationen auf feineren Gittern möglich sind. Außerdem wird das Datenvolumen für die Kommunikation
bei verteilter Ausführung verringert. Die neuen Verfahren konnten ohne große Schwierigkeiten sofort verteilt getestet werden, weil ein FASAN-Programm als Rahmen diente
und kein zus¨atzlicher Programmieraufwand für die Parallelisierung anfiel. Mit Hilfe des
Lokationskonzepts von FASAN konnten wir auch rasch einen ersten Ansatz eines dynamischen Lastverteilungsverfahren für adaptive Berechnungen integrieren. Schließlich
zeigten Beispielrechnungen die Leistungsf¨ahigkeit der von FASAN erzeugten parallelen
Programme.
Für aufbauende Arbeiten besteht vor allem im Bereich der Lastverteilung noch Forschungsbedarf. Sehr nützlich w¨are eine Bibliothek von Parallelisierungsschemata, die entweder direkt als Lokationsfunktionen oder gekapselt in Funktionen höherer Ordnung (als
Skeletons) mit FASAN programmiert werden könnten. Auch müsste die Kombination mit
automatischen Lastverteilungssystemen untersucht werden. Die Java-Implementierung
könnte verbessert werden, indem schnellere Verfahren zum verteilten Methodenaufruf
und zur Serialisierung in die übersetzten FASAN-Programme integriert werden [Pan98].
Ohne besonders großen Aufwand könnten die übersetzten FASAN-Programme auch tole-
151
rant gegenüber dem Ausfall oder größerer Verzögerung einzelner Rechner gemacht werden. Dafür müsste ein verteilter Funktionsaufruf, der eine Fehlermeldung zurückliefert
oder zu lange nicht zurückkehrt, erneut auf einem anderen Rechner erfolgen.
Auf der Seite der Anwendung können weitere Lösungsmethoden für die rekursive Substrukturierung entwickelt, implementiert und mit Hilfe des FASAN-Rahmenprogramms
auf einem Rechnernetz getestet werden. Dass dies in kurzer Zeit realisierbar ist, haben die
drei neuen Lösungsvarianten aus dieser Arbeit gezeigt. Speziell w¨are der Einbau eines
Lösungsverfahrens interessant, das so effizient und speichersparend wie der CG-Löser
arbeitet, aber keine definiten Systemmatrizen voraussetzt und deshalb für allgemeinere
Problemstellung einsetzbar ist. Hier k¨amen Methoden wie Bi-CG oder QMR (Quasi Minimal Residual, [BBC 94]) in Frage.
152
Schlussbetrachtungen und Ausblick
Literaturverzeichnis
153
Literaturverzeichnis
[AG94]
G. S. A LMASI und A. G OTTLIEB: Highly Parallel Computing. Benjamin/Cummings Publishing Company, Redwood City (CA) et al., 1994.
[AG98]
K. A RNOLD und J. G OSLING: The Java programming language. AddisonWesley Publishing Company, Reading (MA) et. al., 2 Auflage, 1998.
[AMR92]
S. E. A BDULLAHI, E. E. M IRANDA und G. A. R INGWOOD: Distributed
Garbage Collection. In: International Workshop on Memory Management
(IWMM92), St. Malo, France., Nummer 637 in LNCS, Seiten 43–81, Berlin,
September 1992. Springer Verlag.
[ANP89]
A RVIND, R. S. N IKHIL und K. K. P INGALI: I-Structures: Data Structures
For Parallel Computing. TOPLAS, 4(11):598–632, 1989.
[Bac78]
J. BACKUS: Can Programming Be Liberated from the von Neumann Style?
A Functional Style and Its Algebra of Programs. Communications of the
ACM, 21(8):613–640, 1978.
[Bac98]
M. BACKSCHAT : A Novel Economic-Based Approach to Dynamic
Load Distribution on Large Heterogeneous Computer Networks. In:
E. D’H OLLANDER, G. J OUBERT, F. P ETERS und U. T ROTTENBERG
(Herausgeber): Parallel Computing: Fundamentals, Applications and New
Directions, Band 12 der Reihe Advances in Parallel Computing, Seiten
739–742, Amsterdam, 1998. Elsevier Science Publishers.
[Bar84]
H. P. BARENDREGT: The Lambda Calculus. Elsevier Science Publishers,
Amsterdam, 1984.
[Bat90]
K. BATHE: Finite-Elemente-Methoden. Springer Verlag, Berlin, 1990.
[Bat96]
K. BATHE: Finite element procedures. Prentice-Hall Int., Eaglewood Cliffs
(NJ), 1996.
[BBC 94]
R. BARRETT, M. B ERRY, T. F. C HAN, J. D EMMEL, J. D ONATO, J. D ON GARRA, V. E IJKHOUT, R. P OZO, C. ROMINE und H. VAN DER VORST:
Templates for the Solution of Linear Systems: Building Blocks for Iterative
Methods, 2nd Edition. SIAM, Philadelphia, 1994.
154
Literaturverzeichnis
[BCH 94]
G. E. B LELLOCH, S. C HATTERJEE, J. C. H ARDWICK, J. S IPELSTEIN und
M. Z AGHA: Implementation of a Portable Nested Data-Parallel Language.
Journal of Parallel and Distributed Computing, 21(1):4–14, April 1994.
[BDS92]
M. B ROY, C. D ENDORFER und K. S TOLEN: HOPSA – a High-level Programming Language for Parallel Computations. interner Bericht, Technische Universit¨at München, 1992.
[BEE98a]
S. B ISCHOF, R. E BNER und T. E RLEBACH: Load Balancing for Problems
with Good Bisectors, and Applications in Finite Element Simulations. In:
Proceedings of the 4th International Euro-Par Conference on Parallel Processing, Euro-Par’98, Nummer 1470 in LNCS, Seiten 383–389, Berlin,
1998. Springer Verlag.
[BEE98b]
S. B ISCHOF, R. E BNER und T. E RLEBACH: Load Balancing for Problems with Good Bisectors, and Applications in Finite Element Simulations: Worst-case Analysis and Practical Results. SFB-Bericht 342/05/98,
Technische Universit¨at München, 1998.
[Bes95]
E. B EST: Semantik: Theorie sequentieller und paralleler Programmierung.
Vieweg Verlag, 1995.
[BES97]
H.-J. B UNGARTZ, R. E BNER und S. S CHULTE: Hierarchische Basen
zur effizienten Kopplung substrukturierter Probleme der Strukturmechanik.
SFB-Bericht 324/05/97 A, Technische Universit¨at München, 1997.
[BG96]
G. E. B LELLOCH und J. G REINER: A Provable Time and Space Efficient
Implementation of NESL. In: ACM SIGPLAN International Conference
on Functional Programming, Seiten 213–225, New York, Mai 1996. ACM
Press.
[BGG 96]
M. B ROY, E. G EISBERGER , R. G ROSU, F. H UBER, B. RUMPE,
B. S CH ÄTZ, A. S CHMIDT, O. S LOTOSCH und K. S PIES: Das AutoFocusBilderbuch — eine anwendungsorientierte Einführung. Technischer Bericht
Technische Universit¨at München, 1996.
[Bir88]
B. S. B IRD: Lectures on constructive functional programming. In:
M. B ROY (Herausgeber): Constructive Mehtods in Computing Science,
Band 55 der Reihe NATO ASO Series F: Computer and Systems Sciences,
Seiten 151–216, Berlin, 1988. Springer Verlag.
[Bis98]
J. B ISHOP: Java Gently. Addison-Wesley Publishing Company, Reading
(MA) et. al., 2 Auflage, 1998.
[BJK 96]
R. B LUMOFE, C. J OERG, B. K USZMAUL, C. L EISERSON, K. R ANDALL
und Y. Z HOU: Cilk: An Efficient Multithread Runtime System. The Journal
of Parallel and Distributed Computing, 37(1):55–69, 1996.
Literaturverzeichnis
[Ble96]
155
G. E. B LELLOCH: Pragramming Parallel Algorithms. Comm. of the ACM,
3(39):85–97, 1996.
[BLOMP98] S. B REITINGER, R. L OOGEN, Y. O RGA -M ALL ÉN und R. P E ÑA: Eden,
Language Definition and Operational Semantics. Technischer Bericht
10(1996), Phillips-Universit¨at Marburg, 1998.
[Bon95]
T. B ONK: Ein rekursiver Algorithmus zur adaptiven numerischen Quadratur mehrdimensionaler Funktionen. Dissertation, Technische Universit¨at
München, 1995.
[BOSW98]
G. B RACHA, M. O DERSKY, D. S TOUTAMIRE und P. WADLER: Making
the future safe for the past: Adding Genericity to the Java Programming
Language. In: The 13th annual ACM SIGPLAN Conferences on ObjectOriented Programming Systems, Languages, and Applications OOPSLA
98, Vancouver, New York, 1998. ACM Press.
[BPZ96]
M. BACKSCHAT, A. P FAFFINGER und C. Z ENGER: Economic Based Dynamic Load Distribution In Large Workstation Networks. In: P. E . A .
F RAIGNIAUD (Herausgeber): Euro-Par’96 Parallel Processing: Proceedings of the 2nd international Euro-Par conference, Lyon, France, August
26-29, 1996, Vol. 2, Nummer 1124 in Lecture Notes in Computer Science,
Seiten 631–634, Berlin, 1996. Springer Verlag.
[Bra97]
D. B RAESS: Finite Elemente: Theorie, schnelle Löser und Anwendungen
in der Elastizitätstheorie. Springer Verlag, Berlin, 1997. 2. überarbeitete
Auflage.
[Bro92]
M. B ROY: Informatik, eine grundlegende Einführung, Teil 1. Springer Verlag, Berlin, 1992.
[BSZ99]
M. BADER, M. S CHIMPER und C. Z ENGER: Hierarchical Bases for the Indefinite Helmholtz Equation. Technischer BerichtSFB 342/03/99 A, Technische Universit¨at München, 1999.
[Bun92]
H.-J. B UNGARTZ: Dünne Gitter und deren Anwendung bei der adaptiven
Lösung der drei-dimensionalen Poisson-Gleichung. Dissertation, Technische Universit¨at München, 1992.
[Bur91]
D. S. B URNETT: Finite Element Analysis. Addison-Wesley Publishing
Company, Reading (MA) et. al., 1991.
[CFBO92]
D. C ANN, J. F EO, A. B OHOEM und R. O LDEHOEFT: SISAL Reference
Manual: language version 2.0. Technischer Bericht Lawrence Livermore
National Laboratory, Livermore, California, 1992.
[Col98]
M. I. C OLE: Algorithmic Skeletons. Pitman/MIT-Press, 1998.
[DGTY95]
J. DARLINGTON, Y.- K . G UO, H. W. TO und J. YANG: Functional Skeletons for Parallel Coordination. In: P. F RAIGNIAUD ET AL . (Herausge-
Literaturverzeichnis
156
ber): Euro-Par’95 Parallel Processing: Proceedings of the first international Euro-Par conference, Stockholm, Sweden, August 29–31, 1995, Lecture
Notes in Computer Science, 966, Berlin, 1995. Springer Verlag.
[Ebn94]
R. E BNER: Neuimplementierung einer funktionalen Sprache zur Parallelisierung numerischer Algorithmen. Diplomarbeit, Technische Universit¨at
München, 1994.
[EP96]
R. E BNER und A. P FAFFINGER : Transformation of Functional Programs
into Data Flow Graphs Implemented with PVM. In: A. B ODE ET. AL .
(Herausgeber): Proceedings of the Third Euro PVM Users’ Group Meeting,
7–9 October 1996, Munich, Berlin, 1996. Springer Verlag.
[EP98]
R. E BNER und A. P FAFFINGER : Higher Level Programming and Efficient
Automatic Parallelization: A Functional Data Flow Approach with FASAN.
In: E. D’H OLLANDER , G. J OUBERT, F. P ETERS und U. T ROTTENBERG
(Herausgeber): Parallel Computing: Fundamentals, Applications and New
Directions, Band 12 der Reihe Advances in Parallel Computing, Seiten
377–384, Amsterdam, 1998. Elsevier Science Publishers.
[EPZ96]
R. E BNER, A. P FAFFINGER und C. Z ENGER: FASAN — eine funktionale Agenten-Sprache zur Parallelisierung von Algorithmen in der Numerik.
In: W. M ACKENS (Herausgeber): Software Engineering im Scientific Computing, Tagungsband zur SESC 1995, Kiel, Braunschweig, 1996. Vieweg
Verlag.
[Far98]
J. FARLEY: Java Distributed Computing.
1998.
[FMS 95]
J. F EO, P. M ILLER, S. S KEDZIELEWSKI, S. D ENTON und C. S OLOMON:
Sisal 90. In: A. P. W. B OHM und J. T. F EO (Herausgeber): High Performance Functional Computing, Seiten 35–47, Livermore, California, April
1995.
[FOT92]
I. F OSTER, R. O LSON und S. T UECKE: Productive Parallel Programming:
The PCN Approach. Scientific Programming, 1(1):51–66, 1992.
[FT90]
I. F OSTER und S. TAYLOR: Strand, New Concepts in Parallel Programming. Prentice-Hall Int., Eaglewood Cliffs (NJ), 1990.
[FT93]
I. F OSTER und S. T UECKE: Parallel Programming with PCN. Technischer
Bericht Argonne National Laboritory, 1993.
[FT94]
I. F OSTER und S. TAYLOR: A Compiler Approach to Scalable ConcurrentProgram Design. ACM TOPLAS, 3(16):577–604, 1994.
[Fun97]
K. F UNK: Anwendung der algebraischen Mehrgittermethode auf konvektionsdominierte Strömungen. Diplomarbeit, Technische Universit¨at
München, 1997.
O’Reilly, Cambridge (MA),
Literaturverzeichnis
157
[GC92]
D. G ELERNTER und N. C ARRIERO: Coordination languages and their significance. Comm. of the ACM, 2(35):97–107, 1992.
[Geo73]
A. G EORGE: Nested Dissection of a Regular Finite Element Mesh. SIAM
J. Numer. Anal., 10(2), 1973.
[GG98]
R. G OLDMAN und R. P. G ABRIEL: Qlisp: Experience and New Directions. In: Proceedings of the ACM/SIGPLAN PPEALS 1988, New Haven,
CT, Seiten 111–123, 1998.
[Gil96]
A. G ILL: Cheap Deforestation for Non-strict Functional Languages. Dissertation, Department of Computing Science, Glasgow University, Januar
1996.
[GL78]
A. G EORGE und J. L IU: An Automatic Nested Dissection Algorithm for
Irregular Finite Element Problems. SIAM J. Numer. Anal., 15(5), 1978.
[GL95]
S. G ORLATCH und C. L ENGAUER: Parallelization of Divide-and-Conquer
in the Bird-Meertens Formalism. Formal Aspects of Computing, 7(6):663–
682, 1995.
[GLP93]
A. J. G ILL, J. L AUNCHBURY und S. L. P EYTON J ONES: A Short Cut
To Deforestation. In: A RVIND (Herausgeber): Functional Programming
and Computer Architecture, Seiten 223–232, New York, Juni 1993. ACM
SIGPLAN/SIGARCH, ACM Press.
[Gri87]
M. G RIEBEL: On the combination of the ideas of multilevel solvers using
hierarchical bases and the substructuring technique for the finite element
method. Technischer BerichtI1987, Technische Universit¨at München, 1987.
[Gri94]
M. G RIEBEL: Multilevelmethoden als Iterationsverfahren über Erzeugendensystemen. Teubner Verlag, Stuttgart, 1994.
[Hac93]
W. H ACKBUSCH: Iterative Lösung großer schwachbesetzter Gleichungssysteme. Teubner Verlag, Stuttgart, 1993.
[Hal85]
R. H. H ALSTEAD: MultiLisp: A Language for Concurrent Symbolic Computation. ACM TOPLAS, 4(7):501–538, 1985.
[Hal90]
R. H. H ALSTEAD: New Ideas in Parallel Lisp: Language Design, Implementation, and Programming Tools. In: G. G OOS und J. H ARTMA NIS (Herausgeber): Parallel Lisp: Languages and Systems, Proceedings of
the U.S./Japan Workshop on Parallel Lisp, Band 441, Seiten 2–57, Berlin,
1990. Springer Verlag.
[Ham94]
K. H AMMOND: Parallel Functional Programming: An Introduction. Technischer Bericht University of Glasgow, 1994.
[HF93]
P. H UDAK und J. H. FASEL: A Gentle Introduction to Haskell. Technischer
Bericht Yale University and Los Alamos National Laboritories, 1993.
158
Literaturverzeichnis
[HG89]
R. H ERRTWICH und H. G.: Kooperation und Konkurrenz: nebenl äufige,
verteilte und echtzeitabhängige Programmsysteme. Springer Verlag, Berlin,
1989.
[Hil93]
J. M. D. H ILL: Data Parallel Haskell: Mixing old and new glue. Technischer Bericht University of London, 1993.
[HL92]
P. H AMMERLUND und B. L ISPER: On the Relation between Functional
and Data Parallel Programming Languages. Technischer Bericht Royal
Institute of Technology, Stockholm, 1992.
[HLPT98]
K. H AMMOND, H.-W. L OIDL, S. P EYTON J ONES und P. W. T RINDER:
Algorithm + Strategy = Parallelism. Journal of Functional Programming
JFP, 8(1), 1998.
[HS94]
R. H ÜTTL und M. S CHNEIDER : Parallel Adaptive Numerical Simulation.
SFB-Bericht 342/01/94 A, Technische Universit¨at München, 1994.
[HS97a]
U. H INKEL und K. S PIES: Spezifikationsmethodik für mobile, dynamische FOCUS-Netze. In: A. W OLISZ, I. S CHIEFERDECKER und A. R EN NOCH (Herausgeber): Formale Beschreibungstechniken f ür verteilte Systeme, GI/ITG-Fachgespräch 1997, St. Augustin, 1997. GMD Verlag.
[HS97b]
F. H UBER und B. S CH ÄTZ: Rapid Prototyping with AutoFocus. In: A. W O LISZ, I. S CHIEFERDECKER und A. R ENNOCH (Herausgeber): Formale
Beschreibungstechniken für verteilte Systeme, GI/ITG-Fachgespräch 1997,
Seiten 343–352, St. Augustin, 1997. GMD Verlag.
[HTI99]
Z. H U, M. T KEICHI und H. I WASAKI: Diffusion: Calculating Efficient Parallel Programs. In: Proceedings of the ACM SIGPLAN Workshop on Partial Evaluation and Semantics-Based Program Manipulation PEPM’99, San
Antonio, Texas, January 22–23, 1999, New York, 1999. ACM Press.
[Hud88]
P. H UDAK: Exploring Para-Functional Programming: Separating the what
from the how. IEEE Software, 1(5):54–61, 1988.
[Hud91]
P. H UDAK: Para-Functional Programming in Haskell, Seiten 159–196.
Addison-Wesley Publishing Company, Reading (MA) et. al., 1991.
[Hüt96]
R. HÜTTL: Ein iteratives Lösungsverfahren bei der Finite-ElementMethode unter Verwendung von rekursiver Substrukturierung und hierarchischen Basen. Dissertation, Technische Universit¨at München, 1996.
[Jen97]
K. J ENSEN: Coloured petri nets: basic concepts, analysis methods and results, Band 1–3. Springer Verlag, Berlin, 1997.
[JH93]
M. J ONES und P. H UDAK: Implicit and Explicit Parallel Programming in
Haskell. Technischer Bericht YALEU/DCS/RR-982, Department of Computer Science, Yale University, August 1993.
Literaturverzeichnis
159
[JL91]
M. J UNG und U. L ANGER: Applications of Multilevel Methods to Practical
Problems. Surv. Math. Ind., 1:217–257, 1991.
[Joe96]
C. F. J OERG: The Cilk System for Parallel Multithreaded Computing. Dissertation, Department of Electrical Engineering and Computer Science,
Massachusetts Institute of Technology, 1996.
[KCR98]
R. K ELSEY, W. C LINGER und J. R EES: Revised Report on the Algorithmic Language Scheme. Technischer Bericht Computer Science Department,
Cornell University, 1998.
[Kel89]
P. H. J. K ELLY: Functional Programming for Loosely-Coupled Multiprocessors. Pitman/MIT Press, 1989.
[KPS94]
H. K UCHEN, R. P LASMEIJER und H. S TOLTZE: Distributed Implementation of a Data Parallel Functional Language. In: Parallel Architectures &
Languages Europe, Seiten 464–477. LNCS 817, Springer Verlag, 1994.
[Kru93]
C. D. K RUMVIEDA: Distributed ML: Abstractions for Efficient and FaultTolerant Programming. Technical report TR93-1376, Cornell University,
Computer Science Department, August 1993.
[Leb98]
M. L EBERECHT : An Efficient Runtime System Combining Dataflow, Multithreading, and Distributed Shared Memory. Dissertation, Technische Universit¨at München, 1998.
[LT97]
H.-W. L OIDL und P. W. T RINDER: Engineering Large Parallel Functional Programs. In: Implementation of Functional Languages, St. Andrews,
Scotland, September 1997, LNCS, Berlin, 1997. Springer Verlag.
[Mae96]
J. W. M AESSEN: pH – a parallel Haskell. Technischer Bericht MIT, 1996.
[MH91]
E. M OHR und R. H. H ALSTEAD: Lazy Task Creation – a Technique for
Increasing the Granularity of Parallel Programs. IEEE Transactions on
Parallel and Distributed Systems, 2(3):264–280, Juli 1991.
[MS96]
M. M ÜLLER und G. S MOLKA: Oz: Nebenläufige Programmierung mit
Constraints. KI-Künstliche Intelligenz, Themenheft Logische Programmierung, Seiten 55–61, 1996.
[Nef99]
J. N EFFINGER: The Volano Report: Which Java platform is fastest, most
scalable? JavaWorld, M¨arz 1999.
[Nik90]
R. S. N IKHIL: ID Version 90.0 Reference Manual. Technischer Bericht
Computation Structures Group Memo 284-1, Laboratory for Computer
Science, Massachusetts Institute of Technology, Juli 1990.
[NSvP93]
E. N ÖCKER, S. S METSERS, M. VAN E EKELEN und R. P LASMEIJER : Concurrent Clean. Technischer Bericht University of Nijmegen, 1993.
[OW97a]
S. OAKS und H. W ONG: Java Threads. O’Reilly , Cambridge, 1997.
160
Literaturverzeichnis
[OW97b]
M. O DERSKY und P. WADLER: Pizza into Java: translating theory into
practice. In: POPL ’97, proceedings of the 24th ACM SIGPLAN-SIGACT
symposium on Principles of programming languages, Seiten 146–159, New
York, 1997. ACM Press.
[Pan98]
J. G. F. PANEL: Java Grande Forum Report: Making Java Work for HighEnd Computing. Technischer Bericht SC 98, Orlando, Florida, 1998.
[Pau91]
L. C. PAULSON: ML for the Working Programmer. Cambridge University
Press, Cambridge (MA), 1991.
[Pey89]
S. L. P EYTON J ONES: Parallel Implementations of Functional Programming Languages. The Computer Journal, 32(2):175–186, 1989.
[Pfa97]
A. P FAFFINGER: Funktionale Beschreibung numerischer Algorithmen auf
dünnen Gittern. Dissertation, Technische Universit¨at München, 1997.
[PH97]
J. P ETERSON und K. H AMMOND: Report on the programming language
Haskell: a non-strict, purely functional language, version 1.4. Technischer
Bericht Yale University, 1997.
[PT94]
G. M. PAPADOPOULOS und K. R. T RAUB: Multithreading: A Revisionist
View of Dataflow Architectures. CSG Memo 330, Laboratory for Computer
Science, Computation Structures Group, MIT, 1994.
[Qui94]
D. Q UINN: Parallel Computing — Theory and Practice. McGraw Hill,
Hamburg, 1994.
[Rau92]
H. R AUSCHMAYER : Werkzeuge zur Bearbeitung funktionaler Programme auf Parallelrechnern. Diplomarbeit, Technische Universit¨at München,
1992.
[Rei84]
J. R EID: TREESOLVE, a Fortran Package for Solving Large Sets of Linear
Finite-Element Equations. In: B. E NGQUIST und T. S MEDSASS (Herausgeber): Proceedings of the IFIP TC 2 Working Conference on PDE Software: Modules, Interfaces and Systems, Söderköping, Sweden, 22–26 August,
1983, Amsterdam, 1984. North Holland.
[Rei85]
W. R EISIG: Systementwurf mit Netzen. Springer Compass. Springer Verlag,
Berlin, 1985.
[Rei98]
W. R EISIG: Elements of distributed algorithms: modeling and analysis with
Petri Nets. Springer Verlag, Berlin, 1998.
[Rep91]
J. H. R EPPY: CML: a Higher-Order Concurrent Language. In: Proc.
PLDI’91, Toronto, Canada, June 26–28, 1991, Seiten 293–305, 1991.
[Rep98]
J. H. R EPPY: Concurrent Programming in ML. Cambrige University Press,
1998.
Literaturverzeichnis
161
[Rou97]
M. ROULO: Java’s three types of portability. Technischer Bericht Web
Publishing Inc., 1997.
[RW91]
B. ROSENSTENGEL und U. W INAND: Petri-Netze: eine anwendungsorientierte Einführung. Vieweg Verlag, Braunschweig, 1991.
[Saa96]
Y. S AAD: Iterative methods for sparse linear systems. PWS Publishing
Company, Boston et. al., 1996.
[SBG96]
B. E. S MITH, P. E. B JØRSTAD und W. D. G ROPP: Domain Decomposition: Parallel Multilevel Methods for Elliptic Partial Differential Equations.
Cambridge University Press, Cambridge UK., 1996.
[Sch95]
M. S CHNEIDER : Verteilte adaptive numerische Simulation auf der Basis der Finite-Elemente-Methode. Dissertation, Technische Universit¨at
München, 1995.
[Sch98]
B. S CH ÄTZ: Ein methodischer Übergang von asynchron zu synchron kommunizierenden Systemen. Dissertation, Technische Universit¨at München,
1998.
[SG86]
H. S TOYAN und G. G ÖRZ: LISP, eine Einführung in die Programmierung.
Springer Verlag, Berlin, 1986.
[SHW93]
G. S MOLKA, M. H ENZ und J. W ÜRTZ: Object-Oriented Concurrent Constraint Programming in Oz. Technischer BerichtRR-93-16, DFKI Saarbrücken, 1993.
[Ski95]
D. B. S KILLIKORN: Parallel Implementation of Tree Skeletons. Technischer Bericht Queen’s University, Kingston, Ontario, 1995.
[SKL90]
V. A. S ARASWAT, K . K AHN und J. L EVY: Janus: A step towards distributed constraint programming. In: Proc. of the North American Conference
on Logic Programming, Austin, Texas, 1990.
[SRP90]
V. A. S ARASWAT, M. R INARD und P. PANANGADEN: Semantic foundations of concurrent constraint programming. Technischer Bericht ACM
Press, New York, 1990.
[SS95]
B. S CH ÄTZ und K. S PIES: Formale Syntax der logischen Kernsprache der
FOCUS-Entwicklungsmethodik. Technischer BerichtTUM-I6529, Technische Universit¨at München, 1995.
[Stö89]
J. S TÖR: Numerische Mathematik. Springer Verlag, Berlin, 1989.
[Sun98]
S UN
MICROSYSTEMS I NC .:
Java 2 Platform API Specification, 1998.
[Sun99]
S UN
MICROSYSTEMS I NC .:
Java HotSpot Performance Engine, 1999.
[Sup98]
S UPERCOMPUTING T ECHNOLOGY G ROUP, MIT: Cilk 5.2 Reference Manual. Technischer Bericht Massachusetts Institute of Technology, 1998.
162
Literaturverzeichnis
[Thi94]
P. T HIEMANN: Grundlagen der funktionalen Programmierung. Teubner
Verlag, Stuttgart, 1994.
[THM 96]
P. W. T RINDER, K. H AMMOND, J. S. M ATTSON J R ., A. S. PARTRIDGE
und S. L. P EYTON J ONES: GUM: a portable implementation of Haskell.
In: Proceedings of Programming Language Design and Implementation,
Philadephia, USA, Mai 1996.
[Tho96]
S. T HOMPSON: Haskell: The Craft of Functional Programming. AddisonWesley Publishing Company, Reading (MA) et. al., 1996.
[Tur79]
D. A. T URNER: A New Implementation Technique for Applicative Languages. Software–Practice and Experience, 9:31–49, 1979.
[Vei90]
J. V EIT: Entwurf und Implementierung eines Sprachkonzepts zur parallelen
Konfiguration numerischer Aufgabenstellungen. Diplomarbeit, Technische
Universit¨at München, 1990.
[Vin96]
S. V INOSKI: CORBA: Integrating Diverse Applications Within Distributed Heterogeneous Environments. Technischer Bericht IONA Technologies,
Inc., Cambridge, MA, 1996.
[VvM 98]
R. V ELDEMA, R. VAN N IEUWPOORT, J. M AASSEN, H. E. BAAL und
A. P LAAT: Efficient Remote Method Invocation. Technischer Bericht Dept.
of Computer Science, Vrjie Universiteit Amsterdam, 1998.
[Wad90]
P. WADLER: Deforestation: transforming programs to eliminate trees. In:
Special issue of selected papers from 2nd European Symposium on Programming, Band 73 der Reihe Theoretical Computer Science, Seiten 231–
248, 1990.
[Wes92]
M. D. W ESTHEAD: Linda and the paradigms of parallelisation. DAI research paper 744, University of Edinburgh, 1992.
[Yse86]
H. Y SERENTANT: On the multi-level splitting of finite element spaces. Numerische Mathematik, 49(3):379–412, 1986.
[Zen90]
C. Z ENGER: Sparse Grids. SFB-Bericht 342/18/90, Technische Universit¨at
München, 1990.
[Zen92]
C. Z ENGER: Funktionale Programmierung paralleler Algorithmen f ür numerische und technisch-wissenschaftliche Anwendungen. In: R. R. G. BA DER , G. W ITTUM (Herausgeber): GAMM-Seminar über numerische Algorithmen auf Transputer-Systemen, Heidelberg, 31.5.–1.6. 1991, TeubnerSkripten zur Numerik, Stuttgart, 1992. Teubner Verlag.
Index
163
Index
" (Folgemarkierungen) . . . . . . . . . . . . 23
(Sequenzen-Konkatenation) . . . . . . . 19
(Konkatenation) . . . . . . . . . . . . . . . . . 40
(leere Sequenz) . . . . . . . . . . . . . . . . . 19
[] (optionaler Teil) . . . . . . . . . . . . . . . . 28
[] (ein- oder mehrmalige Wiederholung) . . . . . . . . . . . . . . . . . . . . . 28
[] (null- oder mehrmalige Wiederholung) . . . . . . . . . . . . . . . . . . . . . 28
!"
. . . . . . . . . siehe Assemblierungsmatrix
. . . . . . . . . . . . . . . . . siehe Tr¨agermenge
(Interpretation)
. . . . . . . . . . . . . . . . . . 20
. . . . . . . . . . . . . siehe -Algebra
(transponierte Matrix) . . . . . . . . . 106
(Sequenz
mit Elementen aus A) . . 19
abh¨angige Transitionen . . . . . . . . . . . . 24
Abh¨angigkeitsbaum . . . . . . . . . . . . . . . 99
Abschluss . . . . . . . . . . . . . . . . . . 37, 43, 48
Übersetzung in Java . . . . . . . . . . . . 83
Abschluss-Objekt . . . . . . . . . . . . . . . . . 84
abstrakte Syntax . . . . . . . . . . . . . . . . . . . 39
adaptive Verfeinerung . . . . . . . . 143, 147
aktivieren . . . . . . . . . . . . . . . . . . . . . . . . . 23
algorithm. Skeletons . . . siehe Skeletons
applikative Sprachen . . . . . . . . . . . . . . . . 1
ARESO . . . . . . . . . . . . . . . . . . . . . . . . . 123
Argument (eines Funktionsknotens) . 48
Array . . . . . . . . . . . . . . . . . . . . . . . . . 28, 75
Assemblierung
Kosten . . . . . . . . . . . . . . . . . . . . . . 129
Assemblierungsmatrix . . . . . . . . . . . . 106
Ausdruck . . . . . . . . . . . . . . . . . . . . . . 18, 40
Ausführung (eines Netzes)
parallel . . . . . . . . . . . . . . . . . . . . . . . 25
sequentiell . . . . . . . . . . . . . . . . . . . . 23
Ausg¨ange (eines Datenflussgraphen) 49
& *
Auswerten . . . . . . . . . . . . . . . . . siehe -
eines Abschlusses . . . . . . . . . . . . . 56
eines Funktionsknotens . . . . . . . . . 55
Auswertungsstrategie . . . . . . . . . . . . . . 17
,
. . . . . . . . . . . . . . . . . . . . siehe Belegung
. . . . . . . . . . siehe Knotenpunktbasis
. . . . . . . . . . . . . . . . . . . . siehe Lastvektor
bedingter Ausdruck . . . . . . . . . . . . . . . . 59
Belegung
einer FASAN-Variablen . . . . . . . . 31
eines Datenflussgraphen . . . . . . . . 48
Bird-Meertens-Formalismus . . . . . . . . 83
BNF-Notation . . . . . . . . . . . . . . . . . . . . . 28
. . . . siehe Vorkonditionierungsmatrix
call by reference . . . . . . . . . . . . . . . . . . . 91
call by value . . . . . . . . . . . . . . . . . . . 17, 91
CG-HB . . . . . . . . . . . siehe CG-Verfahren
CG-Verfahren . . . . . . . . . . . . . . . . . . . . 122
Grundalgorithmus . . . . . . . . . . . . 121
Kosten . . . . . . . . . . . . . . . . . . . . . . 130
Cholesky-Verfahren . . . . . . . . . . . . . . 113
. . . . . . . . . . . . . . siehe Abschluss
. . . . . . . . . . . . . . . . . . . . . . . . . . 39, 78
CORBA . . . . . . . . . . . . . . . . . . . . . . . . . . 77
create . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Currying . . . . . . . . . . . . . . . . . . . . . . 37, 83
(Flussrelation) . . . . . . . . . . . . . . . . . . . 22
(transitive Hülle von ) . . . . . . . . . . 22
Datenabh¨angigkeit . . . . . . . . . . . . . . 3, 27
Datenelement . . . . . . . . . . . 23, 25, 48, 53
Datenfluss-Sprachen . . . . . . . . . . . . . . . . 7
Datenflussgraph . . . . . . . . . . . . . . . . 22, 48
einer bedingten Zuweisung . . . . . 51
einer rekursiven Funktion . . . . . . . 50
eines FASAN-Programms siehe
('& #
Index
164
eines Konstruktors . . . . . . . . . . . . . 50
eines Selektors . . . . . . . . . . . . . . . . 50
parallel . . . . . . . . . . . . . . . . . . . . . . . 54
Datenlokalit¨at . . . . . . . . . . . . . . . . . . . . . 65
Datenparallelismus . . . . . . . . . . . . . . . . . 8
Datenstruktur
adaptiv . . . . . . . . . . . . . . . 11, 99, 103
regul¨ar . . . . . . . . . . . . . . . . . . . . . . . . . 5
Debugging . . . . . . . . . . . . . . . . . . . . 72, 74
Dehierarchisierung . . . . . . . . . . . . . . . 108
deklarative Sprachen . . . . . . . . . . . . . . . . 1
denotationelle Semantik . . . . . . . . . . . . 20
Differentialgleichung . . . . . . . . . . . . . . 99
direkte Kommunikation . . . . . . . . . . . . 65
Diskretisierung . . . . . . . . . . . . . . 105, 119
Fehler . . . . . . . . . . . . . . . . . . . . . 5, 137
. . . . . . . . . . siehe Entkopplungsmatrix
(leere Belegung) . . . . . . . . . . . . . . 48, 53
eager evaluation . . . . . . . . . . . . . . . . . . . 17
ebene Elastizit¨at . . . . . . . . . . . . . . . . . . 137
Eing¨ange (eines Datenflussgraphen) . 49
Elimination
starker Kopplungen . . . . . . . . . . . 117
von Freiheitsgraden . . . . . . 112, 117
endliche Sequenz . . . . . . . . . . . . . . . . . . 19
Endrekursion . . . . . . . . . . . . . . . . . . 33, 79
Entfalten . . . . . . . . . . . . . . . siehe eines
-Knotens . . . . . . . . . . . 57
eines Funktionsknotens . . . . . . . . . 58
eines Konstruktorknotens . . . . . . . 60
eines parallelen Knotens . . . . . . . . 63
eines Selektorknotens . . . . . . . . . . 61
Entkopplungsmatrix . . . . . . . . . . . . . . 111
Ergebnis (eines Funktionsknotens) . . 48
erreichbar . . . . . . . . . . . . . . . . . . . . . . . . . 24
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Erzeuger (eines Puffers) . . . . . . . . . . . . 48
- . . . . . . . . . . . . . . . . . . . . . . . . . . 55–57
explizite Parallelisierung . . . . . . . . . . . 10
externe Funktion . . . . . . . . . . . . . . . . . . 29
externer Typ . . . . . . . . . . . . . . . . . . . . . . 30
('! *
& *
Füllgrad . . . . . . . . . . . . . . . . . . . . . . . . . 140
Factory-Methode . . . . . . . . . . . . . . . . . . 82
Fallunterscheidung . . . . . . . . . . . . . . . . 79
FASAN . . . . . . . . . . . . . . . 11, 22, 27, 123
Abbildung auf abstrakte Syntax
siehe Abbildung auf Datenflussgraphen
siehe
abstrakte Syntax . . . . . . . . . . . 39–46
Datenflussgraph . . . . . . . . . . . . 48–49
operationelle Semantik . . . . . . . . . 47
Sprachbeschreibung . . . . . . . . 27–39
FASAN-Server . . . . . . . . . . . . . . . . 78, 88
Fehlersch¨atzer . . . . . . . . . . . . . . . . . . . 100
feuern . . . . . . . . . . . . . . . . . . . . . . . . . 23, 54
Fill-in . . . . . . . . . . . . . . . . . . . . . . 112, 114
Finite-Element-Methode . . 99, 105, 138
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
FOCUS . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Folgemarkierung . . . . . . . . . . . . . . . . . . 23
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
FRAME . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
Instantiierung . . . . . . . . . . . . . . . . . 83
Freiheitsgrad . . . . . . . . . . . . . . . . . . . . . 101
extern . . . . . . . . . . . . . . . . . . . . . . . 104
intern . . . . . . . . . . . . . . . . . . . . . . . . 104
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Funktion
extern . . . . . . . . . . . . . . . . . . . . . 29, 82
höherer Ordnung . . . . . . 16, 38, 126
rekursiv . . . . . . . . . . . . . . . . . . . . . . . 31
funktionale Sprachen . . . . . . . . . . . . . . . . 1
Kennzeichen . . . . . . . . . . . . . . . . . . 15
Funktionsaufruf . . . . . . . . . 17, 31, 40, 50
parallel . . . . . . . . . . . . . . . . . . . . 34, 52
Funktionsknoten . . . . . . . . . . . . . . . . . . 48
argumentlos . . . . . . . . . . . . . . . . . . . 56
parallel . . . . . . . . . . . . . . . . . . . . . . . 52
Funktionstyp . . . . . . . . . . . . . . . . . . . . . . 18
# !
(&#
&
/ (ausgelassenes Argument) . . . . . 48, 53
Gauß-Seidel-Schritt . . . . . . . . . . . . . . 115
Gauß-Seidel-Verfahren . . . . . . . . . . . . 114
Kosten . . . . . . . . . . . . . . . . . . . . . . 130
mit partieller Elimination . . . . . . 118
Gebietszerlegung . . . . . . . . . . . . . 99, 100
Index
Verfeinerung . . . . . . . . . . . . . . . . . 102
Gent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Gleitpunktoperation . . . . . . . . . . 128, 129
globale Referenz . . . . . . . . . . . . . . . 76, 91
GlobalRef . . . . . . . . . . . . . . . . . . 92–96
Granularit¨at . . . . . . . . . . . . . . . . . . . . . 6, 27
Grundterm . . . . . . . . . . . . . . . . . . . . . . . . 18
Grundtyp . . . . . . . . . . . . . . . . . . . . . . . . . 30
GS-HB . . . siehe Gauß-Seidel-Verfahren
GS-HB-E . siehe Gauß-Seidel-Verfahren
mit partieller Elimination
.
. . . . . . . . . siehe hierarchische Basis
h¨angende Punkte . . . . . . . . . . . . . . . . . 104
Entkopplung . . . . . . . . . . . . . 110, 115
hashCode . . . . . . . . . . . . . . . . . . . . . . . 92
HEAT1 (Testbeispiel) . . . . . . . . . . . . . 137
HEAT2 (Testbeispiel) . . . . . . . . . . . . . 137
HF-Algorithmus . . . . . . . . . . . . . . . . . 131
hierarchische Basis . . . . . . . . . . . . . . . 107
hierarchischer Datentyp . . . . . 32, 44, 59
Übersetzung in Java . . . . . . . . . . . . 86
verteilte Ausführung . . . . . . . . . . . 91
hierarchischer Nachbarpunkt . . . . . . 108
hierarchischer Überschuss . . . . . . . . . 108
Hierarchisierung . . . . . . . . . . . . . 106, 108
Kosten . . . . . . . . . . . . . . . . . . . . . . 129
Hierarchisierungstabelle . . . . . . . . . . 109
Hostsprache . . . . . . . . . . . . . . . . . . . . 9, 29
IDL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
IIOP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
implizite Parallelisierung . . . . . . . . . . . . 7
inkompatible Punkte . . . siehe h¨angende
Punkte
Interpretation . . . . . . . . . . . . . . . . . . . . . 20
Jacobi-Schritt . . . . . . . . . . . . . . . . . . . . 118
Jacobi-Verfahren . . . . . . . . . . . . . . . . . 120
Kosten . . . . . . . . . . . . . . . . . . . . . . 130
Java . . . . . . . . . . . . . . . . . . 16, 29, 73, 144
Java Grande Forum Panel . . . . . . . 74, 78
Java Native Interface . . . . . . . . . . . 29, 74
JavaSpaces . . . . . . . . . . . . . . . . . . . . . . . . 77
J-HB . . . . . . . . . . siehe Jacobi-Verfahren
165
JNI . . . . . . . . siehe Java Native Interface
. . . . . . . . . . . . . . . . siehe Systemmatrix
Kabelbaum . . . . . . . . . . . . . . . . . . . . . . . 59
Prinzip . . . . . . . . . . . . . . . . . . . . 12, 39
Knotengleichungssystem . . . . . . . . . . 101
Knotenpunktbasis . . . . . . . . . . . . . . . . 107
Kommentare (in FASAN) . . . . . . . . . . 29
Kommunikation . . . . . . . . . . . . . . . . . . . . 5
asynchron . . . . . . . . . . . . . . . . . . . . . 64
direkt . . . . . . . . . . . . . . . . . . . . . . . . . 65
konfliktfrei . . . . . . . . . . . . . . . . . . . . . . . . 24
Konjugierte-Gradienten-Verfahren siehe
CG-Verfahren
Konstanten (in FASAN) . . . . . . . . . . . . 28
Konstruktor . . . . . . . . . . . . . . . . . . . . . . . 32
Übersetzung in Java . . . . . . . . . . . . 86
Definition in FASAN . . . . . . . . . . . 44
Kontrollfluss . . . . . . . . . . . . . 6, 34, 74, 85
Konvergenz . . . . . . . . . . . . . . . . . . . . . . 106
Konvergenzrate . . . . . . . . . . . . . . . . . . 139
Koordinationssprache . . . . . . . . . . . . . 7, 9
Kostenabsch¨atzung . . . . . . . . . . . . . . . 129
Kragscheibe . . . . . . . . . . . . . . . . . 100, 137
kritischer Pfad . . . . . . . . . . . . . . . . . . . 148
&
. . . . . . . . . . . . . . . . . . . . . siehe Lokation
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Lösungsvektor . . . . . . . . . . . . . . . . . . . 104
Transformation . . . . . . . . . . . . . . . 112
Lastvektor . . . . . . . . . . . . . . . . . . . . . . . 104
Transformation . . . . . . . . . . . . . . . 112
Lastverteilung . . . . . . . . . . . . . . . . . . . . . 12
durch Lokationen . . . . . . . . . . . . . . 36
dynamisch . . . . . . . . . . . . . . . . 35, 131
statisch . . . . . . . . . . . . . . . . . . . . . . 127
Laufbedingung . . . . . . . . . . . . . . . . . . 6, 66
Ausschluss . . . . . . . . . . . . . . . . . . . . 66
lazy evaluation . . . . . . . . . . . . . . . . . . . . 17
lenient evaluation . . . . . . . . . . . . . . . . . . . 7
Linienbaum . . . . . . . . . . . . . . . . . . . . . . 102
& . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Lokation . . . . . . . . . . . . . 34, 54, 127, 133
im Datenflussgraphen . . . . . . . . . . 52
Lokationsfunktion . . . . . . siehe Lokation
Index
166
. . . . . . . . . . . . . . . . . siehe Markierung
Markierung . . . . . . . . . . . . . . . . . . . . . . . 23
eines Datenflussgraphen . . . . . . . . 49
Matrix
dünnbesiedelt . . . . . . . . . . . . . . . . 114
Reihenoperation . . . . . . . . . . . . . . 110
Matrixmultiplikation
Kosten . . . . . . . . . . . . . . . . . . . . . . 129
Methodenaufruf . . . . . . . . . . . . . . . . . . . 29
remote . . . . . . . . . . . . . . . . . . . . . . . . 74
Monitor . . . . . . . . . . . . . . . . . . . . . . 87, 145
MPI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
(Matrix-Reihenoperation) . . . . . 110
Nachbereich . . . . . . . . . . . . . . . . . . . . . . 22
natürliche Semantik . . . . . . . . . . . . . . . . 20
n-beschr¨ankt . . . . . . . . . . . . . . . . . . . . . . 23
nested dissection . . . . . . . . . . . . . . . . . 112
. . . . . . . . . . . . . . . . . . . . . . . . . . . 49–53
Netz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
nicht-strikt . . . . . . . . . . . . . . . . . . . . . . . . 21
Nonterminal . . . . . . . . . . . . . . . . . . . . . . 28
(&#
(Relaxationsfaktor) . . . . . . . . . . . . . 118
Objektorientierung . . . . . . . . . . . . . . . . . . 2
operationelle Semantik . . . . . . . . . 20, 47
(Potenzmenge) . . . . . . . . . . . . . . . . . . 19
PANEL (Testbeispiel) . . . . . . . . . . . . . 138
para-funktionale Programmierung . . . . 9
parallele Transitionen . . . . . . . . . . . . . . 24
Parallelisierung
explizit . . . . . . . . . . . . . . . . . . . . . . . 10
implizit . . . . . . . . . . . . . . . . . . . . . . . . 7
Parallelit¨atsgrad . . . . . . . . . . . . . . . . . . . 66
partielle Elimination . . . . . . . . . . . . . . 112
passieren . . . . . . . . . . . . . . . . . . . . . . . . . 65
Performance-Garantie . . . . . . . . . . . . 132
Petri-Netz . . . . . . . . . . . . . . . . . . . . . . . . 22
Pfad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Pipeline . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Pizza . . . . . . . . . . . . . . . . . . . 16, 17, 30, 83
Platz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
POISSON (Testbeispiel) . . . . . . . . . . 136
Poisson-Gleichung . . . . . . . . . . . . . . . 136
Polymorphismus . . . . . . . . . . . . . . . 15, 30
Portabilit¨at . . . . . . . . . . . . . . . . . . . . 12, 73
Pr¨adikat . . . . . . . . . . . . . . . . . . . . . . . 32, 95
Übersetzung in Java . . . . . . . . . . . . 86
Definition in FASAN . . . . . . . . . . . 44
prozedurale Sprachen . . . . . . . . . . . . . . . 1
Prozessnetz . . . . . . . . . . . . . . . . . . . . 24, 47
Puffer . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
PVM . . . . . . . . . . . . . . . . . . . . . . . . 72, 100
. . . . . . . . . . . . . . . siehe Konvergenzrate
. . . . . . . . . . . . . . . . . . . . . siehe Residuum
race condition . . . . siehe Laufbedingung
Randbedingung
Dirichlet . . . . . . . . . . . . . . . . 101, 104
. . . . . . . . . . . . . . . . . . . . . . . . . . . 39, 78
rechenbereit . . . . . . . . . . . . . . . . . . . . . . . 54
Rechnergrenze . . . . . . . . . . . . . . . . . . . . 64
recycle . . . . . . . . . . . . . . . . . . . . . 33, 79
referentielle Transparenz . . . . . . . . 15, 91
Referenztabelle . . . . . . . . . . . . . . . . . . . 92
Eintrag . . . . . . . . . . . . . . . . . . . . . . . 95
Referenzz¨ahler . . . . . . . . . . . . . . . . . 69, 95
Reihenoperation . . . . . . . . . . . . . 110, 117
rekursive Funktion . . . . . . . . . . . . . . . . . 31
rekursive Programmdefinition . . . . . . . 20
rekursives Programm . . . . . . . . . . . . . . 21
Relaxationsfaktor . . . . . . . . . . . . 118, 139
RemoteObject . . . . . . . . . . . . . . . . . 88
Replikationsknoten . . . . . . . . . . . . . . . . 61
Residuum . . . . . . . . . . . . . . . . . . . 118, 139
Transformation . . . . . . . . . . . . . . . 112
Ritz-Galerkin-Verfahren . . . . . . . . . . 138
RMI . . . . . . . . . . . . . . . . . . . . . . . . . . 71, 74
rmic . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
. . . . . . . . . . . . . . . . siehe Signatur
-Algebra . . . . . . . . . . . . . . . . . . . . . . . . 20
schalten . . . . . . . . . . . . . . . . . . . . . . . 23, 54
Schleife . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Schnitt . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Seiteneffektfreiheit . . . . . . . . . . . . . 15, 29
. . . . . . . . . . . . . . . . . . . . . . . . . . . 39, 78
Selektor . . . . . . . . . . . . . . . . . . . . . . . 32, 95
Übersetzung in Java . . . . . . . . . . . . 86
Index
Definition in FASAN . . . . . . . . . . . 44
sequentielle Semantik . . . . . . . . . . . . . . 23
Sequenz . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Serialisierung . . . . . . . . . . . . . . . . . . 74, 75
Signatur . . . . . . . . . . . . . . . . . . . . . . . 18, 39
algorithmisch . . . . . . . . . . . . . . . . . 18
Skalarprodukt
Kosten . . . . . . . . . . . . . . . . . . . . . . 129
Skeleton . . . . . . . . . . . . . . . . . . . . 9, 38, 83
Java-Klasse . . . . . . . . . . . . . . . . . . . 89
Socket Factory . . . . . . . . . . . . . . . . . . . . 76
Speicherverwaltung . . . . . . . . . 16, 68, 74
verteilt . . . . . . . . . . . . . . . . . . . . . . . . 92
Spracherweiterung . . . . . . . . . . . . . . . 6, 8
station¨are Warmeleitung . . . . . . . . . . 136
statische Analyse . . . . . . . . . . . . . . . . . . 10
Steifigkeitsmatrix . . . . . . . . . . . . . . . . 138
strikt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Stub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Substrukturierung (Beschreibung) . . 102
Substrukturierungsbaum . . . . . . . . . . 101
Aufbau . . . . . . . . . . . . . . . . . . . . . . 102
balanciert . . . . . . . . . . . . . . . . . . . . 127
unbalanciert . . . . . . . . . . . . . 143, 147
Verzweigungsgrad . . . . . . . . . . . . 125
SWITCH-Methode . . . . . . . . . . . . . . . . . 91
Synchronisation . . . . . . . . . . . . . . . . . 5, 25
synchronized . . . . . . . . . . . . . . . . . 87
Systemmatrix . . . . . . . . . . . . . . . . . . . . 104
Tensorprodukt . . . . . . . . . . . . . . . . . . . 107
Term . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Thread . . . . . . . . . . . . . . . . . . . 34, 74, 145
native . . . . . . . . . . . . . . . . . . . . . . . . . 85
. . . . . . . . . . . . . . . . . . . . . . . . . 41–46
Tr¨agermenge . . . . . . . . . . . . . . . . . . . . . . 20
Transition . . . . . . . . . . . . . . . . . . . . . . . . 22
. . . . . . . . . . . . . . . . . . . . . . . . . . 39, 78
Typausdruck . . . . . . . . . . . . . . . . . . . . . . 18
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Typisierung . . . . . . . . . . . . . . . . . . . . . . . 15
. . . . . . . . . . . . . . . . siehe Lösungsvektor
unabh¨angige Transitionen . . . . . . . . . . 24
. . . . . . . . . . . . . . . . . . . . . . . 55–64
# !
.(
0
(! *
167
unidirektional . . . . . . . . . . . . . . . . . . . . . . 2
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Variable (in FASAN)
belegen . . . . . . . . . . . . . . . . . . . . . . . 31
lesen . . . . . . . . . . . . . . . . . . . . . . . . . 31
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Verbindungsrelation . . . . . . . . . . . . . . . 22
Verbraucher (eines Puffers) . . . . . . . . . 48
Verfeinerung
adaptiv . . . . . . . . . . . . . . . . . . 143, 147
gleichm¨aßig . . . . . . . . . . . . . . . . . . 140
Verklemmung . . . . . . . . . . . . . . . . . . . 6, 68
Verklemmungsfreiheit . . . . . . . . . . . . . 68
Verpackung . . . . . . . . . . . . . . . . . . . . . . . 86
Verpackungsklasse . . . . . . . . . . . . . . . . 81
verteilte Berechnung . . . . . . . . . . . . . . 147
Verzweigungsgrad . . . . . . . . . . . . . . . . 125
Vorbereich . . . . . . . . . . . . . . . . . . . . . . . . 22
Vorkonditionierung . . . . . . . . . . . . . . . 112
. . . . . . . . . . . . . . . . . siehe Term
Wiederverwendung . . . . . . . . . . . . . . . . 12
0
Zielsprache . . . . . . . . . . . . . . . . . . . . . . . 73
Zusammenfügen von Teilgebieten . . 104
Zuweisung . . . . . . . . . . . . . . . . . . . . 31, 40
bedingt . . . . . . . . . . . . . . . . . . . . . . . 31
Herunterladen