LR-Zerlegung dünnbesetzter Matrizen für Parallelrechner mit ... - ZIB
LR-Zerlegung dünnbesetzter Matrizen für Parallelrechner mit ... - ZIB LR-Zerlegung dünnbesetzter Matrizen für Parallelrechner mit ... - ZIB
Diplomarbeit LR-Zerlegung dünnbesetzter Matrizen für Parallelrechner mit verteiltem Speicher Michael Ganß rms@cs.tu-berlin.de Matrikelnummer 131071 Februar 1997 Betreuer: Prof. Dr. Stefan Jähnichen Prof. Dr. Peter Pepper Technische Universität Berlin Fachbereich Informatik Institut für Kommunikations- und Softwaretechnik Fachgebiet Softwaretechnik Sekretariat FR 5-6 Franklinstraße 28/29 D-10587 Berlin
- Seite 3 und 4: Inhaltsverzeichnis 1 Einleitung 1 2
- Seite 5: Danksagung Das in dieser Arbeit bes
- Seite 8 und 9: 1 Einleitung schließlich dünnbese
- Seite 10 und 11: 1 Einleitung 4
- Seite 12 und 13: 2 Lösung linearer Gleichungssystem
- Seite 14 und 15: 2 Lösung linearer Gleichungssystem
- Seite 16 und 17: 2 Lösung linearer Gleichungssystem
- Seite 18 und 19: 2 Lösung linearer Gleichungssystem
- Seite 20 und 21: 2 Lösung linearer Gleichungssystem
- Seite 22 und 23: 2 Lösung linearer Gleichungssystem
- Seite 24 und 25: 3 Parallele LR-Zerlegung element (P
- Seite 26 und 27: 3 Parallele LR-Zerlegung Die SHMEM
- Seite 28 und 29: 3 Parallele LR-Zerlegung 6 7 2 3 Sc
- Seite 30 und 31: 3 Parallele LR-Zerlegung 4. Abbildu
- Seite 32 und 33: 3 Parallele LR-Zerlegung hingegen n
- Seite 34 und 35: 3 Parallele LR-Zerlegung 3.4.3 Komm
- Seite 36 und 37: 3 Parallele LR-Zerlegung 0 1 2 3 0
- Seite 38 und 39: 3 Parallele LR-Zerlegung 3.5.1 Date
- Seite 40 und 41: 3 Parallele LR-Zerlegung Ermittlung
- Seite 42 und 43: 3 Parallele LR-Zerlegung kompatible
- Seite 44 und 45: 3 Parallele LR-Zerlegung for m = 1
- Seite 46 und 47: 3 Parallele LR-Zerlegung ˙≤ ist
- Seite 48 und 49: 3 Parallele LR-Zerlegung globalen P
- Seite 50 und 51: 3 Parallele LR-Zerlegung Phase C: A
Diplomarbeit<br />
<strong>LR</strong>-<strong>Zerlegung</strong> <strong>dünnbesetzter</strong><br />
<strong>Matrizen</strong> <strong>für</strong> <strong>Parallelrechner</strong> <strong>mit</strong><br />
verteiltem Speicher<br />
Michael Ganß<br />
rms@cs.tu-berlin.de<br />
Matrikelnummer 131071<br />
Februar 1997<br />
Betreuer:<br />
Prof. Dr. Stefan Jähnichen<br />
Prof. Dr. Peter Pepper<br />
Technische Universität Berlin<br />
Fachbereich Informatik<br />
Institut <strong>für</strong> Kommunikations- und Softwaretechnik<br />
Fachgebiet Softwaretechnik<br />
Sekretariat FR 5-6<br />
Franklinstraße 28/29<br />
D-10587 Berlin
Inhaltsverzeichnis<br />
1 Einleitung 1<br />
2 Lösung linearer Gleichungssysteme 5<br />
2.1 <strong>LR</strong>-<strong>Zerlegung</strong> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5<br />
2.2 Generischer <strong>LR</strong>-<strong>Zerlegung</strong>s-Algorithmus . . . . . . . . . . . . . . . 12<br />
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong> 17<br />
3.1 Der massiv-parallele Rechner Cray T3D . . . . . . . . . . . . . . . . 17<br />
3.2 Paralleles Programmiermodell . . . . . . . . . . . . . . . . . . . . . 18<br />
3.3 Eingesetzte Funktionen und Klassen . . . . . . . . . . . . . . . . . . 19<br />
3.4 Der parallele Algorithmus . . . . . . . . . . . . . . . . . . . . . . . 23<br />
3.4.1 Die PCAM-Methode . . . . . . . . . . . . . . . . . . . . . . 23<br />
3.4.2 Partitionierung . . . . . . . . . . . . . . . . . . . . . . . . . 25<br />
3.4.3 Kommunikation . . . . . . . . . . . . . . . . . . . . . . . . 28<br />
3.4.4 Agglomeration und Abbildung . . . . . . . . . . . . . . . . . 29<br />
3.5 Die Klasse DSLUFactor . . . . . . . . . . . . . . . . . . . . . . . . 31<br />
3.5.1 Datenverteilung . . . . . . . . . . . . . . . . . . . . . . . . . 32<br />
3.5.2 Elimination von Singletons . . . . . . . . . . . . . . . . . . . 32<br />
3.5.3 Faktorisierung des Nukleus . . . . . . . . . . . . . . . . . . . 34<br />
3.6 Ergebnisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57<br />
3.6.1 Testumgebung . . . . . . . . . . . . . . . . . . . . . . . . . 57<br />
3.6.2 Load-Balancing . . . . . . . . . . . . . . . . . . . . . . . . . 61<br />
3.6.3 Aufwandsparameter paralleler Algorithmen . . . . . . . . . . 65<br />
3.6.4 Faktorisierungszeiten . . . . . . . . . . . . . . . . . . . . . . 66<br />
4 Parallele Lösung von Dreieckssystemen 73<br />
4.1 Sequentieller Algorithmus . . . . . . . . . . . . . . . . . . . . . . . 73<br />
4.2 Paralleler Algorithmus . . . . . . . . . . . . . . . . . . . . . . . . . 76<br />
4.2.1 Partitionierung . . . . . . . . . . . . . . . . . . . . . . . . . 76<br />
I
Inhaltsverzeichnis<br />
4.2.2 Kommunikation . . . . . . . . . . . . . . . . . . . . . . . . 77<br />
4.2.3 Agglomeration und Abbildung . . . . . . . . . . . . . . . . . 77<br />
4.3 Implementierung des parallelen Algorithmus . . . . . . . . . . . . . 80<br />
4.4 Ergebnisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82<br />
5 Zusammenfassung 91<br />
II
Danksagung<br />
Das in dieser Arbeit beschriebene Programm entstand am Konrad-Zuse-Zentrum Berlin.<br />
Mein besonderer Dank gilt dort Roland Wunderling <strong>für</strong> die geduldige und präzise<br />
Beantwortung all meiner Fragen, die stets kompetente Beratung in technischen<br />
Angelegenheiten sowie die kritische und konstruktive Begutachtung meines Arbeitsfortschritts.<br />
Hans-Christian Hege danke ich <strong>für</strong> die Freiheiten, die er mir am <strong>ZIB</strong><br />
gewährte und die Bemühungen, die er meinetwegen unternahm. Danken möchte ich<br />
auch Gabi Keller und Manuel Chakravarty vom Fachgebiet Softwaretechnik <strong>für</strong> die<br />
Beantwortung meiner Fragen sowie die hilfreichen Hinweise und Anregungen, die sie<br />
mir gaben.
1 Einleitung<br />
In vielen wissenschaftlichen Bereichen stößt man auf die Notwendigkeit, lineare Gleichungssysteme<br />
lösen zu müssen, so u. a. in Disziplinen wie der Astrophysik, der Erdöltechnik<br />
oder bei der Modellierung von wirtschaftlichen Prozessen [10]. Generell<br />
ist man bei Verfahren in vielen Feldern der angewandten Mathematik auf die Lösung<br />
von linearen Gleichungssystemen angewiesen, so z. B. bei der Lösung von Differentialgleichungen<br />
[12] oder bei der linearen Programmierung [5]. Nicht viele Probleme,<br />
die in der Informatik untersucht werden, haben ein größeres Anwendungsfeld in der<br />
realen Welt.<br />
Parallele Programmierung Obwohl Computer immer schneller werden, und man<br />
vermuten könnte, daß sie früher oder später schnell genug werden, um den Bedarf an<br />
erhöhter Rechenleistung zu stillen, gibt es stets neue Anwendungen, die nach einer<br />
weiteren technologischen Entwicklung verlangen (ganz abgesehen von wirtschaftlichen<br />
Interessen, die hier aber vernachlässigt werden). So gibt es z. B. im Simulationsbereich<br />
Anwendungen, die sich durch Hinzunahme neuer Parameter oder Verfeinerung<br />
der Auflösung beliebig komplex machen lassen [11, Kapitel 1].<br />
In den letzten 40 Jahren hat sich die Geschwindigkeit von Prozessoren etwa alle 2<br />
Jahre verdoppelt (“Moore’s law” [17]). Es zeichnet sich aber ab, daß dies nicht mehr<br />
lange möglich sein wird, da irgendwann elementare physikalische Grenzen erreicht<br />
sein werden. Auch “instruction level parallelism” (längere Pipelines, mehr Funktionseinheiten)<br />
kann nicht beliebig erhöht werden. Man kann also nicht auf schnellere<br />
Prozessoren als Garanten <strong>für</strong> verbesserte Rechenleistung vertrauen. Es bietet sich<br />
daher an, mehrere Rechner parallel an der Lösung eines Problems in kürzerer Zeit<br />
arbeiten zu lassen. Konsequenterweise wird Nebenläufigkeit ein fundamentales Erfordernis<br />
<strong>für</strong> Algorithmen und Programme sein.<br />
Aufgabenstellung In dieser Arbeit wird ein paralleles Programm zur Lösung eines<br />
linearen Gleichungssystems Ax = b entwickelt.<br />
<strong>Matrizen</strong> aus konkreten Anwendungsbereichen wie oben erwähnt sind fast aus-<br />
1
1 Einleitung<br />
schließlich dünnbesetzt, d. h. cn der n 2 Elemente der Matrix A sind ungleich Null,<br />
wobei c
Aufbau In Kapitel 2 wird das Verfahren der <strong>LR</strong>-<strong>Zerlegung</strong> erläutert und ein sequentieller<br />
Algorithmus zur Durchführung desselben vorgestellt. Im darauffolgenden<br />
Kapitel wird, nach einer zuvorigen Darstellung der hard- und softwaretechnischen Bedingungen,<br />
ein paralleler <strong>LR</strong>-<strong>Zerlegung</strong>s-Algorithmus entwickelt und dessen Implementierung<br />
beschrieben sowie die <strong>mit</strong> diesem Algorithmus gesammelten Ergebnisse<br />
diskutiert. Kapitel 4 behandelt den Entwurf und die Implementierung eines parallelen<br />
Algorithmus zur Lösung von Dreieckssystemen, wie sie bei der <strong>LR</strong>-<strong>Zerlegung</strong> von<br />
Koeffizientenmatrizen allgemeiner Gleichungssysteme entstehen. Dort werden auch<br />
die <strong>mit</strong> der Implementierung erzielten Ergebnisse diskutiert. In Kapitel 5 werden die<br />
in dieser Arbeit gewonnenen Erkenntnisse zusammengefaßt.<br />
3
1 Einleitung<br />
4
2 Lösung linearer Gleichungssysteme<br />
In diesem Kapitel soll zunächst im ersten Abschnitt das Verfahren der <strong>LR</strong>-<strong>Zerlegung</strong><br />
zur Lösung eines linearen Gleichungssystems erläutert werden. Ausgehend von vereinfachten<br />
Gleichungssystemen soll das Verfahren auf allgemeine Gleichungssysteme<br />
ausgedehnt werden. Im zweiten Abschnitt des Kapitels soll dann ein sequentieller Algorithmus<br />
zur Umsetzung des Verfahrens vorgestellt werden.<br />
2.1 <strong>LR</strong>-<strong>Zerlegung</strong><br />
Ein lineares Gleichungssystem <strong>mit</strong> ebensovielen Gleichungen wie Unbekannten hat<br />
die Form<br />
++a ++a<br />
a 11 x 1 + a 12 x 2<br />
a 21 x 1 + a 22 x 2<br />
.<br />
a n1 x 1 + a n2 x 2 ++a<br />
1n<br />
2n<br />
nn<br />
oder kurz in Matrixschreibweise<br />
Ax = b.<br />
x n = b 1<br />
x n<br />
.<br />
= b 2<br />
.<br />
(2.1)<br />
x n = b n<br />
A ist eine reelle n×n Matrix und b,x sind reelle Vektoren der Dimension n. Die Matrix<br />
A und der Vektor b sind gegeben und x ist der unbekannte Lösungsvektor.<br />
Lösung von Dreieckssystemen<br />
++r ++r<br />
r 11 x 1 + r 12 x 2<br />
r 22 x 2<br />
1n<br />
2n<br />
Ein Gleichungssystem der Form<br />
x n = y 1<br />
x n = y 2<br />
. .. .<br />
.<br />
(2.2)<br />
r nn x n = y n<br />
5
2 Lösung linearer Gleichungssysteme<br />
kurz<br />
Rx = y<br />
heißt Dreieckssystem und läßt sich sehr einfach direkt lösen. R ist eine obere Dreiecksmatrix<br />
(“upper triangular matrix”), d. h. r i j = 0 <strong>für</strong> alle i > j. Unter der Voraussetzung,<br />
daß r j j ≠ 0, erhält man die Lösung durch sukzessives Einsetzen der Teillösungen von<br />
unten her. Aus der letzten Gleichung erhält man die Lösung <strong>für</strong> x n , diese setzt man in<br />
die vorletzte ein und erhält so<strong>mit</strong> x n−1 usw.<br />
x n = y n<br />
r nn<br />
x n−1 = (y n−1−r n−1n x n )<br />
r n−1n−1<br />
.<br />
x 1 = (y 1−r 12 x 2 −:::−r<br />
1n x n )<br />
r 11<br />
(2.3)<br />
Das so durchgeführte Lösen heißt Rückwärtssubstitution (“backward solve”). In gleicher<br />
Weise läßt sich auch ein Gleichungssystem der Form<br />
Ly = b (2.4)<br />
lösen, wobei L eine untere Dreiecksmatrix (“lower triangular matrix”) ist, d. h. l i j = 0<br />
<strong>für</strong> alle j > i. Dieses Auflösen nennt man Vorwärtssubstitution (“forward solve”).<br />
Faktorisierung von A Um allgemeine Gleichungssysteme der Form (2.1) zu lösen,<br />
kann man sich der Methode (2.3) bedienen, wenn es gelingt, die Koeffizientenmatrix<br />
A äquivalent in das Produkt einer unteren Dreiecksmatrix L und einer oberen Dreiecksmatrix<br />
R zu transformieren, da<br />
b = Ax = (<strong>LR</strong>)x = L(Rx) ⇒ Ly = b, Rx = y.<br />
Eine solche Transformation leistet die <strong>LR</strong>-<strong>Zerlegung</strong> 1 . Ein allgemeines Gleichungssystem<br />
der Form (2.1), dessen Koeffizientenmatrix in die Faktoren L und R transformiert<br />
werden kann, läßt sich <strong>mit</strong>tels <strong>LR</strong>-<strong>Zerlegung</strong> unter Einhaltung folgender Schritte lösen:<br />
1 Im Englischen LU factorization wg. “lower-upper” vs. “links-rechts”.<br />
6
2.1 <strong>LR</strong>-<strong>Zerlegung</strong><br />
a) A = <strong>LR</strong> <strong>Zerlegung</strong> in obere und untere Dreiecksmatrix<br />
b) Ly = b Vorwärtssubstitution<br />
c) Rx = y Rückwärtssubstitution<br />
Um die Faktoren L und R zu bestimmen, erzeugt man bei der <strong>LR</strong>-<strong>Zerlegung</strong> ausgehend<br />
von A = A (1) in mehreren Transformationsschritten <strong>Matrizen</strong> A (s) , die sich immer<br />
mehr der rechten oberen Dreiecksmatrix R annähern. Die Transformationen werden<br />
durch Multiplikation der A (s) von links <strong>mit</strong> Transformationsmatrizen L s durchgeführt.<br />
Aus deren Produkt läßt sich einfach die untere Dreiecksmatrix L bestimmen.<br />
Um A in eine obere Dreiecksmatrix zu transformieren, muß die erste Zeile von A<br />
nicht verändert werden. Man setzt also<br />
=2<br />
A (1) := A.<br />
Aus den restlichen Zeilen müssen die Elemente der ersten Spalte verschwinden. Dies<br />
kann erreicht<br />
6 375<br />
werden, indem man man A <strong>mit</strong><br />
41<br />
−l 21 1<br />
L 1<br />
.<br />
.. .<br />
−l n1 1<br />
von links multipliziert. Dabei soll<br />
l i1 := a i1<br />
a 11<br />
<strong>für</strong> i = 2,:::,n<br />
sein. Unter der Voraussetzung a 11 ≠0 ist der erste Transformationsschritt so<strong>mit</strong> durchführbar.<br />
Man erhält die Matrix A (2) <strong>mit</strong><br />
A (2) := L 1 A (1) .<br />
Das Element a 11 heißt Pivotelement (kurz Pivot), die dazugehörige Zeile Pivotzeile<br />
(“pivot row”). Das Verfahren kann man fortsetzen, indem man die Elemente a (2)<br />
i2<br />
<strong>mit</strong><br />
i = 3,:::,n aus A (2) durch Multiplikation von A (2) <strong>mit</strong> L 2 verschwinden läßt und daraus<br />
A (3) erhält usf. Man erhält so eine Folge<br />
A = A (1) → A (2) →:::→A (n) = R<br />
7
2 Lösung linearer Gleichungssysteme<br />
von <strong>Matrizen</strong> A (s) der Gestalt<br />
a (1) 375, 1n<br />
a (2)<br />
2n<br />
. ..<br />
A (s) =2 375<br />
.<br />
=<br />
a (s)<br />
(2.5)<br />
sn<br />
. .<br />
a (s)<br />
nn<br />
aus denen<br />
6<br />
sich durch Multiplikation <strong>mit</strong> der Matrix<br />
41<br />
. ..<br />
1<br />
L s (2.6)<br />
−l s+1s 1<br />
.<br />
. ..<br />
264a (1)<br />
11<br />
a (1)<br />
12<br />
a (2)<br />
22<br />
<br />
ssa (s)<br />
nsa (s)<br />
−l ns 1<br />
die <strong>Matrizen</strong> A (s+1) ergeben. Dabei gilt<br />
l is := a(s) is<br />
a (s) <strong>für</strong> i = s + 1,:::,n (2.7)<br />
ss<br />
unter der Voraussetzung, daß das Pivotelement a (s)<br />
ss ≠ 0 ist. Dieser Übergang, auch<br />
Eliminationsschritt genannt, läßt sich in Matrixschreibweise folgendermaßen ausdrücken:<br />
A (s+1) := L s =2 A (s) .<br />
Die Matrix L s ist eine sogenannte Frobenius-Matrix, die die Eigenschaft besitzt, daß<br />
die Inverse L −1<br />
s durch einen Vorzeichenwechsel in den l is <strong>mit</strong> i > s entsteht. Aus der<br />
folgenden Definition<br />
6 375<br />
41<br />
L:= L −1<br />
n−1 L−1 n−2:::L −1 l 21 1<br />
1 .<br />
. .. . ..<br />
l n1l 1<br />
läßt sich<br />
nn−1<br />
R = L −1 A ⇔ <strong>LR</strong> = LL −1 A ⇔ A = <strong>LR</strong><br />
ableiten.<br />
8
2.1 <strong>LR</strong>-<strong>Zerlegung</strong><br />
Pivotsuche Die <strong>LR</strong>-<strong>Zerlegung</strong> ist nach der oben beschriebenen Methode auch unter<br />
der Voraussetzung, daß Ax = b eine eindeutig bestimmte Lösung besitzt, nicht immer<br />
<strong>mit</strong>tels (2.7) möglich. Für die Matrix<br />
A =0 1<br />
1 0,<br />
gilt zwar det A ≠ 0, d. h. es existiert eine eindeutig bestimmte Lösung eines linearen<br />
Gleichungssystems <strong>mit</strong> A als Koeffizientenmatrix, aber l 21 ist nicht definiert, da<br />
a 11 = 0. Vertauscht man jedoch Zeile 1 <strong>mit</strong> Zeile 2, dann ist die <strong>LR</strong>-<strong>Zerlegung</strong> von<br />
A ohne weiteres möglich, es ist sogar A = L = R. Dabei muß man, um die Äquivalenz<br />
des Gleichungssystems Ax = b zu erhalten, auch die entsprechenden Zeilen von<br />
b vertauschen.<br />
Bei der Darstellung von reellen Zahlen als Fließkommazahlen können auch bei<br />
“zu kleinen” Pivotelementen aufgrund von Rundungsfehlern Probleme entstehen.<br />
Beispielsweise hat das Gleichungssystem<br />
0.005x + y = 0.5<br />
x + y = 1.0<br />
(2.8)<br />
die exakte Lösung<br />
x = 100<br />
99<br />
≈ 0.5, y =<br />
199 199 ≈ 0.5.<br />
Führt man hingegen die <strong>LR</strong>-<strong>Zerlegung</strong> <strong>mit</strong> Fließkommaarithmetik einer Genauigkeit<br />
auf zwei Stellen durch, so erhält man<br />
0<br />
1<br />
L =1<br />
R =0.005<br />
(2.9)<br />
200 1,<br />
0 −200.<br />
=<br />
Daraus folgt als Lösung nach Vor- und Rückwärtssubstitution<br />
x = 0.0, y = 0.5.<br />
Vertauscht man jedoch wieder die beiden Zeilen, so ergibt sich<br />
1 0<br />
1<br />
L R =1<br />
(2.10)<br />
0.005 1,<br />
0 1,<br />
sowie<br />
x = 0.5, y = 0.5.<br />
9
2 Lösung linearer Gleichungssysteme<br />
Um die <strong>LR</strong>-<strong>Zerlegung</strong> <strong>für</strong> Koeffizientenmatrizen allgemeiner Gleichungssysteme<br />
durchführen zu können, ist es u. U. nötig, Zeilenvertauschungen vorzunehmen. Für<br />
jede invertierbare Matrix (d. h. <strong>mit</strong> welcher als Koeffizientenmatrix ein lineares Gleichungssystem<br />
(2.1) eine eindeutig bestimmte Lösung besitzt) gibt es eine Folge von<br />
Zeilenvertauschungen, so daß eine <strong>LR</strong>-<strong>Zerlegung</strong> möglich ist (<strong>für</strong> den Beweis siehe<br />
[7, Satz 1.8]).<br />
Um ein möglichst genaues Resultat zu erzielen, führt man die Zeilenvertauschungen<br />
so aus, daß man im s-ten Eliminationsschritt die s-te Zeile <strong>mit</strong> derjenigen Zeile<br />
aus den Zeilen s bis n vertauscht, die das betragsmäßig größte Element in der s-ten<br />
Spalte besitzt. Man sucht also als Pivotelement a (s)<br />
ss das <strong>mit</strong> dem größten Betrag in<br />
der Pivotspalte s aus. Dieses Vorgehen nennt man Spaltenpivotsuche (“column pivoting”).<br />
Man erhält dadurch ein i. allg. besseres Ergebnis als durch Auswahl eines<br />
anderen Pivotelements in der Pivotspalte [7, Kapitel 2]. Man ist aber auch <strong>mit</strong> diesem<br />
Verfahren nicht vor starken Ungenauigkeiten gefeit, wie das zu (2.8) äquivalente<br />
System<br />
2x + 400y = 200<br />
x + y = 1.0<br />
(2.11)<br />
beweist, dessen Lösung bei Spaltenpivotsuche, d. h. wenn man nur Zeilenvertauschungen<br />
zuläßt, dieselbe Ungenauigkeit wie von (2.8) über (2.9) aufweist.<br />
Anstelle der Spaltenpivotsuche <strong>mit</strong> Zeilentausch kann man auch eine Zeilenpivotsuche<br />
<strong>mit</strong> Spaltentausch durchführen. Kombiniert man beide Vorgehensweisen und<br />
wählt das betragsmäßig größte Element a (s)<br />
i s j s<br />
<strong>mit</strong> i s ≥ s und j s ≥ s, so heißt dies vollständige<br />
Pivotsuche (“total pivoting”). Mit vollständiger Pivotsuche erhielte man bei<br />
(2.11) dasselbe (bezüglich der zweistelligen Fließkommadarstellung exakte) Ergebnis<br />
wie bei (2.10).<br />
Wie bereits oben angeführt, müssen etwaige Zeilenvertauschungen auch <strong>für</strong> den<br />
Vektor b der rechten Seite vorgenommen werden. Bei Durchführung der <strong>LR</strong>-<br />
<strong>Zerlegung</strong> <strong>für</strong> eine Koeffizientenmatrix A wird jedoch, wie in der Einleitung erwähnt,<br />
von b abstrahiert, so daß die Vertauschungen (auch Permutationen genannt) nicht explizit<br />
vorgenommen werden können. Aus diesem Grund merkt man sich die Permutationen<br />
in einem n-dimensionalen Vektor π, der Permutationsvektor genannt wird.<br />
Vertauscht man im s-ten Schritt Zeile s <strong>mit</strong> Zeile i s , so definiert man<br />
π s := i s . (2.12)<br />
Will man dann ein Gleichungssystem <strong>mit</strong> der rechten Seite b lösen, so definiert man<br />
eine neue rechte Seite d <strong>mit</strong><br />
d i := b πi <strong>für</strong> i = 1,:::,n<br />
10
2.1 <strong>LR</strong>-<strong>Zerlegung</strong><br />
und löst da<strong>mit</strong> Ly = d. Führt man eine Zeilenpivotsuche durch, so kommen die Spaltenvertauschungen<br />
einer Vertauschung der Indizes von x gleich (äquivalente Umformung<br />
<strong>für</strong> Ax = b, da die Additionen in (2.1) kommutativ sind). Diese Vertauschungen<br />
muß man nach Berechnung der Lösung wieder rückgängig machen. Dazu merkt man<br />
sich die gemachten Permutationen in einem n-dimensionalen Vektor ρ. Vertauscht<br />
man im s-ten Schritt Spalte s <strong>mit</strong> Spalte j s , so definiert man<br />
ρ s := j s . (2.13)<br />
Die Rückwärtssubstitution führt man <strong>für</strong> einen neuen Resultatvektor z durch und erhält<br />
x dann aus der Definition<br />
x ρ j<br />
:= z j <strong>für</strong> j = 1,:::,n.<br />
Zusammenfassend sind <strong>für</strong> allgemeine Gleichungssysteme der Form (2.1) folgende<br />
Schritte notwendig, um die Lösung x <strong>mit</strong>tels <strong>LR</strong>-<strong>Zerlegung</strong> zu bestimmen.<br />
a) Bestimmung von L, R, π und ρ <strong>mit</strong>tels <strong>LR</strong>-<strong>Zerlegung</strong>, so daß<br />
A πi ρ j<br />
= (<strong>LR</strong>) i j<br />
∀i, j: i, j ∈ {1,:::,n}<br />
b) Permutation von b nach d, so daß<br />
d i := b πi<br />
∀i:i ∈ {1,:::,n}<br />
c) Bestimmung der Lösung von Ly = d <strong>mit</strong>tels Vorwärtssubstitution<br />
d) Bestimmung der Lösung von Rz = y <strong>mit</strong>tels Rückwärtssubstitution<br />
e) Permutation von z nach x, so daß<br />
x ρ j<br />
:= z j<br />
∀ j: j ∈ {1,:::,n}<br />
Die Umsetzung der <strong>LR</strong>-<strong>Zerlegung</strong> <strong>für</strong> <strong>Parallelrechner</strong> wird im nächsten Kapitel behandelt,<br />
auf die weiteren Schritte wird in Kapitel 4 eingegangen.<br />
11
2 Lösung linearer Gleichungssysteme<br />
2.2 Generischer <strong>LR</strong>-<strong>Zerlegung</strong>s-Algorithmus<br />
Der Algorithmus zur Faktorisierung einer Matrix A der Dimension n, dargestellt in<br />
Algorithmus 2.1, besteht aus einer Folge von n − 1 Eliminationsschritten. In jedem<br />
dieser Schritte wird ein Nichtnullelement a (s)<br />
i s j s<br />
der aktiven Submatrix ausgewählt, das<br />
Pivotelement. Die aktive Submatrix im s-ten Eliminationsschritt ist die (n − s + 1)-<br />
dimensionale Restmatrix von A (s) <strong>mit</strong> Elementen a (s)<br />
i j aus den Zeilen und Spalten aus<br />
denen noch kein Pivotelement ausgewählt wurde (siehe (2.5)). Man beginnt <strong>mit</strong> den<br />
Zeilen- und Spaltenindexmengen I und J <strong>mit</strong> I = J = {1,:::,n} und entfernt aus diesen<br />
sukzessive die Indizes der Pivotelemente, so daß die aktive Submatrix stets die Zeilen<br />
i <strong>mit</strong> i ∈ I und die Spalten j <strong>mit</strong> j ∈ J umfaßt.<br />
Die Vertauschungen von Zeile s <strong>mit</strong> Zeile i s und von Spalte s <strong>mit</strong> Spalte j s , die zur<br />
Auswahl des Pivotelements notwendig sind, werden nicht explizit ausgeführt, sondern<br />
nur in Permutationsvektoren π und ρ wie im vorigen Abschnitt beschrieben vermerkt.<br />
Die Vertauschungen werden zum Abschluß vorgenommen, indem man die gemerkten<br />
Vertauschungen von Zeilen und Spalten an A (n) vornimmt und so R erhält. Man<br />
errechnet deshalb auch in jedem Schritt die Werte der Elemente ¯l i js einer temporären<br />
Matrix ¯L, deren Zeilen und Spalten zum Abschluß des Algorithmus so wie in π und ρ<br />
vermerkt vertauscht werden und daraus L entsteht.<br />
Die Elemente a (s+1)<br />
i j<br />
, aus denen sich in jedem Eliminationsschritt A (s+1) ergibt, werden<br />
in der Update-loop erzeugt. Bei einer Implementierung des Algorithmus rechnet<br />
man die A (s) nicht <strong>mit</strong>, sondern führt in jedem Schritt Veränderungen (Updates) an A<br />
aus, so daß nach Ausführung der Update-loop stets A = A (s+1) gilt. Die Begriffe sind<br />
noch einmal in Abbildung 2.1 veranschaulicht.<br />
In Algorithmus 2.1 ist noch nichts darüber ausgesagt, auf welche Weise das Pivotelement<br />
bestimmt wird (Spaltenpivotsuche, Zeilenpivotsuche, . . . ). Die gewählte<br />
Vorgehensweise in diesem Punkt hat einen großen Einfluß auf die Güte des Algorithmus<br />
als Ganzes, die sich hier primär durch eine möglichst hohe Zeiteffizienz, aber<br />
auch durch eine gute Genauigkeit definiert. Um beidem gerecht zu werden, müssen<br />
beim Entwurf eines Verfahrens <strong>für</strong> die Pivotauswahl mehrere Forderungen beachtet<br />
werden.<br />
Vermeidung von Fill Zum einen will man möglichst die Dünnbesetztheit der Matrix<br />
erhalten. Da <strong>für</strong> Elemente a (s)<br />
i j s<br />
= 0 in der Pivotspalte ¯l i js = 0 ist, beschränkt man<br />
die L-loop auf die Nichtnullelemente der Pivotspalte und nutzt so die Dünnbesetztheit<br />
der aktiven Submatrix zum Zwecke der Effizienzsteigerung aus. Für Elemente a (s)<br />
i s j = 0<br />
in der Pivotzeile sowie Elemente ¯l i js = 0 aus ¯L ist a (s+1) = a (s) , weshalb die Updateloop<br />
auf Nichtnullelemente der Pivotzeile und -spalte beschränkt wird und man so<br />
ebenfalls von der Dünnbesetztheit der aktiven Submatrix profitieren kann.<br />
12
2.2 Generischer <strong>LR</strong>-<strong>Zerlegung</strong>s-Algorithmus<br />
for s = 1 to n − 1 do<br />
Wähle Pivot a (s)<br />
i s j s<br />
≠ 0 aus, <strong>mit</strong> i s ∈ I, j s ∈ J<br />
π s := i s ;<br />
ρ s := j s ;<br />
I:= In{i s };<br />
J:= Jn{j s };<br />
for all a (s)<br />
i j s<br />
≠ 0 <strong>mit</strong> i ∈ I do<br />
¯l i js := a (s)<br />
end for<br />
for all a (s)<br />
a (s+1)<br />
i j<br />
end for<br />
end for<br />
l i j := ¯l πi ρ j<br />
;<br />
i j s=a (s)<br />
i s j s<br />
;<br />
i s j ≠ 0 <strong>mit</strong> j ∈ J und ¯l i js ≠ 0 <strong>mit</strong> j ∈ J do<br />
:= a (s)<br />
i j<br />
− ¯l i js ⋅ a (s)<br />
i s j ;<br />
{Pivot-loop}<br />
{Zeilenpermutation festhalten}<br />
{Spaltenpermutation festhalten}<br />
{Pivotzeile entfernen}<br />
{Pivotspalte entfernen}<br />
{L-loop}<br />
{Update-loop}<br />
{L bilden}<br />
r i j := ā πi ρ j<br />
; {R bilden aus Ā = A (n) }<br />
Algorithmus 2.1: <strong>LR</strong>-<strong>Zerlegung</strong><br />
Es kann jedoch sein, daß in der Update-loop neue Nichtnullelemente kreiert werden,<br />
d. h. falls a (s)<br />
i j<br />
= 0 und ¯l i js ≠ 0 sowie a (s)<br />
i s j<br />
≠ 0, dann ist a(s+1) i j<br />
≠ 0. a (s+1)<br />
i j<br />
wird dann<br />
als Fill element, die Menge<br />
375<br />
aller Fill elements kurz als Fill oder Fill-in bezeichnet.<br />
Fill gilt es zu vermeiden, da er in den darauffolgenden Eliminationsschritten zusätzliche<br />
Kosten in der L- und Update-loop verursacht. Man ist hierbei auf Heuristiken<br />
angewiesen, da eine optimale Lösung nicht in vertretbarem Zeitaufwand erreichbar<br />
ist [24]. In welcher Weise die Pivotwahl den Fill beeinflußen kann, zeigt sich, wenn<br />
man die Matrix<br />
(2.14)<br />
264a 11 a 12a<br />
a 21 a 22<br />
.<br />
a n1<br />
1n<br />
. ..<br />
a nn<br />
betrachtet. Wählt man nämlich hier a 11 als Pivot, so ist ein Update <strong>für</strong> alle Elemente<br />
a i j <strong>mit</strong> i > 1, j > 1 vonnöten und die aktive Submatrix ist im nächsten Eliminationsschritt<br />
dichtbesetzt, was zu einem erheblichen Mehraufwand bei der weiteren Faktorisierung<br />
führt. Wählt man dagegen z. B. a nn als Pivot, so ergibt sich ein Update<br />
lediglich <strong>für</strong> a 11 und die dünnbesetzte Struktur der Matrix bleibt erhalten.<br />
Eine Heuristik zur Auswahl eines Pivotelementes <strong>mit</strong> dem Ziel der Erhaltung der<br />
Dünnbesetztheit der Matrix ist die Markowitz Strategie [14]. Die Wahl eines Elements<br />
13
2 Lösung linearer Gleichungssysteme<br />
aktive<br />
Submatrix<br />
Pivotspalte<br />
ρ s := j s<br />
Pivot<br />
Pivotzeile<br />
π s := i s<br />
a (s)<br />
i s j s<br />
a (s)<br />
i s j<br />
I<br />
¯l i js<br />
a (s)<br />
i j<br />
Update<br />
J<br />
Abbildung 2.1: Eliminationsschritt<br />
a i j führt dazu, daß höchstens<br />
M i j = (r i − 1)(c j − 1) (2.15)<br />
neue Nichtnullelemente geschaffen werden, wobei r i bzw. c j die Anzahl der Nichtnullelemente<br />
in Zeile i bzw. Spalte j darstellen. M i j wird dabei die Markowitz-Zahl<br />
von a i j genannt. Das Bestreben ist es also, ein solches Element als Pivot zu wählen,<br />
das die geringstmögliche Markowitz-Zahl aufweist.<br />
Numerische Stabilität Zum anderen will die numerische Stabilität gewahrt bleiben,<br />
d. h. die Lösung eines linearen Gleichungssystems <strong>mit</strong> den Faktoren <strong>LR</strong> der Koeffizientenmatrix<br />
soll möglichst genaue Resultate liefern. Dazu könnte man die Spalten-<br />
14
2.2 Generischer <strong>LR</strong>-<strong>Zerlegung</strong>s-Algorithmus<br />
und/oder Zeilenpivotsuche einsetzen und stets nur das betragsmäßig größte Element<br />
einer Zeile oder Spalte als Pivot akzeptieren, wie in 2.1 beschrieben. Dies würde jedoch<br />
die Menge der möglichen Pivots zu sehr einschränken, da man ja auch den Fill<br />
möglichst gering halten will. Deshalb wählt man den flexibleren Ansatz des threshold<br />
pivoting (etwa “Schwellwertpivotsuche”) [9], wobei man nur Nichtnullelemente a i j<br />
als potentielle Pivots zuläßt, die die threshold condition (“Schwellwertbedingung”)<br />
erfüllen, die definiert ist als<br />
|a i j | ≥ u ⋅ max<br />
c∈J |a ic|, (2.16)<br />
wobei 0 ≤ u ≤ 1 ein Programmparameter ist. Solche Elemente heißen im weiteren<br />
akzeptabel.<br />
Auswahl des Pivotelements Der in dieser Arbeit eingesetzte Algorithmus zur Auswahl<br />
eines Pivotelements, dargestellt in Algorithmus 2.2, setzt Threshold pivoting zur<br />
Sicherung der numerischen Stabilität sowie eine Variante der Markowitz Strategie zur<br />
Reduktion des Fill ein [25, 23]. Es wird dabei ein akzeptables Element der aktiven<br />
Submatrix gesucht, dessen Markowitz-Zahl möglichst klein ist.<br />
Analog zu Algorithmus 2.1 werden Mengen I′ und J′ von Zeilenindizes bzw. Spaltenindizes<br />
definiert, die zunächst alle Indizes der Zeilen bzw. Spalten der aktiven Submatrix<br />
enthalten. Diese werden sukzessive um die Indizes von bereits nach möglichen<br />
Pivots untersuchten Zeilen bzw. Spalten verkleinert. Da die Suche nach Pivots in allen<br />
Zeilen und Spalten zu aufwendig wäre, beschränkt man die Suche auf P Zeilen oder<br />
Spalten, d. h. es wird nacheinander jeweils die Zeile oder Spalte ausgewählt, die die<br />
geringste Anzahl an Nichtnullelementen hat und darin nach akzeptablen Pivotkandidaten<br />
gesucht. Dies wird fortgesetzt bis insgesamt P Zeilen und Spalten untersucht<br />
wurden.<br />
Bei Versuchen <strong>mit</strong> dem im Rahmen dieser Arbeit entstandenen Programm ergab<br />
sich P = 4 als Wert, der konsistent gute Ergebnisse liefert, die durchschnittlich am<br />
wenigsten vom experimentellen Optimum abweichen, so daß P = 4 auch als default-<br />
Wert gewählt wurde.<br />
Es ist jedoch möglich, daß auch nach P Schritten kein Kandidat gefunden wurde,<br />
der die Schwellwertbedingung (2.16) erfüllt, da diese eine zeilenweise Bedingung<br />
ist und u. U. ausschließlich Spalten untersucht wurden. In diesem Fall wird einfach<br />
solange in den nächstdünnbesetzten Zeilen oder Spalten weitergesucht, bis ein akzeptabler<br />
Kandidat gefunden wurde.<br />
Andererseits ist es auch möglich, daß während der Suche ein optimaler Kandidat<br />
gefunden wird, d. h. ein solcher, dessen Markowitz-Zahl minimal bezüglich der akti-<br />
15
2 Lösung linearer Gleichungssysteme<br />
ven Submatrix ist. In diesem Fall wird die Suche abgebrochen und das Element sofort<br />
ausgewählt.<br />
I′:= I;<br />
J′:= J;<br />
n:= 1;<br />
best:= ⊥;<br />
M best := −1;<br />
repeat<br />
Wähle i ∈ I′ oder j ∈ J′ <strong>mit</strong> minimaler Anzahl von Nichtnullelementen<br />
for all a i j ≠ 0 in dieser Zeile oder Spalte do<br />
if a i j akzeptabel? then<br />
if M i j minimal? then<br />
Wähle a i j als Pivot aus<br />
else if best = ⊥ ∨ M i j < M best then<br />
best:= a i j ;<br />
M best := M i j ;<br />
end if<br />
end if<br />
end for<br />
I′:= I′n{i}; oder J′:= J′n{ j};<br />
n:= n + 1;<br />
until n > p ∧ best ≠ ⊥;<br />
Wähle best als Pivot aus<br />
Algorithmus 2.2: Auswahl eines Pivotelements<br />
16
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
Um bestimmte Designentscheidungen nachvollziehbar zu machen, soll in diesem Kapitel<br />
zunächst eine Klärung der dem Programm zugrundeliegenden Bedingungen in<br />
Form von Hard- und Software erfolgen. Anschließend soll, ausgehend von dem in<br />
Kapitel 2 beschriebenen sequentiellen Algorithmus zur <strong>LR</strong>-<strong>Zerlegung</strong> und den unten<br />
dargestellten Voraussetzungen, die Entwicklung eines parallelen Algorithmus erläutert<br />
werden. Im letzten Abschnitt dieses Kapitels werden die <strong>mit</strong> diesem Algorithmus<br />
erzielten Ergebnisse diskutiert.<br />
3.1 Der massiv-parallele Rechner Cray T3D<br />
Der T3D [16] ist ein MIMD (Multiple Instruction Multiple Data) Rechner <strong>mit</strong> verteiltem<br />
Speicher (distributed memory), der aber logisch global addressierbar ist. Das<br />
bedeutet, daß jeder Prozessor eine separate Folge von Instruktionen auf einem eigenen<br />
lokalen Speicher ausführt.<br />
Dies unterscheidet ihn von Vertretern anderer <strong>Parallelrechner</strong>modelle, wie SIMD<br />
(Single Instruction Multiple Data), bei dem alle Prozessoren von einem einzigen Instruktionsstrom<br />
gesteuert auf verschiedenen Daten operieren, sowie von Multiprozessor-Rechnern<br />
(oder MIMD-Rechnern <strong>mit</strong> gemeinsamem Speicher), bei denen nur ein<br />
physikalischer Speicher <strong>für</strong> alle Prozessoren zu Verfügung steht.<br />
Von vielen distributed-memory MIMD Rechnern unterscheidet sich der T3D außerdem<br />
dadurch, daß er über einen globalen Adreßraum verfügt, weshalb Zugriffe auf<br />
den Speicher verschiedener Prozessoren direkt möglich sind, was bei anderen Rechnern<br />
häufig nur <strong>mit</strong>tels Message Passing geschehen kann, bei dem der Zugriff nicht<br />
vom Empfänger initiiert werden kann und kein asynchrones Empfangen möglich ist.<br />
Diese Eigenschaft des T3D spielt bei dem <strong>für</strong> diese Arbeit entstandenen Programm<br />
insofern eine wichtige Rolle, als daß die eingesetzten Kommunikationsroutinen davon<br />
Gebrauch machen und so ein effizienter asynchroner Datenaustausch möglich wird.<br />
Abbildung 3.1 veranschaulicht die Klassifizierung noch einmal.<br />
Eine CPU und der dazugehörige lokale Speicher bilden beim T3D ein processor<br />
17
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
element (PE). Die CPU ist ein superskalarer 64bit-RISC-Prozessor vom Typ DEC Alpha,<br />
der <strong>mit</strong> 150 MHz getaktet ist. Jedes PE besitzt einen Speicher von 64 MB. Im<br />
Cray T3D ist die Netzwerktopologie ein dreidimensionaler Torus. Die Datenkanäle<br />
sind bidirektional und arbeiten in x-, y- und z-Richtung unabhängig voneinander.<br />
Der Zielrechner cray des Konrad-Zuse-Zentrums Berlin verfügt über 256 PEs, die in<br />
einem 4 × 4 × 8 Torus angeordnet sind, wobei 2 PEs jeweils zu einem Rechenknoten<br />
zusammengeschlossen sind. Mehrere PEs bilden eine Partition, deren Größe stets eine<br />
Potenz von 2 sein muß. Die maximale Transferrate beim Verschicken von Daten von<br />
einem PE zu einem anderen beträgt ungefähr 100 MB/s bei einer Latenzzeit (Startup-Zeit,<br />
d. h. Kommunikationszeit, die unabhängig vom Datenvolumen benötigt wird)<br />
von unter 1µs.<br />
3.2 Paralleles Programmiermodell<br />
Obwohl der T3D ein MIMD-Rechner ist, wird jedoch ein Single Program Multiple<br />
Data (SPMD) Programmiermodell implementiert, das eine leichte Abschwächung<br />
des MIMD-Ansatzes <strong>mit</strong> sich bringt, d. h. jedes PE führt dasselbe Programm aus,<br />
operiert jedoch auf unterschiedlichen Daten. Die einzelnen PEs sind durch logische<br />
PE-Nummern von 0 bis p − 1 identifizierbar, wobei p die Anzahl der PEs darstellt.<br />
Um eine maximale Ausnutzung der Hardware des T3D sowie eine möglichst hohe<br />
Nebenläufigkeit zu erzielen, wurde ein kontrollparalleles Programmiermodell unter<br />
Benutzung der SHMEM-Bibliothek [3] anderen Ansätzen vorgezogen.<br />
Im Gegensatz zu einem kontrollparallelen Programmiermodell nutzt ein datenparalleles<br />
nur die Nebenläufigkeit, die aus der Verteilung von Daten resultiert. Dies<br />
geschieht meist durch Programmierung in einer speziellen datenparallelen Sprache,<br />
deren Compiler dann den Kommunikationscode automatisch generiert. Dies ist natürlich<br />
wesentlich weniger fehleranfällig und leichter zu entwerfen als handgeschriebener<br />
(kontrollparalleler) Code.<br />
Auch an kontrollparallelen Programmiermodellen gibt es eine gewisse Auswahl.<br />
Ein weit verbreitetes ist Message Passing, wobei eine logische Unterteilung des Programms<br />
in tasks vorgenommen wird, die Nachrichten (Messages) über channels kommunizieren.<br />
Solche Modelle sind häufig sehr portabel (z. B. die MPI Bibliothek [15]),<br />
stellen jedoch nur eine geringfügig höhere Abstraktion als die hardwarespezifischen<br />
Kommunikationsroutinen (wie z. B. SHMEM) dar, weshalb sie gerne <strong>für</strong> general purpose<br />
Software oder ad-hoc Lösungen eingesetzt werden. Aufgrund ihrer unspezifischen<br />
Ausrichtung sind sie jedoch oftmals langsamer als hardwarenähere Modelle.<br />
Für die SHMEM-Routinen wird z. B. eine Latenzzeit von unter 1µs angegeben, <strong>für</strong> die<br />
Message Passing Routinen (PVM) dagegen 2–10µs (das sind bis zu 1500 Taktzyklen).<br />
18
3.3 Eingesetzte Funktionen und Klassen<br />
Verbindungsnetzwerk<br />
Memory Memory Memory<br />
CPU CPU CPU<br />
SIMD Rechner<br />
Instruktionsfolge<br />
Verbindungsnetzwerk<br />
Memory Memory Memory<br />
CPU CPU CPU<br />
Adreßraum<br />
Cray T3D<br />
Speicherorganisation<br />
Verbindungsnetzwerk<br />
CPU CPU CPU<br />
Memory<br />
Memory Memory Memory<br />
MIMD <strong>mit</strong> lokalem Adreßraum<br />
CPU CPU CPU<br />
Shared-memory MIMD Rechner<br />
Abbildung 3.1: Verschiedene Modelle von <strong>Parallelrechner</strong>n<br />
3.3 Eingesetzte Funktionen und Klassen<br />
Neben den unten beschriebenen Klassen und Funktionen wurde ein TCL-Skript, das<br />
zu Beginn entstand, erfolgreich eingesetzt, welches den Output der einzelnen PEs in<br />
verschiedenen Fenstern anzeigt.<br />
19
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
Die SHMEM Bibliothek Die SHMEM-Bibliothek [3] stellt Funtionen zur Kommunikation<br />
von Daten sowie zur Synchronisation bereit. Die wichtigsten Funktionen<br />
sind:<br />
shmem_get(long *target, long *source, int nlong, int pe);<br />
shmem_put(long *target, long *source, int nlong, int pe);<br />
wobei<br />
target Zieladresse (lokal bei shmem_get, entfernt 1 bei shmem_put)<br />
source Quelladresse (entfernt bei shmem_get, lokal bei shmem_put)<br />
nlong Anzahl der 64bit-Worte, die übertragen werden sollen<br />
pe logische Nummer des PE, <strong>mit</strong> dem kommuniziert werden soll<br />
shmem_put schreibt nlong 64bit-Worte von source des aufrufenden PE in den Speicher<br />
des PE, das durch pe identifiziert ist, an die Adresse target. Das Schreiben<br />
erfolgt asynchron, das entfernte PE wird nicht von der Operation informiert.<br />
shmem_get liest nlong 64bit-Worte aus dem Speicher des PE, das durch pe identifiziert<br />
ist, von der Adresse source und schreibt sie in den Speicher des aufrufenden<br />
PE an die Adresse target.<br />
shmem_put ist dabei wesentlicher schneller als shmem_get, so daß, wann immer<br />
möglich, erstere Routine eingesetzt werden sollte.<br />
Beim T3D wird bei remote writes (shmem_put) nicht auf die Kohärenz des Cache<br />
geachtet, so daß u. U. veraltete Daten aus dem Cache gelesen werden. Es hat<br />
sich deshalb als angemessen erwiesen, das remote write invalidate bit zu Beginn des<br />
Programms zu setzen und nicht wieder zu löschen. Dadurch wird bei jedem Remote<br />
write automatisch die betroffene Cache line ungültig.<br />
Die wichtigste Funktion zur Synchronisation von PEs ist der barrier-Mechanismus,<br />
der eine Fortführung des Programms erst bei Erreichen aller PEs des Synchronisationspunktes<br />
(Barrier) ermöglicht. Die SHMEM-Bibliothek stellt hierzu eine Funktion<br />
shmem_barrier zur Verfügung. Daneben ist eine zweiseitige Synchronisation<br />
über ein spin-wait auf eine Adresse, an die ein anderes PE schreibt, einfach zu realisieren.<br />
1 d. h. zur Laufzeit nicht lokal.<br />
20
3.3 Eingesetzte Funktionen und Klassen<br />
Die Klasse DistrObj Die Klasse DistrObj (“distributed objects”) [22] stellt<br />
Kommunikations-, Synchronisations- sowie Reduktionsmethoden bereit, die eine Kooperation<br />
der auf verschiedenen PEs kreierten Objekte der Klasse (oder typischerweise<br />
einer davon abgeleiteten) ermöglichen. Weiterhin ist das Unterteilen der Menge<br />
von beteiligten PEs möglich, so daß eine problemorientierte Aufteilung der Prozessoren<br />
gewährleistet wird, indem die gleichförmige Sicht auf einen “kleineren” <strong>Parallelrechner</strong><br />
angeboten wird (d. h. Numerierung von 0 bis ¯p − 1, wobei ¯p = 2 i ≤ p).<br />
DistrObj stellt seine Methoden auch als globale Funktionen zur Verfügung, so daß<br />
das Programm zu Beginn als großes DistrObj verstanden werden kann, das alle PEs<br />
der zur Verfügung stehenden Partition umfaßt. Hiervon können dann <strong>mit</strong>tels der Funktion<br />
partition kleinere DistrObjs kreiert werden. Diese wird folgendermaßen eingesetzt:<br />
DistrObj partition(int first, int stride, int size) const;<br />
wobei<br />
first Nummer des ersten PE des neuen DistrObj<br />
stride 2 stride ist die Schrittweite der PE-Nummern<br />
size Anzahl der PEs im neuen DistrObj<br />
Die wichtigste Kommunikationsform der Klasse DistrObj wird <strong>mit</strong>tels der Methode<br />
gossip bereitgestellt. Unter gossipping (“tratschen”) versteht man das kollektive<br />
Austauschen von verteilten Daten, die <strong>mit</strong>tels einer bestimmten Operation zusammengeführt<br />
werden und nach Beendigung des Gossips allen PEs zur Verfügung stehen.<br />
Die Kommunikationsstruktur der gossip-Methode der Klasse DistrObj ist ein<br />
Hypercube [11, Kapitel 11]. Es werden also log 2 p Schritte zum Zusammenführen<br />
aller Daten benötigt. Zum Austauschen der Daten werden Routinen der SHMEM-<br />
Bibliothek genutzt. Abbildung 3.2 veranschaulicht dies noch einmal <strong>für</strong> eine PE-<br />
Anzahl von 8. Die Methode gossip sieht konkret folgendermaßen aus:<br />
void gossip(MsgBuffer &data, MsgBuffer &work,<br />
GossipFunction gf);<br />
wobei<br />
data lokaler Teil der verteilten Daten<br />
work Arbeitsbereich, in den entfernte PEs ihre Daten schreiben<br />
21
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
6 7<br />
2 3<br />
Schritt 2<br />
4 5<br />
Schritt 3<br />
0 1<br />
Schritt 1<br />
Abbildung 3.2: Kommunikationsschritte der Methode gossip<br />
gf Funktion zum Zusammenführen der Daten<br />
Eine GossipFunction hat folgendes aussehen:<br />
typedef void (DistrObj::*GossipFunction)(int stage,<br />
MsgBuffer &local,<br />
MsgBuffer &received);<br />
In local werden die <strong>mit</strong>tels der spezifizierten Funktion aus den lokalen Daten local<br />
und den empfangenen received zusammengeführten Informationen wieder abgelegt<br />
und im nächsten Schritt (stage+1) an ein weiteres PE gesandt.<br />
Weitere benutzte Klassen Neben den oben umrissenen Klassen bzw. Funktionsbibliotheken<br />
existieren noch weitere Klassen und Funktionen, die den Umgang <strong>mit</strong><br />
dünnbesetzten <strong>Matrizen</strong> erleichtern. Diese entstammen allesamt der Klassenbibliothek,<br />
die im Rahmen von [22] entstand. Besonders wichtig sind im Zusammenhang<br />
<strong>mit</strong> der vorliegenden Arbeit die Klassen SVector (“sparse vector”) und SVSet (“sparse<br />
vector set”). Ein SVector beschreibt einen dünnbesetzten Vektor, dessen Elemente<br />
je einen Index sowie einen numerischen Wert besitzen. Ein SVSet ermöglicht<br />
es, die Elemente einer Anzahl von SVectors in einem großen zusammenhängenden<br />
Speicherblock zusammenzufassen, die jeweils über einen Index erreichbar sind, so<br />
daß man so<strong>mit</strong> eine dünnbesetzte Matrix beschreiben kann. Der Speicherblock kann<br />
22
3.4 Der parallele Algorithmus<br />
dynamisch vergrößert werden und so neue Elemente sowie Vektoren hinzugefügt werden.<br />
Weiterhin stehen noch die Klassen Array sowie DataArray zur Verfügung, die<br />
beide dynamische Arrays implementieren, wobei Array zur Speicherverwaltung die<br />
C++-Operatoren new und delete verwendet, also leicht ineffizienter ist als die nicht<br />
C++-konforme Klasse DataArray, welche realloc() verwendet.<br />
3.4 Der parallele Algorithmus<br />
Das Design paralleler Algorithmen ist kein Prozeß, der sich an simplen Rezepten orientiert.<br />
Es verlangt vielmehr einen großen Anteil kreativen Denkens, kann jedoch von<br />
einer methodischen Herangehensweise profitieren, die den Entscheidungsfindungsprozeß<br />
gliedert. Die Entscheidungen, die während der Entwicklung des parallelen<br />
Algorithmus dieser Arbeit getroffen werden mußten, sollen unter Verwendung der<br />
PCAM-Methode [11, Kapitel 2] dokumentiert werden, die im ersten Unterabschnitt<br />
vorgestellt wird. Dies wird jedoch nur bis zu einem Grade durchgeführt, der auf<br />
diesem Abstraktionsniveau sinnvoll erscheint. Es bleiben also noch wesentliche Designentscheidungen<br />
offen, die im nächsten Abschnitt diskutiert werden.<br />
3.4.1 Die PCAM-Methode<br />
Der Name der PCAM-Methode rührt von den Anfangsbuchstaben ihrer 4 Einzelschritte<br />
her:<br />
1. Partitionierung (“partitioning”). Die Berechnung und die Daten, auf denen<br />
während dieser Berechnung operiert wird, werden möglichst fein unterteilt.<br />
Praktische Aspekte wie die Zahl der Prozessoren werden ignoriert, stattdessen<br />
konzentriert man sich auf die Erkennung potentieller Möglichkeiten der parallelen<br />
Ausführung.<br />
2. Kommunikation (“communication”). Die zur Koordination der Teilberechnungen<br />
nötige Kommunikation wird bestimmt und angemessene Kommunikationsstrukturen<br />
sowie Algorithmen definiert.<br />
3. Agglomeration (“agglomeration”). In diesem Schritt werden Teilberechnungen<br />
zu größeren zusammengefaßt, indem man auf praktische Gesichtspunkte eingeht<br />
<strong>mit</strong> dem Ziel einfacherer Implementation und erhöhter Performance durch<br />
weniger Kommunikation.<br />
23
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
4. Abbildung (“mapping”). Jede Teilberechnung wird einem Prozessor zugewiesen.<br />
Die Abbildung kann statisch geschehen oder zur Laufzeit durch Loadbalancing<br />
Algorithmen bestimmt werden.<br />
Für jede der einzelnen Phasen gibt es verschiedene Herangehensweisen. Bei der<br />
Partitionierung z. B. unterscheidet man zwischen domain decomposition sowie functional<br />
decomposition. Erstere Technik konzentriert sich zunächst auf die Unterteilung<br />
der an der Berechnung beteiligten Daten und anschließend auf die Zuordnung der Berechnung<br />
zu den Daten. Bei der Functional decomposition geht man komplementär<br />
vor, d. h. man unterteilt zunächst die Berechnungsschritte und kümmert sich dann um<br />
die da<strong>mit</strong> verbundenen Daten.<br />
Die zwischen den einzelnen Teilaufgaben (Tasks) notwendigen Kommunikationsstrukturen<br />
kann man wie folgt kategorisieren:<br />
• Bei lokaler Kommunikation kommuniziert ein Task nur <strong>mit</strong> einer kleinen Menge<br />
von anderen Tasks, bei globaler Kommunikation <strong>mit</strong> vielen anderen Tasks.<br />
• Bei strukturierter Kommunikation sind die einzelnen Tasks in einer regulären<br />
Struktur angeordnet, z. B. einem Baum, bei unstrukturierter in einem beliebigen<br />
Graph.<br />
• Bei statischer Kommunikation ändert sich die Identität der Kommunikationspartner<br />
in Form von Tasks nicht während der Laufzeit, bei dynamischer Kommunikation<br />
u. U. schon.<br />
• Bei synchroner Kommunikation kooperieren sendende und empfangende Tasks<br />
<strong>mit</strong>einander, bei asynchroner Kommunikation finden Kommunikationsvorgänge<br />
ohne Beteiligung eines der beiden Partner statt. Dabei muß zwischen der<br />
Sende- und der Empfangsphase unterschieden werden. Beide können unabhängig<br />
voneinander entweder synchron oder asynchron sein.<br />
In den ersten beiden Phasen wird eine i. allg. zu feinkörnige Aufteilung der Berechnung<br />
vorgenommen. Durch Erhöhung der Granularität (Zusammenfassung von<br />
Teilberechnungen) versucht man deshalb in der Agglomerationsphase, eine adäquatere<br />
Anpassung an den geplanten Einsatzbereich (sprich die zur Verfügung stehende<br />
Hardware) zu erreichen. Dabei muß aber auch berücksichtigt werden, daß die da<strong>mit</strong><br />
erzielte Granularität nicht wieder zu groß ist, so daß dies der Skalierbarkeit, d. h.<br />
der Ausnutzung von mehr Nebenläufigkeit bei Einsatz von mehr PEs, im Wege steht.<br />
Entwickelt man beispielsweise auf 4 PEs und agglomeriert die Berechnung in ebensoviele<br />
Teilberechnungen, so bringt dies evtl. eine gute Performancesteigerung; plant<br />
24
3.4 Der parallele Algorithmus<br />
man jedoch dann den Einsatz bei z. B. 128 PEs, so wird ein Großteil des Rechenpotentials<br />
ungenutzt bleiben.<br />
In der Abbildungsphase versucht man, die Parallelität durch Plazierung nebenläufiger<br />
Tasks auf verschiedenen Prozessoren zu erhöhen sowie Lokalität durch Plazierung<br />
häufig kommunizierender Tasks auf einem Prozessor zu verbessern.<br />
3.4.2 Partitionierung<br />
Bei der Partitionierung bietet sich die Domain decomposition an. Gemäß der Forderung,<br />
möglichst viel Parallelismus auszunutzen, wird die zu faktorisierende Matrix<br />
vollständig aufgeteilt, so daß ein Task genau ein Element der Matrix bearbeitet. Betrachtet<br />
man Algorithmus 2.1, so fällt auf, daß sich dadurch beide inneren Schleifen,<br />
die L- sowie die Update-loop parallelisieren lassen. Weiterhin ermöglicht diese Aufteilung<br />
die Parallelisierung beider Schleifen von Algorithmus 2.2, der Pivotauswahl.<br />
Dies läßt von allen Schleifen des Algorithmus lediglich die äußerste, die Pivotloop,<br />
als nicht parallelisierbar aufgrund der Partitionierung anhand der Daten übrig.<br />
Diese Schleife ist inhärent sequentiell, da die aktive Submatrix im Schritt s von den<br />
Updates in Schritt s − 1 abhängt. Im Worst case, d. h. komplett dichte Pivotzeile und<br />
-spalte in Schritt s−1, verändern sich alle Elemente der in Schritt s aktiven Submatrix<br />
in der Update-loop von Schritt s − 1. Für dünnbesetzte <strong>Matrizen</strong> ist dies jedoch selten<br />
der Fall. Es ist deshalb sehr wohl möglich, mehrere Schritte der Pivot-loop zu einem<br />
parallelen zusammenzufassen, wenn kompatible (oder auch unabhängige) Elemente<br />
als Pivots ausgewählt werden. Zwei Elemente a i j und a kl heißen kompatibel und<br />
können simultan eliminiert werden, falls gilt<br />
a il = a k j = 0. (3.1)<br />
Die Elemente a i j und a kl der Matrix<br />
264a i<br />
. .<br />
kl375<br />
j0 0a kl375 264a i j0<br />
a k ja<br />
sind also kompatibel, die der Matrix<br />
.<br />
.<br />
25
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
hingegen nicht.<br />
Wählt man bei der Durchführung von Algorithmus 2.1 a (s)<br />
i s j s<br />
als Pivotelement, das<br />
zu a (s)<br />
k s l s<br />
kompatibel sei, so ergeben sich keine Updates <strong>für</strong> a (s)<br />
k s j<br />
, d. h.<br />
¯l ks j s<br />
= a (s)<br />
k s j s=a (s)<br />
i s j s<br />
= 0 ⇒ a (s+1)<br />
k s j<br />
= a (s)<br />
k s j . (3.2)<br />
Des weiteren ergeben sich keine Updates <strong>für</strong> a (s)<br />
il s<br />
, d. h.<br />
a (s)<br />
i s l s<br />
= 0 ⇒ a (s+1)<br />
il s<br />
= a (s)<br />
il s<br />
. (3.3)<br />
Wählt man stattdessen a (s)<br />
k s l s<br />
als Pivotelement, so entstehen keine Updates <strong>für</strong> a (s)<br />
i s j<br />
, d. h.<br />
¯l is l s<br />
= a (s)<br />
i s l s=a (s)<br />
k s l s<br />
= 0 ⇒ a (s+1)<br />
i s j = a (s)<br />
i s j . (3.4)<br />
Außerdem entstehen keine Updates <strong>für</strong> a (s)<br />
i j s<br />
, d. h.<br />
a (s)<br />
k s j s<br />
= 0 ⇒ a (s+1)<br />
i j s<br />
= a (s)<br />
i j s<br />
. (3.5)<br />
Wählt man beide Elemente als Pivots aus und führt zuerst die L-loop <strong>für</strong> a (s)<br />
i s j s<br />
aus und<br />
anschließend <strong>für</strong> a (s)<br />
k s j s<br />
, dann ergibt sich folgendes:<br />
¯l i js := a (s)<br />
i j s=a (s)<br />
i s j s<br />
¯l ils := a (s+1)<br />
il s=a (s+1)<br />
k s l s<br />
.<br />
Wegen (3.3) ergibt sich<br />
¯l ils := a (s)<br />
il s=a (s)<br />
k s l s<br />
.<br />
Führt man hingegen die L-loop zuerst <strong>für</strong> a (s)<br />
k s l s<br />
und anschließend <strong>für</strong> a (s)<br />
i s j s<br />
aus, so ergibt<br />
sich folgendes:<br />
¯l ils := a (s)<br />
il s=a (s)<br />
k s l s<br />
¯l i js := a (s+1)<br />
i j s=a (s+1)<br />
i s j s<br />
.<br />
26
3.4 Der parallele Algorithmus<br />
Wegen (3.5) folgt<br />
¯l i js := a (s)<br />
i j s=a (s)<br />
i s j s<br />
,<br />
also dasselbe Ergebnis wie bei umgekehrter Ausführungsreihenfolge. Führt man zuerst<br />
die Update-loop <strong>für</strong> a (s)<br />
i s j s<br />
aus und dann <strong>für</strong> a (s)<br />
k s l s<br />
, so ergibt sich folgendes:<br />
a (s+1)<br />
i j := a (s)<br />
i j − ¯l i js ⋅ a (s)<br />
i s j<br />
a (s+2)<br />
i j<br />
:= a (s+1)<br />
i j<br />
Wegen (3.2) folgt<br />
a (s+2)<br />
i j<br />
− ¯l ils ⋅ a (s+1)<br />
k s j<br />
= a (s)<br />
i j<br />
− ¯l i js ⋅ a (s)<br />
i s j − ¯l ils ⋅ a (s)<br />
k s j .<br />
= a (s)<br />
i j − ¯l i js ⋅ a (s)<br />
i s j − ¯l ils ⋅ a (s+1)<br />
k s j<br />
.<br />
Führt man die Update-loop in umgekehrter Reihenfolge aus, so ergibt sich<br />
a (s+1)<br />
i j<br />
:= a (s)<br />
i j<br />
− ¯l ils ⋅ a (s)<br />
k s j<br />
a (s+2)<br />
i j<br />
:= a (s+1)<br />
i j<br />
und wegen (3.4)<br />
− ¯l i js ⋅ a (s+1)<br />
i s j<br />
= a (s)<br />
i j<br />
− ¯l ils ⋅ a (s)<br />
k s j − ¯l i js ⋅ a (s+1)<br />
i s j<br />
a (s+2)<br />
i j = a (s)<br />
i j − ¯l i js ⋅ a (s)<br />
i s j − ¯l ils ⋅ a (s)<br />
k s j .<br />
Aus diesem Grund ist die Update-Operation bei kompatiblen Pivots kommutativ, d. h.<br />
die aus a i j resultierenden Updates können vor oder nach den Updates von a kl ausgeführt<br />
werden, ohne daß sich an der aktiven Submatrix des nächsten Eliminationsschrittes<br />
etwas ändert (bis auf die<br />
375<br />
unvermeidlichen Rundungsfehler). Kompatibilität<br />
läßt sich durch Induktion auf eine Anzahl von Pivots m erweitern, die dann (im permutierten<br />
Zustand) eine m-dimensionale Diagonalmatrix der Form<br />
<br />
0<br />
.<br />
0 a i2 j ..<br />
2<br />
.<br />
.<br />
. .. . .. 0<br />
0 0 a im j m<br />
.<br />
.<br />
..<br />
264a i1 j 1<br />
0<br />
bilden. Um maximale Parallelität zu nutzen, wird zunächst gefordert, möglichst viele<br />
kompatible Pivots in einem Schritt zu eliminieren.<br />
27
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
3.4.3 Kommunikation<br />
Ein hoher Kommunikationsaufwand ist <strong>für</strong> die Phase der Pivotauswahl vonnöten.<br />
Um Algorithmus 2.2 ausführen zu können, ist es einerseits nötig, die Markowitz-<br />
Zahlen sowie das <strong>für</strong> die Überprüfung des Threshold-Kriteriums notwendige Maximalelement<br />
zu bestimmen. Dies erfordert stets eine globale, synchrone Kommunikation,<br />
da die hierzu erforderlichen Daten (die r i und c j aus (2.15) sowie das “Wissen”<br />
über das Maximalelement) auf viele Tasks verteilt sind und von vielen benötigt<br />
werden. Aus diesem Grund bietet sich hier der Einsatz der kooperativen Methode<br />
gossip an, <strong>mit</strong>tels derer dieser Kommunikationsvorgang effizient gestaltet werden<br />
kann. In jedem Eliminationsschritt ändern sich jedoch einige, aufgrund der dünnbesetzten<br />
Struktur häufig nur wenige der Daten durch Verkleinerung der aktiven Submatrix,<br />
Erzeugung von Fill-in sowie Änderung der Werte der Nichtnullelemente durch<br />
Updates, weshalb man das Kommunikationsvolumen gering hält, indem man nur Änderungen<br />
an den Faktoren der Markowitz-Zahlen (d. h. den rowcounts und columncounts)<br />
sowie geänderte Maximalelemente überträgt.<br />
Da mehrere kompatible Pivots in einem Schritt eliminiert werden sollen, ist es<br />
nötig, Informationen über die Kompatibilität bzw. Inkompatibilität mehrerer Elemente<br />
zu erlangen. Dies ist nicht anders zu bewerkstelligen, als die Elemente, die eine<br />
Inkompatibilität verursachen könnten, zu überprüfen. Für zwei Elemente a i j und a kl<br />
bedeutet dies, daß die Elemente a il und a k j auf den Wert 0 hin getestet werden müssen.<br />
Erst wenn dies geschehen ist, ist erwiesen (oder widerlegt), daß die Elemente a i j<br />
und a kl kompatibel sind. Deshalb muß hier eine globale, synchrone Kommunikation<br />
stattfinden, an der alle Tasks beteiligt sind, deren Elemente auf gegenseitige Kompatibilität<br />
überprüft werden sollen. Für m Elemente bedeutet dies einen zeitlichen<br />
Aufwand vonO (m 2 ). Auch hier kann die Methode gossip zur effizienten Kommunikation<br />
eingesetzt werden.<br />
Betrachtet man Algorithmus 2.1, so erkennt man, daß zur Durchführung der<br />
Update-loop beim Update von Element a i j der Wert des Elementes ¯l i js sowie der<br />
des Elementes a is j benötigt wird, die, ausgehend von der im Partitionierungsschritt<br />
gewählten Struktur, nicht auf dem Task von Element a i j residieren. Aus diesem<br />
Grund ist hier eine Kommunikation nötig, deren Struktur asynchron ist, da die Tasks,<br />
denen die benötigten Elemente zugeordnet sind, nicht an dem Kommunikationsvorgang<br />
beteiligt werden müssen, weshalb sich die direkte Verwendung von Routinen<br />
der SHMEM-Bibliothek anbietet.<br />
Geht man von sehr dünnbesetzten <strong>Matrizen</strong> aus, wie sie in der linearen Programmierung<br />
vorkommen, so läßt sich der Kommunikationsaufwand erheblich reduzieren,<br />
wenn man Zeilen und Spalten <strong>mit</strong> nur einem Nichtnullelement gesondert behandelt.<br />
Solche Elemente heißen singletons. Für Einzelelemente in Zeilen (row singletons)<br />
28
3.4 Der parallele Algorithmus<br />
muß lediglich die L-loop durchgeführt werden (potentielle Nichtnullelemente in der<br />
Spalte), jedoch nicht die Update-loop, da alle a is j = 0 <strong>mit</strong> j ≠ j s . Für Einzelelemente in<br />
Spalten (column singletons) entfallen sowohl L- als auch Update-loop, da alle ¯l i js = 0<br />
<strong>mit</strong> i ≠ i s . Singletons sind generell kompatibel, da keine Updates entstehen. Natürlich<br />
würden diese Elemente auch bei einer “normalen” Pivotauswahl bevorzugt behandelt<br />
(M i j = 0), jedoch würde ein unnötig hoher Kommunikations- und Rechenaufwand entstehen,<br />
der durch die gesonderte Behandlung vor der “eigentlichen” Faktorisierung<br />
vermieden werden soll.<br />
3.4.4 Agglomeration und Abbildung<br />
Zu Zwecken der Agglomeration böte es sich an, Elemente einer Zeile oder Spalte zusammenzufassen,<br />
da so die Kommunikation zum Bestimmen des Maximalelementes<br />
einer Zeile oder Spalte, das zur Überprüfung der Threshold condition in Algorithmus<br />
2.2 notwendig ist, sowie zur Er<strong>mit</strong>tlung des Rowcounts bzw. des Columncounts,<br />
der zur Bestimmung der Markowitz-Zahlen benötigt wird, entfiele. Bei einer zeilenweisen<br />
Verteilung der Matrixelemente ginge jedoch Nebenläufigkeit verloren, da die<br />
Kommunikation von Informationen einer Zeile, wie z. B. das Verschicken aller Elemente<br />
a is j einer Pivotzeile, die zur Durchführung der Update-loop in Algorithmus 2.1<br />
benötigt werden, stets nur von einem Task ausginge. Dies hätte zwar den Vorteil, daß<br />
insgesamt nur wenige Kommunikationsschritte notwendig wären, was sich bei einer<br />
Architektur <strong>mit</strong> hohen Latenzzeiten auszahlen würde. Da der T3D jedoch besonders<br />
geringe Latenzzeiten im Vergleich zu anderen Architekturen aufweist (siehe 3.1), wird<br />
hier eine grid distribution (oder auch cyclic oder scattered distribution) gewählt. Für<br />
p = XY Prozessoren ist sie definiert durch die Abbildung<br />
a i j → Prozessor q = ( j mod X + (i mod Y) ⋅ X), ∀i, j: i, j ∈ {0,:::,n<br />
− 1}, (3.6)<br />
wenn man die p Prozessoren in einem X ×Y-Gitter anordnet und sie von 0 bis XY − 1<br />
durchnumeriert. Abbildung 3.3 zeigt eine 4×4-Matrix, die auf 4 Prozessoren in einem<br />
2 × 2-Gitter verteilt wurde.<br />
Die Grid distribution führt zu einer optimalen Load balance und hat eine geringe<br />
Kommunikationskomplexität <strong>für</strong> die <strong>LR</strong>-<strong>Zerlegung</strong> dichtbesetzter <strong>Matrizen</strong>. Dies impliziert,<br />
daß beim Algorithmus <strong>für</strong> dünnbesetzte <strong>Matrizen</strong> jeder Prozessor ungefähr<br />
dieselbe Anzahl an (Null- oder Nichtnull-) Elementen zugewiesen bekommt. Falls<br />
sich <strong>für</strong> eine gegebene Matrix die statistische Annahme bewahrheitet, daß jedes Element<br />
der Matrix die gleiche Wahrscheinlichkeit hat, den Wert 0 zu besitzen, dann folgt<br />
daraus, daß die Nichtnullelemente gleichmäßig über die zur Verfügung stehenden Prozessoren<br />
verteilt werden. Falls diese Annahme nicht zutrifft, weil Nichtnullelemente<br />
29
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
0 1 2 3<br />
0<br />
1<br />
2<br />
3<br />
0 1 0 1<br />
2 3 2 3<br />
0 1 0 1<br />
2 3 2 3<br />
Abbildung 3.3: Beispiel einer grid distribution<br />
sich an bestimmten Stellen der Matrix häufen, z. B. in der rechten unteren Ecke oder in<br />
dichtbesetzten Untermatrizen, dann werden diese Häufungselemente über viele Prozessoren<br />
verteilt, und so wieder ein guter Lastausgleich erreicht. Die Speicherausnutzung<br />
profitiert ebenfalls von dieser Aufteilung, da es unwahrscheinlich ist, daß ein<br />
Prozessor keinen Platz mehr <strong>für</strong> neue Elemente hat, während andere noch viel freien<br />
Speicher besitzen.<br />
Eine Alternative zu Matrix-unabhängigen Verteilungsverfahren sind solche, die<br />
Wissen über das Muster von Nichtnullelementen ausnutzen, um eine optimale Lastverteilung<br />
zu erreichen. Das Muster der Nichtnullelemente ändert sich jedoch in jedem<br />
Eliminationsschritt. Eine Anpassung an diese Veränderungen würde jedoch eine<br />
ständige Umverteilung von Elementen erfordern, die zusätzlichen Kommunikationsaufwand<br />
erfordert (und Rechenaufwand zur Analyse des Musters), so daß eine solche<br />
Verteilung, besonders im Hinblick auf sehr dünnbesetzte <strong>Matrizen</strong>, bei deren Faktorisierung<br />
der Rechenaufwand schon den Kommunikationsaufwand unterschreiten kann,<br />
nicht in Betracht kommt.<br />
Im Partitionierungsschritt wurde gefordert, möglichst viele kompatible Pivots in<br />
einem Schritt zu eliminieren, um die Nebenläufigkeit besser auszunutzen. Dies hat jedoch,<br />
wie sich bei der Analyse der Kommunikationsstruktur herausstellte, den Nachteil<br />
der stark vergrößerten Kommunikationskomplexität bei der Pivotauswahl (O (m 2 )<br />
<strong>für</strong> die Kompatibilitätsbestimmung von m Pivotkandidaten). Deshalb wird die Anzahl<br />
der in einem Schritt eliminierbaren Pivots auf eine “kleine” Zahl begrenzt.<br />
Durch Fill-in wird die aktive Submatrix in jedem Schritt dichter. Aus diesem<br />
Grund kann es ab einer bestimmten Dichte günstiger sein, auf die parallele Elimination<br />
von mehreren Pivots zu verzichten und statt dessen nur ein Element pro Schritt zu<br />
30
3.5 Die Klasse DSLUFactor<br />
eliminieren, was den Aufwand zur Kombatibilitätsbestimmung spart.<br />
Der parallele Algorithmus zur <strong>LR</strong>-<strong>Zerlegung</strong> ist in Algorithmus 3.1 wiedergegeben.<br />
Verteile Matrix anhand der Grid distribution<br />
while Row singletons vorhanden sind do<br />
Eliminiere Row singletons<br />
end while<br />
while Column singletons vorhanden sind do<br />
Eliminiere Column singletons<br />
end while<br />
while Dimension der aktiven Submatrix > 0 do {Faktorisierung des Nukleus}<br />
if aktive Submatrix dünnbesetzt? then<br />
Wähle Menge kompatibler Pivots und eliminiere sie<br />
else<br />
{aktive Submatrix dichtbesetzt}<br />
Wähle Pivot und eliminiere ihn<br />
end if<br />
end while<br />
Algorithmus 3.1: Paralleler Algorithmus zur <strong>LR</strong>-<strong>Zerlegung</strong><br />
3.5 Die Klasse DSLUFactor<br />
Die in dieser Arbeit entstandene Klasse DSLUFactor setzt den parallelen Algorithmus<br />
zur <strong>LR</strong>-<strong>Zerlegung</strong> um. Sie ist eine abgeleitete Klasse von DistrObj, die als<br />
Inputinterface die Methode<br />
int DSLUFactor::factor(const SVector *r[], int dimension,<br />
int nnzero);<br />
zur Verfügung stellt, wobei<br />
r die zu faktorisierende Matrix als zeilenweises Array von SVectors<br />
dimension die Dimension (n) der Matrix<br />
nnzero die Gesamtanzahl der Nichtnullelemente in der Matrix<br />
Der Rückgabewert ist negativ, falls die Matrix singulär ist, d. h. keine eindeutige Lösung<br />
<strong>für</strong> Ax = b <strong>mit</strong> der zu faktorisierenden Matrix als A existiert.<br />
31
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
3.5.1 Datenverteilung<br />
Die Datenverteilung geschieht in der Funktion DSLUFactor::factor selbst, und<br />
zwar exakt nach der oben beschriebenen grid distribution. Die Aufteilung <strong>mit</strong>tels der<br />
Grid distribution wird zusätzlich durch zwei Unterklassen (“nested classes”) DSLURow<br />
und DSLUCol unterstützt, die ebenfalls abgeleitete Klassen von DistrObj sind, und so<br />
partitioniert, daß sie <strong>für</strong> das eigene 2 PE die eingeschränkte Sicht auf die PEs ermöglicht,<br />
denen jeweils dieselben Zeilen bzw. Spalten der Matrix zugeordnet sind (die<br />
also im Prozessorgitter dieselbe Zeile oder Spalte bilden, siehe Abbildung 3.3). In<br />
diesen Klassen werden auch alle Daten verwaltet, die sich ausschließlich auf Zeilen<br />
resp. Spalten beziehen, z. B. die Permutationsvektoren π und ρ.<br />
Alle dem eigenen PE zugeordneten Nichtnullelemente werden dazu in ein SVSet<br />
kopiert, das vorher auf die Größe nnzero mal einem bestimmten Faktor gebracht<br />
wurde. Dieses initiale Vergrößern hat zwar nur spekulativen Charakter, reicht aber in<br />
den meisten Fällen aus, um nicht (oder nur selten) während der Faktorisierung neuen<br />
Speicher allokieren zu müssen (was sehr kostenintensiv sein kann). Dabei werden<br />
alle Elemente doppelt gehalten, d. h. im Objekt der Klasse DSLURow befinden sich<br />
alle Elemente 3 nach Zeilen angeordnet, im Objekt der Klasse DSLUCol nach Spalten<br />
geordnet, jedoch lediglich die Zeilenindizes (und nicht die numerischen Werte).<br />
Diese Redundanz in den Datenstrukturen ermöglicht es, effizient auf die Elemente<br />
der aktiven Submatrix zugreifen zu können. Zum Beispiel ist die L-loop stark spaltenorientiert,<br />
während ein zeilenweiser Aufbau den Zugriff auf die Elemente in der<br />
Pivotzeile innerhalb der Update-loop vereinfacht.<br />
3.5.2 Elimination von Singletons<br />
Als nächster Schritt gemäß Algorithmus 3.1 folgt die Elimination der row singletons.<br />
Im ersten Iterationsschritt werden hierzu alle lokalen Zeilen betrachtet und aus<br />
den gefundenen Singletons ein lokales Array gebildet, was dann <strong>mit</strong>tels der gossip-<br />
Methode zu einem globalen zusammengefügt wird. Hierbei stellte sich heraus, daß es<br />
wichtig ist, Informationen über die Reihenfolge der Singletons innerhalb dieses Arrays<br />
zu speichern. Erinnert man sich an die Gossip-Struktur (siehe Abbildung 3.2), so<br />
bemerkt man, daß bei einem simplen Zusammenfügen (“concatenate”) der Daten jedes<br />
PE zum Schluß eine andere Reihenfolge der Einzeldaten innerhalb der gesamten<br />
Daten besitzt (angefangen <strong>mit</strong> den eigenen Daten). Würde man jetzt die so er<strong>mit</strong>telten<br />
Pivots der Reihe nach eliminieren (d. h. die Permutation festhalten), so entstünde auf<br />
2 Als eigene Betriebs<strong>mit</strong>tel werden im folgenden solche bezeichnet, die zur Laufzeit lokal sind.<br />
3 Element ist hier und im folgenden meist ein abstrakter Begriff, der noch nichts über die Information<br />
aussagt, <strong>mit</strong>tels derer das mathematische Objekt Element einer Matrix beschrieben wird.<br />
32
3.5 Die Klasse DSLUFactor<br />
jedem PE eine eigene Pivotreihenfolge, was an und <strong>für</strong> sich kein Problem darstellt.<br />
Will man jedoch später in einem kooperativen Kontext auf die Permutationen zugreifen<br />
(sprich bei einem parallelen Löser), so müssen diese global konsistent sein. Die<br />
Reihenfolge spielt dabei keine Rolle (da alle Singletons kompatibel sind) 4 , sie muß<br />
nur eindeutig sein. Die Singletons werden deshalb nach aufsteigender PE Nummer<br />
eliminiert. Dazu wird die L-loop <strong>für</strong> den lokalen Teil der Pivotspalte durchgeführt<br />
und die Ergebnisse dann ausgetauscht.<br />
Alle sukzessiven Iterationsschritte werden nur anhand der Rowcounts vorgenommen,<br />
die sich geändert haben, d. h. es werden nicht wieder alle Zeilen “angefaßt”,<br />
sondern nur diejenigen, in denen sich eine Veränderung aufgrund der letzten Iteration<br />
ergeben hat, die also neue Singletons enthalten können. Wie bereits angeklungen,<br />
steht an jedem Ende der Iterationsschleife ein Austausch (<strong>mit</strong>tels gossip) der Änderungen<br />
(Updates) an den Rowcounts (Änderungen an den Columncounts können<br />
sich nicht ergeben). Dies braucht jedoch nur in X-Richtung des Prozessorgitters zu<br />
erfolgen, da nur die globalen Rowcounts <strong>für</strong> eigene Zeilen benötigt werden. Die Iterationsschleife<br />
wird abgebrochen, falls keine Singletons mehr vorhanden sind. Der<br />
vollständige Algorithmus zur Elimination der Row singletons ist in Algorithmus 3.2<br />
angegeben.<br />
repeat<br />
Finde lokale Row singletons<br />
Tausche Row singletons aus<br />
for all Row singletons do<br />
Führe lokalen Teil der L-loop aus<br />
Tausche entstandenen Teil von ¯L aus<br />
end for<br />
Tausche Änderungen an den Rowcounts aus<br />
until keine Row singletons mehr vorhanden<br />
Algorithmus 3.2: Elimination von Row singletons<br />
Anschließend werden die Column singletons eliminiert. Dies geschieht im Prinzip<br />
analog zu Algorithmus 3.2, jedoch entfällt hier die Ausführung der L-loop sowie das<br />
Austauschen der Elemente in ¯L. Da in den jeweiligen Zeilen der Column singletons<br />
weitere Elemente vorhanden sein können, müssen die Pivotzeilen traversiert werden,<br />
um die entsprechenden Änderungen an den Columncounts vornehmen zu können.<br />
Diese werden wieder <strong>mit</strong>tels gossip ausgetauscht und dann im nächsten Schritt zur<br />
4 allenfalls <strong>für</strong> die Genauigkeit des Lösers, die von der Reihenfolge der mathematischen Operationen<br />
auf den Fließkommarepräsentationen der Elemente abhängt. Eine Optimierung in dieser Hinsicht<br />
ist hier aber nicht angestrebt.<br />
33
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
Er<strong>mit</strong>tlung neu entstandener Column singletons eingesetzt. Der Algorithmus zur Eliminierung<br />
von Column singletons ist in Algorithmus 3.3 angegeben.<br />
repeat<br />
Finde lokale Column singletons<br />
Tausche Column singletons aus<br />
for all Column singletons do<br />
Traversiere Pivotzeile und etabliere lokale Änderungen der Columncounts<br />
end for<br />
Tausche Änderungen an den Columncounts aus<br />
until keine Column singletons mehr vorhanden<br />
Algorithmus 3.3: Elimination von Column singletons<br />
3.5.3 Faktorisierung des Nukleus<br />
Die verbleibende Submatrix nach Elimination von Singletons enthält nur noch Zeilen<br />
und Spalten <strong>mit</strong> jeweils mehr als einem Nichtnullelement. Sie wird Nukleus der zu<br />
faktorisierenden Matrix genannt. Es ist zwar möglich, daß während der Faktorisierung<br />
des Nukleus weitere Singletons auftreten, jedoch ist deren Anzahl so gering, daß<br />
eine gesonderte Behandlung derselben zu keiner Performancesteigerung führt. Solange<br />
die Matrix dünnbesetzt ist, wird zur Faktorisierung in jedem Schritt eine Menge<br />
kompatibler Pivotelemente ausgewählt. Anschließend führt jedes PE die L-loop<br />
über lokale Elemente aus. Die daraus entstehenden Segmente 5 von ¯L (die Elemente<br />
¯l i js von ¯L in der Pivotspalte j s <strong>mit</strong> i ≠ i s ) werden ebenso wie die lokalen Segmente<br />
der Pivotzeilen (die Elemente a is j in der Pivotzeile i s <strong>mit</strong> j ≠ j s ) ausgetauscht. Danach<br />
wird die Update-loop parallel ausgeführt. Dadurch ergeben sich Änderungen an<br />
den Rowcounts sowie Columncounts durch Fill-in, Elimination der Pivotmenge sowie<br />
evtl. durch Auslöschung von Elementen innerhalb der Update-loop (sehr selten ist<br />
a (s)<br />
i j = ¯l i js ⋅ a (s)<br />
i s j ⇒a(s+1) i j = 0). Außerdem können sich die Maximalbeträge der Elemente<br />
in den Zeilen ändern. Diese Änderungen werden nach Durchführung der Update-loop<br />
<strong>mit</strong>tels Gossip ausgetauscht, so daß die Daten <strong>für</strong> die Er<strong>mit</strong>tlung der Pivotmenge im<br />
nächsten Eliminationsschritt lokal zur Verfügung stehen. Algorithmus 3.4 zeigt die<br />
parallele Faktorisierung des Nukleus.<br />
5 Ein Segment definiere hier eine Menge von Elementen eines Vektors.<br />
34
3.5 Die Klasse DSLUFactor<br />
while Dimension der aktiven Submatrix > 0 do<br />
if aktive Submatrix dünnbesetzt? then<br />
Wähle Menge kompatibler Pivots<br />
else<br />
{aktive Submatrix dichtbesetzt}<br />
Wähle Pivot<br />
end if<br />
for all Pivots do<br />
Führe lokalen Teil der L-loop aus<br />
Tausche neue Segmente von ¯L und Pivotzeile aus<br />
end for<br />
for all Pivots do<br />
Führe lokalen Teil der Update-loop aus<br />
end for<br />
Tausche Änderungen der Rowcounts aus<br />
Tausche Änderungen der Columncounts aus<br />
Tausche Änderungen der Maximalelemente aus<br />
end while<br />
Algorithmus 3.4: Faktorisierung des Nukleus<br />
Auswahl der Pivotmenge<br />
Die Auswahl einer geeigneten Menge von Pivotelementen, die in einem parallelen<br />
Schritt eliminiert werden, unterliegt einer Reihe von Forderungen:<br />
• Vermeidung von Fill (siehe Seite 12)<br />
• Numerische Stabilität (siehe Seite 14)<br />
• Große Menge von kompatiblen Pivots<br />
• Gleichmäßige Lastverteilung auf die einzelnen PEs<br />
Wie man die ersten beiden Forderungen möglichst gut erfüllt, wurde bereits im letzten<br />
Kapitel behandelt. Hier tauchen aber zwei weitere Forderungen auf, die es zu erfüllen<br />
gilt, und die in Einklang <strong>mit</strong> den anderen beiden gebracht werden müssen. Wiederum<br />
läßt sich auch bei diesem Problem keine optimale Lösung <strong>mit</strong> geringem Aufwand<br />
finden, so daß man erneut auf eine heuristische Herangehensweise angewiesen ist.<br />
Glücklicherweise widerspricht die Forderung nach einer großen Menge von Pivots<br />
nicht den anderen Erfordernissen. Vielmehr sieht es statistisch so aus, als wenn<br />
die Forderung nach Vermeidung von Fill sogar die Bildung einer großen Menge von<br />
35
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
kompatiblen Pivots begünstigt. Schließlich ist es nach (3.1) wahrscheinlich, daß Elemente,<br />
die eine geringe Anzahl von Nichtnullelementen in ihrer Zeile bzw. Spalte<br />
haben, kompatibel zu vielen anderen Elementen sind.<br />
Andererseits muß die Menge der kompatiblen Pivots auch, wie in 3.4.4 beschrieben,<br />
auf ein “kleines” Maß beschränkt werden, so daß der Fill-in nicht überhandnimmt.<br />
Denn orientiert man sich nur an der Größe der Pivotmenge, so kann es sein,<br />
daß Elemente <strong>mit</strong> vergleichsweise schlechter Markowitz-Zahl ausgewählt werden,<br />
weil diese zufällig zu solchen <strong>mit</strong> sehr guter Markowitz-Zahl kompatibel sind.<br />
Darüberhinaus ist es wichtig, darauf zu achten, daß nicht ein starkes Ungleichgewicht<br />
bei der Auswahl der Pivots entsteht, also z. B. Pivots fast ausschließlich von<br />
einem PE ausgewählt werden. Dadurch würde nämlich die durch die Grid distribution<br />
bezweckte Gleichverteilung der aktiven Submatrix unterlaufen werden, weil sich<br />
auf einem PE die lokale Submatrix durch die Elimination der Pivotzeilen und -spalten<br />
stark verringern würde im Vergleich zu anderen PEs.<br />
Bisherige Ansätze Smart und White [19] setzen einen Algorithmus ein, in dem<br />
die Pivotmenge S kompatible Elemente <strong>mit</strong> einer Markowitz-Zahl von mincount bis<br />
mincount + a hat, wobei a ein Programmparameter ist. mincount ist die geringste<br />
gefundene Markowitz-Zahl der aktiven Submatrix. Die Menge S wird, ausgehend<br />
von der leeren Menge, sukzessive um Elemente <strong>mit</strong> aufsteigender Markowitz-Zahl<br />
erweitert.<br />
Der Algorithmus von Alaghband [1] generiert eine Anzahl von Mengen kompatibler<br />
Pivots, von denen die größte Menge ausgewählt wird. Bei gleichgroßen Mengen<br />
entscheidet die minimale Markowitzsumme. Aus der Pivotmenge S werden anschließend<br />
Elemente entfernt, die eine Markowitz-Zahl haben, die größer als eine benutzerspezifizierte<br />
Zahl ist oder deren Betrag kleiner als ein Thresholdparameter ist. Hier<br />
wird also eine möglichst große Anzahl von Pivots angestrebt (u. U. zu Lasten des<br />
Fill-in).<br />
Der von Davis und Yew [6] vorgestellte Algorithmus generiert eine Pivotmenge S,<br />
die Elemente <strong>mit</strong> einer Markowitz-Zahl von mincount bis a ⋅ mincount enthält, wobei<br />
a ein Programmparameter ist (“typically two to eight”, a = 4 in ihren Experimenten).<br />
Alle Prozessoren suchen nach akzeptablen Pivotkandidaten und versuchen, sie<br />
zu der momentanen Menge S hinzuzufügen. Falls ein Kandidat kompatibel <strong>mit</strong> allen<br />
Elementen von S ist, wird er hinzugefügt. Konflikte zwischen Prozessoren, die versuchen,<br />
gleichzeitig einen Kandidaten hinzuzufügen, werden durch kritische Abschnitte<br />
verhindert. Dies impliziert, daß der Algorithmus nichtdeterministisch arbeitet, da<br />
Laufzeiten Einfluß auf das Ergebnis haben.<br />
Van der Stappen, Bisseling und Van de Vorst [21] präsentieren einen Algo-<br />
36
3.5 Die Klasse DSLUFactor<br />
rithmus, der eine Menge S von Pivotkandidaten aufbaut, indem jeder Prozessor in<br />
den ncol dünnbesetztesten Spalten nach akzeptablen Pivotkandidaten <strong>mit</strong> minimaler<br />
Markowitz-Zahl sucht. Diese werden zusammengefügt, so daß jeder Prozessor<br />
die globale Menge S von ncol Elementen hat, die aus den akzeptablen Elementen<br />
<strong>mit</strong> den niedrigsten Markowitz-Zahlen besteht. Dabei wird S nach aufsteigenden<br />
Markowitz-Zahlen sortiert. Anschließend werden Elemente <strong>mit</strong> einer unakzeptabel<br />
hohen Markowitz-Zahl M i j > a ⋅ mincount entfernt. Die endgültige Menge S<br />
wird dann, analog zu [19] sukzessive aus den Elementen gebildet, die die geringste<br />
Markowitz-Zahl haben und zu den restlichen Elementen in S kompatibel sind.<br />
Ein ähnlicher Algorithmus wird auch von Asenjo und Zapata [2] eingesetzt, jedoch<br />
wird eine Abschwächung des Kompatibilitätskriteriums zugelassen, was zu einer<br />
größeren Pivotmenge, aber auch zu einer aufwendigeren Update-loop führt.<br />
All diesen Algorithmen ist gemein, daß sie bei der Pivotauswahl die obige letzte<br />
Forderung nach Lastausgleich außer acht lassen. Dies wird vielmehr durch den Permutationsschritt<br />
geleistet. Zeilen und Spalten der Matrix A können implizit (durch<br />
Gebrauch der Permutationsvektoren π und ρ wie in Algorithmus 2.1) oder explizit<br />
(durch Verschieben der Daten innerhalb der Matrix) permutiert werden. Explizite<br />
Permutation führt zu einer guten Lastverteilung (Prozessoren, die Nichtnullelemente<br />
durch Elimination verlieren, bekommen durch explizite Permutation neue Elemente<br />
zugewiesen, was zu einem Lastausgleich führt). Die Lastverteilung bei impliziter<br />
Permutation hängt jedoch von der “zufälligen” Pivotreihenfolge ab. Aus diesem<br />
Grund implementieren die meisten parallelen <strong>LR</strong>-<strong>Zerlegung</strong>s-Algorithmen eine explizite<br />
Permutation.<br />
Auswahlalgorithmus in DSLUFactor Die Auswahl der Pivotmenge in dieser Arbeit<br />
orientiert sich weitgehend an [23]. Die Auswahl geschieht dabei in drei Phasen.<br />
In Phase A wird zunächst eine lokale Liste 6 von Pivotkandidaten aufgestellt, die dann<br />
zu einer globalen, sortierten Liste zusammengefügt wird, aus der Elemente <strong>mit</strong> inakzeptabel<br />
hoher Markowitz-Zahl entfernt werden. In Phase B werden Inkompatibilitäten<br />
erfaßt und in Phase C die Menge S durch sukzessives Hinzufügen (bzw. Entfernen<br />
der inkompatiblen Elemente) von Elementen <strong>mit</strong> niedriger Markowitz-Zahl gebildet.<br />
Phase A: Lokale Auswahl von Pivotkandidaten Algorithmus 3.5 zeigt den hier<br />
eingesetzten Algorithmus zur Auswahl einer Menge von lokalen Pivotkandidaten.<br />
Jedes PE wählt also c p (oder weniger, falls nicht mehr c p Elemente auf dem lokalen<br />
PE vorhanden sind) Pivotkandidaten aus. Die Auswahl selber geschieht da-<br />
6 Im folgenden wird stets von einer Liste gesprochen, die streng genommen eine Menge ist, intern<br />
aber als DataArray repräsentiert ist.<br />
37
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
for m = 1 to c p do<br />
Wähle lokalen Pivotkandidaten a i j<br />
Füge a i j in lokale sortierte Liste von Pivotkandidaten ein<br />
end for<br />
Bilde globale sortierte Liste von Pivotkandidaten <strong>mit</strong>tels gossip<br />
for all Pivotkandidaten a i j do<br />
if M i j > a ⋅ M i0 j 0<br />
then<br />
Entferne a i j aus der globalen Liste von Pivotkandidaten<br />
end if<br />
end for<br />
Algorithmus 3.5: Lokale Auswahl von Pivotkandidaten<br />
bei <strong>mit</strong>tels Algorithmus 2.2. Betrachtet man noch einmal Algorithmus 2.2, so sieht<br />
man, daß zu Beginn jeden Durchlaufs der repeat-Schleife die Bestimmung der Zeile<br />
oder Spalte <strong>mit</strong> der geringsten Anzahl von Nichtnullelementen (d. h. des geringsten<br />
Rowcounts bzw. Columncounts) steht. Dies soll natürlich nicht durch wiederholtes<br />
Durchsuchen aller Zeilen und Spalten geschehen. Deshalb hat jedes DSLURow- bzw.<br />
DSLUCol-Objekt ein Array von Objekten der Klasse IdRing, die ebenfalls im Rahmen<br />
dieser Arbeit entstand. Die Klasse IdRing implementiert einen doppelt verketteten<br />
Ring, der hier dazu dient, die Indizes der Zeilen bzw. Spalten <strong>mit</strong> gleichem Rowbzw.<br />
Columncount zu speichern. Das Array faßt die Ringe nach aufsteigender Nichtnullelementeanzahl<br />
zusammen, so daß man <strong>mit</strong>tels min[c] auf den Ring von Indizes<br />
<strong>mit</strong> einer Nichtnullelementeanzahl von c+1 7 zugreifen kann. Die Arrayelemente sind<br />
dabei Dummies, die nur als Anfangspunkte der Ringe dienen. Die Ringelemente sind<br />
in einem weiteren Array gespeichert, das einen direkten Zugriff über die Zeilen- bzw.<br />
Spaltennummer erlaubt und so das Umhängen von Indizes vereinfacht. Ein Ring ist<br />
also leer, wenn das erste Element des Rings sich selbst als Nachfolger sowie Vorgänger<br />
hat. Abbildung 3.4 zeigt ein Beispiel eines Rings. Die Zeile (oder Spalte) 11 hat<br />
hier 48 Nichtnullelemente. Sie ist dazu in den Ring <strong>mit</strong> Index 47 eingehängt. Weiterhin<br />
kann man auf das Ringelement anhand des oberen Arrays zugreifen. 8 Es gibt<br />
keine Zeile (oder Spalte) <strong>mit</strong> 49 Nichtnullelementen, deshalb ist der Ring 48 leer.<br />
Auf diese Art und Weise kann also schnell auf die Zeile oder Spalte <strong>mit</strong> der geringsten<br />
Anzahl von Nichtnullelementen zugegriffen werden. Das Überprüfen der<br />
Threshold-Bedingung (2.16) geschieht anhand eines Arrays des DSLURow-Objektes,<br />
das die Maximalbeträge der Zeilen des eigenen PEs gespeichert hat. Änderungen<br />
7 Arrays sind von 0 bis n−1 durchnumeriert und eine Zeile oder Spalte kann 1 bis n Nichtnullelemente<br />
enthalten.<br />
8 Die beiden Kästchen <strong>mit</strong> der Zahl 11 symbolisieren hier natürlich dieselbe Speicherstelle.<br />
38
10<br />
46<br />
3.5 Die Klasse DSLUFactor<br />
<br />
11 12<br />
<br />
47 48<br />
11<br />
Abbildung 3.4: Beispiel der Ring-Struktur<br />
hieran werden ebenso wie Änderungen an den Row- und Columncounts (anhand derer<br />
auch die Ringe aktualisiert werden) nach jedem Eliminationsschritt ausgetauscht.<br />
Beim Durchsuchen von Spalten muß zur Überprüfung des Threshold-Kriteriums der<br />
Wert des potentiellen Pivotkandidaten erst in der zugehörigen Zeile gesucht werden,<br />
was <strong>für</strong> <strong>Matrizen</strong> <strong>mit</strong> sehr vielen Elementen zum dominierenden Zeitfaktor werden<br />
kann – deshalb kann die Suche <strong>mit</strong>tels eines Programmparameters auf Zeilen beschränkt<br />
werden. 9 Die entsprechende Zeile von Algorithmus 2.2 lautet dann:<br />
Wähle i ∈ I′ <strong>mit</strong> minimaler Anzahl von Nichtnullelementen<br />
Nachdem ein lokaler Pivotkandidat best gefunden wurde, wird dessen Zeilen- sowie<br />
Spaltenindex aus den jeweiligen Ringen ausgehängt, da<strong>mit</strong> die Zeile und Spalte im<br />
nächsten Schritt der lokalen Pivotauswahl nicht noch einmal durchsucht wird.<br />
Als nächstes wird der Pivotkandidat in die lokale sortierte Liste von Pivotkandidaten<br />
eingefügt. Dies geschieht <strong>mit</strong>tels Insertion Sort anhand der Ordnungsrelation<br />
a i j ˙≤ a kl ⇔<br />
M i j < M kl oder<br />
(M i j = M kl und i < k).<br />
(3.7)<br />
9 In Experimenten führte dies zu einer nur geringfügigen Verschlechterung der Pivotreihenfolge.<br />
39
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
˙≤ ist eine totale Ordnung <strong>für</strong> die Menge der lokalen Pivotkandidaten, da i ≠ k <strong>für</strong> alle<br />
Pivotkandidatenpaare (a i j ,a kl ).<br />
Besondere Aufmerksamkeit verdient der Parameter c p , der die (maximale) Anzahl<br />
der im aktuellen Pivotschritt von einem PE auszuwählenden Pivotkandidaten festlegt.<br />
Wie bereits oben erwähnt, implementieren die meisten <strong>LR</strong>-<strong>Zerlegung</strong>s-Algorithmen<br />
eine explizite Permutation, die ein dynamisches Load-balancing verwirklicht. Dies ist<br />
notwendig, da die statische Lastverteilung <strong>mit</strong>tels der Grid distribution sich während<br />
der Laufzeit stark verändern kann. Nachteil bei expliziter Permutation ist der stark erhöhte<br />
Kommunikationsaufwand. Deshalb wird im Algorithmus von DSLUFactor der<br />
Parameter c p zum dynamischen Lastausgleich genutzt. Dieses Verfahren ist erstmals<br />
in [23] beschrieben und wird hier modifiziert eingesetzt. Ziel des Verfahrens ist es,<br />
daß höherbelastete PEs auch mehr lokale Pivotkandidaten auswählen, da<strong>mit</strong> sie durch<br />
Elimination derselben stärker an Last (d. h. an Nichtnullelementen in den Pivotzeilen<br />
und -spalten) verlieren als wenigerbelastete PEs, und sich so ein Lastausgleich ergibt.<br />
Der Aufwand des Algorithmus zur Pivotauswahl ist wesentlich geringer als der der<br />
Update-loop, so daß die Erhöhung des ersteren kaum ins Gewicht fällt im Vergleich<br />
zum Ausgleich des letzteren.<br />
Ein Maß <strong>für</strong> die Belastung eines PEs ist die Größe des lokalen Anteils an der<br />
aktiven Submatrix<br />
d p = |I | ⋅ | J|,<br />
d. h. d p ist das Produkt der Anzahl der lokalen Zeilen und der Anzahl der lokalen<br />
Spalten. Es wäre auch möglich, das Besetzungsmuster der aktiven Submatrix als<br />
Maß <strong>für</strong> die Belastung heranzuziehen, was noch akkurater wäre. Dies würde jedoch<br />
zusätzlichen Rechenaufwand bedeuten, weshalb hier der einfachere Ansatz gewählt<br />
wurde. 10 Jedes PE wählt in Schritt s eine Anzahl von lokalen Pivotkandidaten<br />
c p = d p<br />
n 2 c, (3.8)<br />
s<br />
wobei n s die Dimension der aktiven Submatrix ist und c ein Programmparameter. In<br />
[23] wird gezeigt, daß die optimale Größe von c nur von der Prozessorgeschwindigkeit<br />
sowie von Latenzzeiten bei der Kommunikation abhängt, nicht jedoch von der zu<br />
faktorisierenden Matrix. Im nächsten Kapitel wird das Thema Load-balancing noch<br />
einmal aufgegriffen.<br />
Hat jedes PE c p lokale Pivotkandidaten ausgewählt, so werden diese <strong>mit</strong>tels<br />
gossip zu einer globalen Liste von Pivotkandidaten zusammengefügt. Dies geschieht<br />
10 In [23] wird nur die Anzahl der Zeilen verwendet, da dort eine zeilenweise Verteilung der Matrix<br />
zugrunde gelegt wurde.<br />
40
3.5 Die Klasse DSLUFactor<br />
dermaßen, daß die GossipFunction in jedem Kommunikationsschritt die lokale und<br />
entfernte Liste von Pivotkandidaten <strong>mit</strong>tels Merge Sort zu einer global sortierten verschmilzt.<br />
Hierbei kann es aber vorkommen, daß Pivotkandidaten sowohl die gleiche<br />
Markowitz-Zahl als auch die gleiche Zeilennummer haben, weshalb erst die Ordnungsrelation<br />
a i j ˙≤ a kl ⇔<br />
M i j < M kl oder<br />
(M i j = M kl und i < k) oder<br />
(M i j = M kl und i = k und j < l)<br />
(3.9)<br />
eine totale Ordnung über der Menge der globalen Pivotkandidaten definiert. Nach<br />
dem gossip-Schritt hat jedes PE dieselbe globale Liste von Pivotkandidaten.<br />
Aus dieser werden im letzten Schritt der Phase A noch die Elemente entfernt, die<br />
eine unakzeptabel hohe Markowitz-Zahl aufweisen.<br />
Phase B: Erkennung von Inkompatibilitäten In Algorithmus 3.6 wird der Ablauf<br />
der zweiten Phase des in DSLUFactor verwendeten Algorithmus zur Pivotauswahl<br />
gezeigt.<br />
for all Pivotzeilen i (von Pivotkandidaten a i j ) do<br />
for all lokale Elemente a il do<br />
if Pivotkandidat a kl ≠ a i j in Spalte l then<br />
if a i j ˙≤ a kl then<br />
Füge (a i j ,a kl ) zur Liste von Inkompatiblen hinzu<br />
else<br />
Füge (a kl ,a i j ) zur Liste von Inkompatiblen hinzu<br />
end if<br />
end if<br />
end for<br />
end for<br />
Bilde globale Liste von Inkompatiblen <strong>mit</strong>tels gossip<br />
Algorithmus 3.6: Erkennung von Inkompatibilitäten<br />
Ziel der Phase B ist es, alle Inkompatibilitäten zwischen Elementen der globalen<br />
Liste der Pivotkandidaten zu erkennen. Dies soll so geschehen, daß am Ende dieser<br />
Phase die Relation N ⊆ S × S auf allen PEs vorhanden ist, wobei S die Menge der<br />
41
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
globalen Pivotkandidaten symbolisiert und die zweistellige Relation N definiert ist<br />
als<br />
(a i j ,a kl ) ∈ N ⇔<br />
(a il ≠ 0 oder a k j ≠ 0) und<br />
a i j ˙≤ a kl .<br />
(3.10)<br />
N wird als DataArray von Strukturen der Form<br />
struct Incomp<br />
{<br />
short a,b;<br />
Incomp(int x=-1, int y=-1): a(x), b(y) {};<br />
};<br />
repräsentiert, d. h. jedes Incomp-Objekt definiert ein geordnetes Paar (a,b). Dies<br />
ermöglicht eine geringe Kommunikationskomplexität beim anschließenden Gossip,<br />
kann jedoch redundanter als eine Repräsentation als |S | ⋅ | S|-Array sein (oder anderer<br />
Datenstrukturen), jedoch nur bei sehr vielen Inkompatibilitäten, d. h. dem Vorliegen<br />
einer sehr dichtbesetzten Submatrix, wovon hier aber nicht ausgegangen wird, da dieser<br />
Fall gesondert behandelt wird (siehe Algorithmus 3.4).<br />
Betrachtet man noch einmal (3.1), so würde ein naiver Ansatz zur Bildung der<br />
Relation N vielleicht lauten, <strong>für</strong> ein Element a i j der globalen Liste von Pivotkandidaten<br />
alle anderen Pivotkandidaten a kl durchzugehen und zu untersuchen, ob es ein<br />
Nichtnullelement a il gibt, das auf eine Inkompatibilität hindeutet und dann das Paar<br />
(a i j ,a kl ) (bzw. (a kl ,a i j )) der Relation N hinzuzufügen. 11 Dieses Vorgehen hätte einen<br />
Aufwand vonO (|S| 2 ). Dies bedeutet aber einen unnötigen Aufwand bei der mehrfachen<br />
Suche in Zeilen des Pivotkandidaten a i j .<br />
Deshalb wird bei der Feststellung von Inkompatibilitäten der umgekehrte Weg<br />
gegangen, d. h. es werden alle Zeilen von Pivotkandidaten nach Nichtnullelementen<br />
untersucht, die zwei Pivotkandidaten inkompatibel zueinander machen. Jedes PE geht<br />
also alle (lokalen Teile der) Zeilen durch, in denen ein Pivotkandidat a i j vorhanden<br />
ist und prüft dabei <strong>für</strong> jedes Nichtnullelement a il , ob es zu einer Inkompatibilität zu<br />
einem anderen Pivotkandidaten führt, d. h. ob in der betreffenden Spalte l ein Pivotkandidat<br />
a kl existiert. Hierzu ist natürlich eine Datenstruktur notwendig, die diese<br />
Überprüfung inO (1) ermöglicht. Deshalb wird noch ein DataArray angelegt, das <strong>für</strong><br />
jede Spalte Auskunft gibt, ob in ihr ein Pivotkandidat vorhanden ist. Dadurch ergibt<br />
42
3.5 Die Klasse DSLUFactor<br />
S<br />
0 1 2<br />
a gh a i j<br />
a kl<br />
1 ⊥ 0 2<br />
a i j a im 0 a il<br />
a kl<br />
a gh<br />
Abbildung 3.5: Erkennung von Inkompatibilitäten<br />
sich <strong>für</strong> Algorithmus 3.6 ein Aufwand vonO (|S|). Abbildung 3.5 zeigt noch einmal<br />
alle relevanten Elemente der Phase B.<br />
In diesem Beispiel besteht die globale Liste der Pivotkandidaten S aus drei Elementen<br />
a gh ˙≤ a i j ˙≤ a kl . Beim Untersuchen der Zeile i wird eine Inkompatibilität zwischen<br />
a i j und a kl (dem Pivotkandidaten <strong>mit</strong> dem Index 2 in S) aufgrund von a il festgestellt.<br />
In Spalte m befindet sich kein Pivotkandidat (durch ⊥ angedeutet), deshalb<br />
entsteht aufgrund von a im keine Inkompatibilität. Es besteht ebenfalls keine Inkompatibilität<br />
zwischen a i j und a gh , da a ih = 0. 12 Nach der Untersuchung von Zeile i ist<br />
N also um (a i j ,a kl ) erweitert.<br />
Abschließend werden alle Teilrelationen <strong>mit</strong>tels gossip zu einer großen Relation<br />
vereinigt. In jedem Gossip-Schritt werden also die kommunizierten DataArrays<br />
zusammengefügt.<br />
11 Äquivalent wäre es natürlich, nur alle größeren (im Sinne von ˙≤ ) Pivotkandidaten durchzugehen<br />
und dann auch nach a k j zu suchen.<br />
12 Es könnte eine Inkompatibilität durch a g j entstehen. Dies wird beim untersuchen von Zeile g geprüft.<br />
43
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
Phase C: Auswahl kompatibler Pivotkandidaten Zu Beginn der Phase C verfügt<br />
jedes PE über die gleiche globale Liste von Pivotkandidaten sowie über die gleiche<br />
Menge von geordneten Paaren inkompatibler Elemente. Mittels dieser Informationen<br />
soll nun in dieser Phase eine Menge von kompatiblen Pivotkandidaten erstellt werden,<br />
die möglich (“feasible”) und optimal ist. Möglich ist eine Menge von Pivotkandidaten<br />
S′⊆S genau dann wenn<br />
feasible(S′) ⇔<br />
∀(a i j ,a kl ) ∈ S′×S′: (a i j ,a kl ) ∉ N,<br />
d. h. alle Pivotkandidaten kompatibel sind. Das Optimalitätskriterium wird als<br />
optimal(S′) ⇔<br />
∀Q ⊆ S, feasible(Q): |Q| ≤ |S′| und<br />
∀Q ⊆ S, feasible(Q), |Q| = |S′|: ∑M i j ≤ ∑M kl , a i j ∈ S′, a kl ∈ Q<br />
definiert, d. h. die Forderungen nach Minimierung der Summe der Markowitz-Zahlen<br />
(die ein Maß <strong>für</strong> den potentiellen Fill-in angeben) sowie einer möglichst großen Pivotmenge<br />
werden hier angestrebt.<br />
Um zu einer optimalen Lösung zu gelangen, müssen Verfahren der globalen Optimierung<br />
eingesetzt werden (“backtrack”, “branch and bound” o. ä.), da kein Kriterium<br />
existiert, daß <strong>für</strong> jede partielle Lösung entscheidet, welcher Pivotkandidat hinzugenommen<br />
werden muß, um zu einer optimalen Lösung zu gelangen. In DSLUFactor<br />
wurde jedoch trotzdem ein lokaler Optimierungsalgorithmus (“greedy algorithm”)<br />
eingesetzt, da es sich nicht lohnt, den Mehraufwand zur Auffindung einer optimalen<br />
Lösung zu betreiben, die nur geringfügig besser ist als die heuristisch gewonnene.<br />
Algorithmus 3.7 zeigt diesen Algorithmus.<br />
m:= 1;<br />
while m Elemente in der globalen Liste von Pivotkandidaten vorhanden do<br />
{a i j ist m-tes Element der globalen Liste von Pivotkandidaten}<br />
for all geordneten Paare (a i j ,a kl ) aus der globalen Liste von Inkompatiblen do<br />
Entferne a kl aus der globalen Liste von Pivotkandidaten<br />
end for<br />
m:= m + 1;<br />
end while<br />
Algorithmus 3.7: Phase C: Auswahl kompatibler Pivotkandidaten<br />
Das heuristische Auswahlkriterium lautet also, immer das Element <strong>mit</strong> der kleinsten<br />
Markowitz-Zahl (also das vorderste in der sortierten Liste) auszuwählen und alle<br />
44
3.5 Die Klasse DSLUFactor<br />
dazu inkompatiblen zu entfernen usf. Hierbei wird natürlich die Forderung nach einer<br />
möglichst großen Pivotmenge außer acht gelassen, was aber hoffentlich durch die<br />
geringen Markowitz-Zahlen der ausgewählten Elemente dennoch zum Tragen kommt.<br />
Die in Phase B aufgestellte Relation N als Array von Incomp-Objekten eignet<br />
sich schlecht <strong>für</strong> die Durchführung von Algorithmus 3.7, da man auf alle Inkompatiblen<br />
eines Pivotkandidaten hintereinander zugreifen will. Deshalb wird zu Beginn<br />
der Phase C in DSLUFactor noch eine Umformung von N vorgenommen, so daß die<br />
Inkompatibilitäten in m DataArrays gespeichert werden, wenn m die Anzahl der Pivotkandidaten<br />
ist.<br />
Beim Durchführen des Algorithmus muß man weiterhin auf die Kohärenz der in<br />
Phase A benötigten Ringe achten, d. h. alle Zeilen und Spalten der ausgewählten Pivotkandidaten<br />
werden noch einmal aus ihren Ringen ausgehängt, da sie von einem<br />
anderen PE ausgewählt sein können, also noch nicht in Phase A ausgehängt wurden.<br />
Außerdem müssen die Zeilen und Spalten der Pivotkandidaten, die aus der Pivotmenge<br />
entfernt werden, wieder in ihre Ringe eingehängt werden, da sie nicht eliminiert<br />
werden und im nächsten Schritt wieder zur Auswahl herangezogen werden können.<br />
Resümee Führt man sich noch einmal die Forderungen zu Beginn dieses Abschnittes<br />
vor Augen, so sieht man, daß die in DSLUFactor implementierte Auswahl von Pivotelementen<br />
jede der Zielsetzungen konsequent verfolgt. Die numerische Stabilität<br />
wird durch das threshold pivoting in Phase A angestrebt. Die Minimierung des Fill-in<br />
wird durch die Suche in den dünnbesetztesten Zeilen bzw. Spalten in Phase A effizient<br />
umgesetzt, und durch die Bevorzugung von Kandidaten <strong>mit</strong> geringer Markowitz-Zahl<br />
bei der Mengenbildung in Phase C weiter unterstützt. Darüberhinaus verhindert die<br />
Markowitz-Zahlenbegrenzung nach [6], daß Ausreißer in die Pivotmenge aufgenommen<br />
werden. Eine große Pivotmenge wird implizit durch die Auswahl von c p lokalen<br />
Pivotkandidaten und die geringen Markowitz-Zahlen angestrebt. Dies könnte noch<br />
intensiver betrieben werden, etwa durch globale Optimierungsverfahren in Phase C<br />
oder eine lokale Optimierung, die Kandidaten <strong>mit</strong> wenig Inkompatiblen bevorzugt.<br />
Eine gute Lastverteilung wird durch dynamisches, passives Load-balancing versucht<br />
zu erreichen.<br />
Die L-loop<br />
Nach Beendigung der Pivotauswahl verfügt jedes PE über die globale Menge von<br />
kompatiblen Pivots, die nach aufsteigender Markowitz-Zahl sortiert ist. Diese Menge<br />
kann natürlich auch leer sein, d. h. es wurden keine Pivots gefunden. Das bedeutet<br />
aber, daß es mindestens eine Zeile des Gleichungssystems Ax = b gibt, in der kein x i<br />
auftaucht, es also keine eindeutig bestimmte Lösung des Gleichungssystems gibt. In<br />
45
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
diesem Falle wird die Faktorisierung abgebrochen und der Wert −1 zurückgeliefert,<br />
der die Singularität der Matrix anzeigt.<br />
Betrachtet man noch einmal Algorithmus 3.4 sowie Algorithmus 2.1, so folgt als<br />
nächster Schritt der Faktorisierung die Durchführung der L-loop, d. h. das Aufbauen<br />
(eines Teils) der Matrix ¯L. Hierzu zunächst einige (evtl. trivial erscheinende) Aspekte,<br />
die dabei Beachtung verdienen.<br />
Vergegenwärtigt man sich noch einmal die Struktur der Matrix ¯L bzw. L wie in<br />
Kapitel 2 beschrieben, so erkennt man, daß die Diagonalelemente l ii alle den Wert 1<br />
haben. Diese Elemente werden natürlich nicht explizit behandelt und auch nicht in<br />
Datenstrukturen gespeichert.<br />
Teil der Aufgabenstellung ist es außerdem, daß die kompletten <strong>Matrizen</strong> L und R<br />
nach Beendigung der Faktorisierung auf allen PEs vorhanden sind. Dies ermöglicht<br />
die einfache Neuverteilung der Faktoren im anschließenden Lösungsschritt.<br />
Bestandteil der L-loop ist hier auch der Austausch der Elemente in den Pivotzeilen,<br />
die in der Update-loop benötigt werden. Die L-loop hat deshalb eine Doppelfunktion<br />
– sie beinhaltet den Aufbau von ¯L sowie die den Austausch von Elementen,<br />
die in der Update-loop benötigt werden.<br />
Um eine geringe Komplexität der L-loop zu erreichen, ist es von Vorteil, hier nur<br />
die unbedingt notwendige Arbeit zu verrichten. Aus diesem Grund wird an dieser<br />
Stelle ein wenig auf die Struktur der Update-loop vorgegriffen, deren Durchführung<br />
die Abarbeitung der L-loop voraussetzt. Abbildung 3.6 zeigt einen Ausschnitt der<br />
aktiven Submatrix einer Beispielmatrix.<br />
Alle gezeigten Elemente sind <strong>mit</strong> der Nummer des PEs gekennzeichnet, auf das<br />
sie verteilt sind (4 PEs bei einer Verteilung im 2 × 2 Gitter). Die dunkel unterlegten<br />
Elemente sind Pivotelemente (die restlichen Elemente in der linken oberen Ecke sind<br />
imaginäre, da sie aufgrund der Kompatibilität den Wert 0 haben müssen). Links unten<br />
ist der Teil der Matrix, dessen Elemente zur Bildung von ¯L dienen, wovon drei<br />
Elemente hervorgehoben sind. Rechts oben sind drei Elemente der Pivotzeilen hervorgehoben.<br />
Das Element, das sich im Schnittpunkt dieser Elemente befindet, ist ein<br />
potentielles Update-Element. Wie unschwer ersichtlich, kann ein Update (oder ein<br />
Fill-in) <strong>für</strong> ein PE (im Beispiel 0) nur passieren, wenn ein Element der Pivotzeile in<br />
einer dem PE <strong>mit</strong>tels der Grid distribution zugeordneten Spalte steht und ein Element<br />
der Pivotspalte (aus der sich ¯L konstituiert) in einer dem PE zugeordneten Zeile steht.<br />
Es können sich also <strong>für</strong> PE 0 in diesem Beispiel keine Updates aus Elementen einer<br />
Pivotzeile ergeben, die PE 1 oder 3 zugeordnet sind. Ebensowenig können Updates<br />
aus Elementen der Pivotspalte verursacht werden, die PE 2 oder 3 zugeordnet sind.<br />
Es bietet sich deshalb an, die Elemente der Pivotzeile nur spaltenweise, d. h. innerhalb<br />
von DSLUCol, und die Elemente von ¯L nur zeilenweise, d. h. innerhalb von<br />
DSLURow auszutauschen. Dies wurde auch zunächst so implementiert. Um dann die<br />
46
3.5 Die Klasse DSLUFactor<br />
0 1 0<br />
2 3 2<br />
0 1 0<br />
0<br />
2<br />
0<br />
DSLUCol<br />
0 1 0 0<br />
DSLURow<br />
Abbildung 3.6: Möglichkeiten <strong>für</strong> Updates<br />
vollständigen <strong>Matrizen</strong> L und R auf allen PEs zu haben, ist am Ende der Faktorisierung<br />
aber noch ein Schritt nötig, in dem die Elemente von ¯L und Ā 13 in der jeweils<br />
anderen Richtung kommuniziert werden, d. h. die Elemente von ¯L zwischen den Zeilen<br />
ausgetauscht werden und die Elemente von Ā zwischen den Spalten. In diesem<br />
letzten Schritt wird also ein sehr hohes Datenvolumen transportiert, was zu “Verklemmungen”<br />
des Verbindungsnetzwerks des T3D führt. Dies ergab einen so hohen Aufwand,<br />
daß darunter die Gesamtperformance der Faktorisierung litt. Deshalb wurde<br />
eine andere Kommunikationsstrategie innerhalb der L-loop verfolgt.<br />
Es werden doch alle Elemente der Pivotzeilen und von ¯L innerhalb der L-loop<br />
kommuniziert. Dies könnte natürlich <strong>mit</strong>tels gossip geschehen, da diese Methode<br />
eine geringe Kommunikationskomplexität und niedrige Latenzzeiten (wenige, aber<br />
große Pakete) aufweist. Andererseits ist sie aber stark synchron, da immer auf das<br />
Eintreffen von Daten spezifischer Partner in jedem Kommunikationsschritt gewartet<br />
werden muß. Sie eignet sich also vornehmlich <strong>für</strong> Aufgaben, bei denen die Daten<br />
noch reduziert werden müssen (z. B. Summenbildung oder das Merge Sort bei der<br />
Pivotauswahl). In diesem Kontext (purer Datenaustausch) sollte aber ein asynchroneres<br />
Verfahren eingesetzt werden. Dazu dient die Klasse MVector, die ebenfalls im<br />
13 Ā ist nach Beendigung der Faktorisierung die nichtpermutierte R-Matrix.<br />
47
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
Rahmen dieser Arbeit entstand.<br />
Die Klasse MVector Die Klasse MVector (“Vektor von Matrixelementen”) stellt<br />
eine komplette Unterstützung <strong>für</strong> die verteilte Speicherung von dünnbesetzten <strong>Matrizen</strong><br />
zur Verfügung. Sie ist eine abgeleitete Klasse von DSVector (“dynamic sparse<br />
vector”) [22] und übernimmt so die Funktionalität der Speicherung eines dünnbesetzten<br />
Vektors. Dieser Vektor ist aber unterteilt in Untervektoren, die dynamisch<br />
erzeugt werden können und so eine Sicht auf verschiedene Zeilen oder Spalten bietet.<br />
Weiterhin wird die Unterscheidung in lokale und globale Anteile der Untervektoren<br />
eingeführt, sowie Kommunikationsmethoden zum Austausch der jeweiligen Anteile.<br />
Ein Untervektor eines MVectors kann nicht vergrößert werden, so daß dessen jeweilige<br />
Größe im voraus bekannt sein muß, d. h. bevor die Elemente hinzugefügt werden.<br />
Die Anzahl der Untervektoren ist jedoch dynamisch, so daß sukzessive neue Vektoren<br />
hinzugefügt werden können. Dieses Konzept vermeidet häufige realloc()s<br />
und das Umkopieren von Vektoren. Der typische Benutzungszyklus eines MVectors<br />
sieht etwa so aus:<br />
1. Festlegen der maximalen Zahl von Untervektoren (die Dimension der durch den<br />
MVector beschriebenen Matrix)<br />
2. Kreieren einer Anzahl von neuen Untervektoren (<strong>mit</strong> vorgegebener Größe)<br />
3. Hinzufügen von lokalen Elementen zu diesen Untervektoren<br />
4. Konstruieren der globalen Untervektoren (kommunizieren <strong>mit</strong> anderen PEs)<br />
5. Wiederholen der Schritte 2-4<br />
6. Benutzen der Untervektoren<br />
Abbildung 3.7 zeigt den Aufbau eines MVectors. In diesem Beispiel wurden zwei<br />
Untervektoren (0 und 1) kreiert, die die Zeilen 17 bzw. 29 repräsentieren. Die unterlegten<br />
Bereiche symbolisieren den Füllstand der Zeilen, d. h. in Zeile 17 wurden<br />
schon alle lokalen sowie globalen Elemente eingefügt, in Zeile 29 fehlen noch einige<br />
lokale Elemente und es sind noch nicht alle entfernten Elemente von den anderen PEs<br />
eingetroffen. Der restliche Bereich des MVectors ist noch frei, d. h. hier können noch<br />
Untervektoren kreiert werden, ohne daß ein realloc() fällig wird.<br />
Besondere Bedeutung kommt der Kommunikationsmethode<br />
void MVector::broadcast(int c);<br />
48
3.5 Die Klasse DSLUFactor<br />
0 1 2<br />
lokaler Teil von<br />
Zeile 17<br />
globaler Teil von<br />
Zeile 17<br />
lokaler Teil von<br />
Zeile 29<br />
globaler Teil von<br />
Zeile 29<br />
Abbildung 3.7: Aufbau eines MVectors<br />
Freier Teil des<br />
MVectors<br />
zu, deren Name schon verrät, daß sie Informationen an alle anderen PEs verschickt.<br />
Der Parameter c bezeichnet den Untervektor, dessen lokaler Anteil auf die anderen<br />
PEs verteilt werden soll. Die Funktionalität der Methode ist es, den lokalen Teil des<br />
Untervektors c (in Abbildung 3.7 hell unterlegt) in den globalen Bereich des Untervektors<br />
c (in Abbildung 3.7 dunkel unterlegt) auf allen anderen PEs zu schreiben.<br />
Nachdem alle PEs ein broadcast(c) ausgeführt haben, sollen alle PEs den gleichen<br />
Untervektor zur Verfügung haben, wobei die Reihenfolge der Elemente keine Rolle<br />
spielt. Es muß also eine Regelung gefunden werden, wohin ein Prozessor seine Daten<br />
schreibt, ohne Daten von anderen PEs zu überschreiben, da lediglich die Größe und<br />
die Anfangsadresse des globalen Bereichs <strong>für</strong> jedes PE bekannt ist.<br />
Ein Ansatz wäre es, auf jedem PE einen Füllstand des globalen Bereichs zu speichern,<br />
dessen Veränderung nur innerhalb eines kritischen Abschnitts stattfinden kann,<br />
und sich der globale Bereich so<strong>mit</strong> schrittweise füllt, wenn die PEs ihre Daten hineinschreiben.<br />
Dies bedeutet jedoch, daß die Kommunikation synchronisiert ist, d. h.<br />
es kann Overhead entstehen, während ein PE auf die Freigabe des kritischen Bereichs<br />
wartet. Oben wurde jedoch bereits erwähnt, daß Synchronität prinzipiell “böse” ist,<br />
besonders bei einer Broadcast-Methode, die viele Einzelschritte braucht im Gegensatz<br />
zu einer kollektiven Methode wie dem Gossip.<br />
Die in MVector eingesetzte Methode kommt daher völlig ohne Synchronisation<br />
aus. Zur Übertragung der Daten wird shmem_put eingesetzt. Abbildung 3.8 veranschaulicht<br />
das Vorgehen anhand eines Beispiels. Es ist dabei ein Untervektor dargestellt,<br />
der auf vier PEs verteilt ist, von denen drei gezeigt sind. Im oberen Teil ist<br />
der erste Schritt des Broadcast von PE 0 skizziert, im unteren Teil der zweite (von<br />
insgesamt drei). Die hell unterlegten Bereiche sind wieder die lokalen Anteile der<br />
Untervektoren, die dunkel unterlegten bereits eingetroffene Teile des globalen An-<br />
49
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
1)<br />
0 3 2 1 1 0 3 2 2<br />
1 0 3<br />
2)<br />
0 3 2 1 1 0 3 2 2 1 0 3<br />
Abbildung 3.8: Ablauf von MVector::broadcast<br />
teils. Nicht unterlegte Bereiche stellen noch nicht eingetroffene Teile des globalen<br />
Anteils dar, es sind sozusagen die Slots <strong>für</strong> die entfernten PEs, in die ihre Daten hineingeschrieben<br />
werden. 14<br />
Das Vorgehen ist also folgendermaßen. PE 0 schreibt zuerst seine Daten an den<br />
Anfang des globalen Bereichs von PE 1 und liest die Größe des lokalen Bereichs von<br />
PE 1 (<strong>mit</strong>tels shmem_get). Dies ist durch die Pfeile angedeutet. Im zweiten Schritt<br />
schreibt PE 0 seine Daten in den globalen Bereich von PE 2, läßt aber Platz <strong>für</strong> die<br />
Daten von PE 1 (deren Größe es im vorherigen Schritt gelesen hat). Es liest weiterhin<br />
die Größe des lokalen Bereichs von PE 2, die es zu der Größe des lokalen Bereichs<br />
von PE 1 (dem Offset) addiert. In diesem Sinne wird weiter verfahren, bis alle PEs die<br />
Daten von PE 0 haben. Für die anderen PEs ist das Vorgehen analog, sie fangen jedoch<br />
immer beim jeweils folgenden PE im round-robin Sinne an, d. h. PE 3 würde im Beispiel<br />
bei PE 0 beginnen usw. Die verschiedenen Aufrufe von MVector::broadcast<br />
sind dabei völlig autark, d. h. die PEs müssen nichts von den Kommunikationsvorgängen<br />
auf anderen PEs wissen. Der Kommunikationsalgorithmus ist noch einmal in<br />
Algorithmus 3.8 angegeben.<br />
Durchführung der L-loop Mittels MVectors kann die L-loop wesentlich vereinfacht<br />
werden. Die Klasse DSLUFactor hat zwei MVectors uv und lv, die die Pivotzeilen<br />
(d. h. die nichtpermutierte R-Matrix) bzw. die ¯L-Matrix aufnehmen.<br />
Es wird also zunächst <strong>für</strong> jeden Pivot ein Untervektor in uv, der die Pivotzeile<br />
repräsentiert, und ein Untervektor in lv, der die entsprechende Spalte von ¯L repräsentiert,<br />
erzeugt.<br />
14 Diese Slots sind nicht explizit vorhanden, da sie Kenntnis über die Größe der lokalen Anteile auf<br />
anderen PEs voraussetzen, die zu Beginn des Broadcast nicht vorhanden ist.<br />
50
3.5 Die Klasse DSLUFactor<br />
offset:= 0;<br />
pe:= (me + 1) mod p;<br />
{me ist lokales PE, p Anzahl der PEs}<br />
while pe ≠ me do<br />
start:=Anfang des globalen Bereichs auf PE pe;<br />
Schreibe lokale Daten an start+offset auf PE pe<br />
offset:= offset+Größe des lokalen Bereichs auf PE pe;<br />
pe:= (pe + 1) mod p;<br />
end while<br />
Algorithmus 3.8: Algorithmus von MVector::broadcast<br />
Dann wird <strong>für</strong> jeden Pivot zunächst dessen Spalte j s durchlaufen und die Elemente<br />
¯l i js = a i js=a is j s<br />
(vgl. Algorithmus 2.1) dem Untervektor lv[j s ] hinzugefügt. Dabei<br />
werden gleichzeitig die Elemente a i js aus den Zeilen und der Spalte der aktiven Submatrix<br />
(d. h. den SVSets) entfernt. Die Position des Elementes a i js in seiner Zeile<br />
muß dabei erst durch Suchen bestimmt werden, was sich aber nicht vermeiden läßt.<br />
Als nächstes wird der fertige lokale Teil der Spalte von ¯L <strong>mit</strong>tels Broadcast an die<br />
anderen PEs verteilt.<br />
Analog wird <strong>mit</strong> der Pivotzeile verfahren, bloß das diesmal beim Entfernen die<br />
betreffende Spalte durchsucht werden muß. Man könnte zwar vermuten, daß das Entfernen<br />
von Elementen nicht notwendig sei, da die pivotisierten Zeilen bzw. Spalten<br />
nicht noch einmal gebraucht werden (sie werden ja eliminiert), aber durch die doppelte<br />
Abspeicherung sind Elemente z. B. aus der Pivotspalte noch in nicht eliminierten<br />
Zeilen zu finden.<br />
Den vollständigen Algorithmus zur Durchführung der L-loop zeigt Algorithmus<br />
3.9.<br />
Die Update-loop<br />
Nachdem die Pivotzeilen und die relevanten Teile der ¯L-Matrix ausgetauscht wurden,<br />
kann als nächster Schritt in Algorithmus 2.1 die Update-loop in Angriff genommen<br />
werden.<br />
Die Update-loop ist bei weitem der zeitaufwendigste Teil der Faktorisierung, da<br />
ihr AufwandO (n 2 s) beträgt, wenn n s wieder die Dimension der aktiven Submatrix<br />
darstellt. Dies ist klar, da im Fall einer dichtbesetzten Matrix wirklich jedes Element<br />
der aktiven Submatrix geändert wird. Aus diesem Grund verlangt die Update-loop<br />
nach einem besonders effizienten Algorithmus, der viele Optimierungen ermöglicht,<br />
die eine gute Parallelität erzielen.<br />
Wie bereits des öfteren erwähnt, ist eine möglichst asynchrone Abarbeitung der<br />
51
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
for all Pivots a is j s<br />
do<br />
Kreiere Untervektor <strong>für</strong> Spalte j s in lv<br />
Kreiere Untervektor <strong>für</strong> Zeile i s in uv<br />
end for<br />
for all Pivots a is j s<br />
do<br />
for all Elemente a i js in Spalte j s <strong>mit</strong> i ≠ i s do<br />
Füge ¯l i js = a i js=a is j s<br />
lv[j s ] hinzu<br />
Entferne a i js aus Spalte j s<br />
Entferne a i js aus Zeile i<br />
end for<br />
Verteile lokalen Teil von lv[j s ] <strong>mit</strong>tels Broadcast<br />
for all Elemente a is j in Zeile i s do<br />
Füge a is j uv[i s ] hinzu<br />
Entferne a is j aus Zeile i s<br />
Entferne a is j aus Spalte j<br />
end for<br />
Verteile lokalen Teil von uv[i s ] <strong>mit</strong>tels Broadcast<br />
end for<br />
Algorithmus 3.9: Durchführung der L-loop<br />
einzelnen Faktorisierungsschritte von größter Bedeutung <strong>für</strong> einen effizienten parallelen<br />
Algorithmus. Dies läßt sich häufig durch Konzentration der lokalen Bearbeitung<br />
am Anfang der Berechnung erreichen.<br />
Die Ausgangssituation zu Beginn der Update-loop sieht so aus, daß alle lokalen<br />
Teile der MVectors uv und lv aufgebaut sind und an alle anderen PEs verschickt.<br />
Aufgrund der asynchronen Kommunikation der L-loop ist aber das Vorhandensein<br />
sämtlicher globaler Teile von uv und lv nicht gesichert. Dies könnte man durch eine<br />
explizite Synchronisation (z. B. <strong>mit</strong>tels des Barrier-Mechanismus des T3D) erzwingen.<br />
Dies würde jedoch verschenkte Rechenzeit auf den PEs bedeuten, die früh <strong>mit</strong><br />
der L-loop fertig sind. Dies gilt es natürlich zu vermeiden.<br />
Betrachtet man noch einmal Abbildung 3.6, so erkennt man, daß Updates auf PE<br />
0, die aufgrund der Auswahl von Pivots entstehen, die ebenfalls auf PE 0 sind, sich<br />
nur durch wiederum lokale Elemente in uv und lv auf PE 0 ergeben können. Es<br />
kann also nicht sein, daß sich bei der Elimination des Pivots auf PE 0 ein Update<br />
auf PE 0 durch ein Element in der Pivotzeile auf PE 2 (PE 2 hat keine Elemente in<br />
Zeilen, die PE 0 hat) oder durch ein Element in der Pivotzeile auf PE 1 (PE 0 hat<br />
keine Spalten von PE 1) ergibt. Ebensowenig kann sich ein Update auf Prozessor 0<br />
durch ein Element in ¯L von Prozessor 1 (PE 1 hat keine Elemente in Spalten, die PE<br />
52
3.5 Die Klasse DSLUFactor<br />
0 hat) oder von Prozessor 2 (PE 0 hat keine Zeilen von PE 2) ergeben. Wie man sieht,<br />
gilt das natürlich nur <strong>für</strong> Pivotelemente, die von PE 0 stammen, da sich z. B. auch ein<br />
Update durch ein Element in der Pivotzeile des Pivots auf PE 2 ergeben kann (PE 0<br />
und PE 2 haben ja dieselben Spalten). Für andere PEs gilt das <strong>für</strong> PE 0 gesagte jedoch<br />
insofern, als daß auch dort Updates durch Elimination von lokalen Pivots nur lokale<br />
Informationen benötigen.<br />
Es wird also innerhalb der Update-loop eine Unterscheidung in lokale Pivots und<br />
entfernte Pivots getroffen. Zur Elimination von lokalen Pivots ist nur der lokale Teil<br />
von uv und lv nötig, so daß dieser Teil der Update-loop asynchron geschehen kann,<br />
d. h. obwohl evtl. noch nicht alle anderen PEs <strong>mit</strong> der Abarbeitung der L-loop fertig<br />
sind bzw. deren Daten noch nicht eingetroffen sind. Die Struktur von uv und lv<br />
kommt dieser Abarbeitung entgegen – es muß stets nur der Anfang des MVectors<br />
abgearbeitet werden.<br />
Wie ebenfalls aus Algorithmus 2.1 und Abbildung 3.6 ersichtlich, geschehen Updates<br />
immer an den Schnittpunkten von Zeilen aus ¯L und den Spalten der Pivotzeilen.<br />
Hat man also ein Element ¯l i js , so können Updates an allen Elementen a i j <strong>mit</strong> j ≠ j s<br />
auftreten, <strong>für</strong> die a is j ≠ 0 gilt. Bei einer dichtbesetzten Speicherung ist der Algorithmus<br />
zur Ausführung der Updates trivial, man arbeitet <strong>für</strong> jedes Element von ¯L einfach<br />
die Elemente der zugehörigen Pivotzeile ab, multipliziert die Elemente und speichert<br />
die Differenz zum ursprünglichen Wert wieder in der Matrix ab.<br />
Bei der vorliegenden Speicherung als SVSet ist dies nicht möglich. Hat man z. B.<br />
ein Element ¯l i js und ein Element a is j so kann man das Update-Element a i j nicht durch<br />
einfache Indizierung finden. Es muß innerhalb seiner Zeile (oder Spalte) gesucht<br />
werden. Ist es nicht vorhanden, so liegt ein Fill-in vor. Bei festem ¯l i js muß diese<br />
Suche aber <strong>für</strong> jedes a is j erfolgen, was aber eine ständige erneute Suche in derselben<br />
Zeile (oder Spalte) impliziert. Dies muß vermieden werden.<br />
Ein Ansatz zur Lösung des Problems ist es, die Elemente der aktiven Submatrix zu<br />
sortieren (wie in [2] geschehen). Diese Methode wird aber in DSLUFactor aufgrund<br />
des Sortieraufwandes nicht angewandt. Stattdessen wurde ein Verfahren gewählt, das<br />
zwar eine höhere Speicherkomplexität, jedoch eine geringere Zeitkomplexität besitzt.<br />
Der Ansatz ähnelt dem bei der Erkennung von Inkompatibilitäten.<br />
Die Vorgehensweise wird also wieder umgekehrt, d. h. statt das Element zu suchen,<br />
das verändert werden soll, wird <strong>für</strong> jedes Element der zu “updatenden” Matrix<br />
überprüft, ob es verändert wird oder nicht. Dieser Ansatz verhindert jedoch zunächst<br />
nicht das Suchen, sondern verlagert es nur auf die Pivotzeile. Deshalb wird die aktuelle<br />
Pivotzeile in jedem Duchlauf der Update-loop in einen dichtbesetzten Vektor<br />
kopiert, so daß die Updates direkt ohne Suche ausgeführt werden können. Abbildung<br />
3.9 veranschaulicht das Vorgehen. Beim Bearbeiten von Zeile i wird <strong>für</strong> jedes<br />
Element a i j in workp[j] nachgeschaut, ob dort eine 1 steht. Ist dies der Fall, so ist<br />
53
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
workp<br />
<br />
<br />
-1 0 1<br />
<br />
<br />
work<br />
a is l 0.0 a is j<br />
¯l i js<br />
a il a ik a i j<br />
Abbildung 3.9: Durchführung der Update-loop<br />
a is j ≠0 und kann dem Vektor work entnommen werden und der Update-Schritt ausgeführt<br />
werden. Ist in workp[j] eine 0, so braucht kein Update ausgeführt zu werden,<br />
da a is j = 0. Die Arrays workp und work werden zu Beginn der Faktorisierung allokiert<br />
und im Falle von workp <strong>mit</strong> 0 initialisiert. Ist ein Update erfolgt, so wird workp[j]<br />
auf −1 gesetzt, um dies anzuzeigen. Dies ist nötig, da nicht nur “echte” Updates<br />
geschehen können, d. h. Veränderungen der Werte von Nichtnullelementen, sondern<br />
auch Fill-in entstehen kann.<br />
Fill-in wird einem anschließenden Schritt behandelt. Dazu wird <strong>für</strong> jedes noch<br />
nicht behandelte Nichtnullelement in der Pivotzeile (−1 in workp zeigt dies an) ein<br />
neues Element in A kreiert. Diese zweiteilige Behandlung der Updates hat außerdem<br />
den Vorteil, daß die Anzahl der Fill-Elemente vor der Erzeugung des Fill-ins bekannt<br />
ist (nämlich die Differenz aus der Anzahl der Nichtnullelemente und der im ersten<br />
Teil behandelten Elemente). Daher kann genügend Platz im entsprechenden SVector<br />
geschaffen werden, was die Zahl der realloc()s vermindert.<br />
Der Vektor workp könnte in einer sehr speicherkritischen Umgebung noch wegoptimiert<br />
werden, indem man direkt auf work arbeitet und die ausgezeichneten Werte<br />
0 und −1 durch “nicht mögliche” Fließkommazahlen darstellt.<br />
Sehr selten kann es vorkommen, daß ein Element nach dem Update den Wert 0<br />
hat (was natürlich schön ist). Es kann dann aus seiner Zeile gelöscht werden. In der<br />
Spalte muß es jedoch gesucht werden, was aber aufgrund der geringen Häufigkeit des<br />
Auftretens nicht ins Gewicht fällt.<br />
Es soll an dieser Stelle noch einmal auf die Problematik der doppelten Abspeicherung<br />
aller Elemente zurückgekommen werden. Dadurch, daß die Elemente neben<br />
ihrer zeilenweisen Anordnung auch in Spalten abgespeichert sind (nicht jedoch die<br />
numerischen Werte, die nur einfach vorhanden sind), kommt es zu einigen Problemen,<br />
wie bereits angesprochen. Erstens muß beim Entfernen der Elemente stets nach ih-<br />
54
3.5 Die Klasse DSLUFactor<br />
nen in der Spalte gesucht werden, ebenso beim Entfernen aufgrund von Auslöschung<br />
in der Update-loop. Dem steht aber der wesentlich einfachere Zugriff in der L-loop<br />
gegenüber, der diesen Overhead wettmacht. Bei der lokalen Auswahl von Pivotkandidaten<br />
muß zum Überprüfen der Threshold-Bedingung in der Zeile gesucht werden,<br />
um den numerischen Wert zu er<strong>mit</strong>teln, ebenso in der L-loop. Im ersteren Fall kann<br />
das (bei relativ dichtbesetzten <strong>Matrizen</strong>, d. h. ab etwa mehr als 20 Elementen pro Zeile)<br />
zu einem starken Overhead führen, weshalb das Suchen in Spalten abschaltbar ist<br />
(zusätzlich werden die Abfragen nach Akzeptabilität und geringerer Markowitz-Zahl<br />
aus Algorithmus 2.2 vertauscht, um so <strong>für</strong> “schlechte” Elemente gar nicht erst den<br />
Wert bestimmen zu müssen). Man könnte dem jedoch begegnen, indem man auch<br />
die numerischen Werte doppelt abspeichert. Dann muß jedoch in der Update-loop<br />
bei jeder Veränderung auch die entsprechende andere Inkarnation des Wertes verändert<br />
werden, was wiederum eine Suche impliziert, die hier jedoch aufgrund der hohen<br />
Komplexität der Update-loop sehr schwer wiegt. Aus all diesen Gründen wurde es in<br />
DSLUFactor bei der beschriebenen Struktur belassen.<br />
Algorithmus 3.10 zeigt den Teil der Update-loop, in dem die nicht-lokalen Pivotelemente<br />
eliminiert werden. Der vorhergehende lokale Teil ist analog, es muß<br />
lediglich jede Instanz des Wortes “global” durch “lokal” ersetzt werden. Außerdem<br />
entfallen natürlich die “Warte”-statements.<br />
Aktualisierung von globalen Daten<br />
Durch die Elimination der Pivots ändern sich die Werte einiger Datenstrukturen, die<br />
im nächsten Schritt der Faktorisierung wieder gebraucht werden, und die man nicht<br />
jedesmal neu berechnen will.<br />
Änderungen an Row- und Columncounts Durch das Wegfallen der Elemente in<br />
den Pivotzeilen und -spalten sowie durch Fill-in ändert sich die Anzahl der Elemente<br />
in einigen Zeilen und Spalten. Diese Werte, die Row- bzw. Columncounts, werden<br />
bei der Bestimmung der Markowitz-Zahl von Pivotkandidaten im nächsten Schritt<br />
der Faktorisierung benötigt. Sobald die erste Änderung in einer Zeile oder Spalte<br />
auftritt, wird der alte Wert (d. h. der Wert, der vor dem aktuellen Eliminationsschritt<br />
galt) vermerkt. Dies kann in der L-loop (beim Entfernen von Elementen) oder in der<br />
Update-loop (beim Hinzufügen von Elementen, evtl. auch beim Entfernen) passieren.<br />
Die Differenz zwischen altem und neuem Wert wird dann nach der Update-loop in einer<br />
Methode von DSLURow (<strong>für</strong> die Rowcounts) bzw. DSLUCol (<strong>für</strong> die Columncounts)<br />
<strong>mit</strong>tels Gossip ausgetauscht.<br />
55
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
for all globale Pivots a is j s<br />
do<br />
for all Elemente a is j im globalen Teil von uv[i s ] <strong>mit</strong> j ≠ j s do<br />
Warte auf Eintreffen von a is j<br />
workp[j]:= 1;<br />
work[j]:= a is j;<br />
end for<br />
for all Elemente ¯l i js im globalen Teil von lv[j s ] do<br />
Warte auf Eintreffen von ¯l i js<br />
for all Elemente a i j do<br />
if workp[j] = 1 then<br />
workp[j]:= −1;<br />
a i j := a i j − ¯l i js ⋅ work[j];<br />
if a i j = 0 then {dies wird natürlich als a i j ≈ 0 implementiert}<br />
Entferne a i j aus Zeile i<br />
Entferne a i j aus Spalte j<br />
end if<br />
end if<br />
end for<br />
Vergrößere SVector von Zeile i, so daß Fill-in paßt<br />
for all Elemente a is j im globalen Teil von uv[i s ] do<br />
if workp[j] = −1 then<br />
workp[j]:= 1;<br />
else if workp[j] = 1 then<br />
if ¯l i js ⋅ work[j]≠ 0 then<br />
Kreiere Element a i j = −¯l i js ⋅ work[j]<br />
end if<br />
end if<br />
end for<br />
end for<br />
for all Elemente a is j im globalen Teil von uv[i s ] do<br />
workp[j]:= 0;<br />
end for<br />
end for<br />
Algorithmus 3.10: Globaler Teil der Update-loop<br />
Änderungen der IdRings Die beim aktualisieren der Row- und Columncounts<br />
ausgetauschten Informationen werden auch zum Umhängen der Indexelemente in den<br />
IdRings genutzt. Für jede Zeile oder Spalte, in der eine Änderung stattgefunden hat,<br />
56
3.6 Ergebnisse<br />
wird deren Index aus seinem aktuellen Ring ausgehängt und in den neuen eingehängt.<br />
Änderungen der Maximalelemente Bei der Pivotauswahl wird das beträgsmäßig<br />
größte Element jeder Zeile benötigt, anhand derer die Threshold-Bedingung (2.16)<br />
überprüft werden kann. Änderungen hieran können ebenfalls dort auftreten, wo sich<br />
Row- bzw. Columncounts ändern können. Allerdings muß u. U. eine völlige Neuberechnung<br />
des lokalen Maximums erfolgen, wenn sich der Wert des Elementes, das<br />
das aktuelle Maximum darstellt, verringert 15 . Man kann sich diesen Sachverhalt merken<br />
(z. B. indem man den Wert des Maximalbetrages auf −1 setzt), und dann in einem<br />
anschließenden Schritt alle nicht bekannten Maxima er<strong>mit</strong>teln. Dies erfordert aber<br />
wiederum das Untersuchen von ganzen Zeilen, was aber schon während der Abarbeitung<br />
der Update-loop geschieht. Deshalb werden diese beiden Schritte verquickt.<br />
Dazu wird innerhalb der Update-loop vor Untersuchung der Zeile i <strong>für</strong> ein ¯l i js der<br />
Maximalbetrag dieser Zeile auf 0 gesetzt und dann während der Abarbeitung neu er<strong>mit</strong>telt.<br />
Nach Austausch der Ändererungen an den Rowcounts werden die globalen<br />
Maxima der Zeilen <strong>mit</strong>tels Gossip bestimmt. Innerhalb der GossipFunction werden<br />
dabei die empfangenen und lokalen Elemente paarweise verglichen und nur die<br />
größeren im nächsten Schritt weitergesandt.<br />
Die verwendete Vorgehensweise erwies sich als überlegen im Vergleich zu der<br />
nachträglichen Neuberechnung, auch wenn dabei evtl. unnötige Neubestimmungen<br />
passieren. Es müssen jedoch nicht noch einmal die gesamten Zeilen untersucht werden.<br />
3.6 Ergebnisse<br />
In diesem Abschnitt sollen die Ergebnisse beschrieben werden, die <strong>mit</strong> der im vorigen<br />
Abschnitt beschriebenen Implementierung der Klasse DSLUFactor erzielt wurden.<br />
Zunächst wird die Testumgebung beschrieben, innerhalb derer die Ergebnisse er<strong>mit</strong>telt<br />
wurden. Anschließend wird auf Erfahrungen <strong>mit</strong> der gewählten Load-balancing<br />
Strategie eingegangen. Darauffolgend werden die Größen definiert, deren Meßergebnisse<br />
abschließend diskutiert werden.<br />
3.6.1 Testumgebung<br />
Um einen möglichst akkuraten Vergleich verschiedener Implementierungen anstellen<br />
zu können, ist es wichtig, eine ebenso akkurate Umgebung zu definieren, innerhalb<br />
15 Ein Spezialfall hiervon ist natürlich die Elimination oder Auslöschung.<br />
57
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
derer die Tests durchgeführt werden.<br />
Die Funktion main() Zu Testzwecken wurde die Klasse DSLUFactor in eine aufrufende<br />
main()-Funktion eingebettet, die Arbeiten zur Vor- und Nachbereitung der<br />
Faktorisierung sowie zur Ergebniser<strong>mit</strong>tlung übernimmt. Sämtliche der im vorigen<br />
Abschnitt beschriebenen Faktorisierungsparameter können durch Kommandozeilenparameter<br />
beeinflußt werden. Hier wird auch die Geometrie des Prozessorgitters festgelegt.<br />
Die Tests wurden <strong>mit</strong> Prozessorzahlen von 1, 2, 4, 8 und 16 durchgeführt. 16<br />
In allen Tests wurde eine quadratische Aufteilung gewählt, außer bei den Prozessorgittern<br />
1 × 2 sowie 2 × 4. Es sind natürlich auch andere Aufteilungen möglich, diese<br />
wurden jedoch nicht berücksichtigt. Eine Untersuchung in dieser Hinsicht könnte<br />
noch interessant sein. Die Er<strong>mit</strong>tlung sämtlicher Zeiten wurde <strong>mit</strong>tels Methoden der<br />
Klasse Timer durchgeführt, die wie auch SVector etc. im Rahmen von [22] entstanden<br />
ist. Sie benutzt die Funktion times(2).<br />
<strong>Matrizen</strong> Um das Verhalten der Klasse DSLUFactor zu testen, wurden verschiedene<br />
Experimente <strong>mit</strong> einer Reihe von Testmatrizen durchgeführt, die aus zwei verschiedenen<br />
Quellen stammen.<br />
Netlib LP <strong>Matrizen</strong> Zum einen wurden 21 <strong>Matrizen</strong> aus der linearen Programmierung<br />
gewählt, die aus dem Netlib LP set [4] stammen. Diese <strong>Matrizen</strong> liegen im .wun-<br />
Format vor, das die Nichtnullelemente zeilenweise als 3-Tupel (Spalte,Zeile,Wert)<br />
abspeichert, wobei der Wert <strong>mit</strong>tels strtod(3) gelesen werden kann. Tabelle 3.1<br />
zeigt einige Eigenschaften der Netlib LP <strong>Matrizen</strong>.<br />
Harwell-Boeing <strong>Matrizen</strong> Des weiteren wurden 15 <strong>Matrizen</strong> aus der Harwell-<br />
Boeing Sparse Matrix Collection [10] ausgewählt, die aus verschiedenen Anwendungsbereichen<br />
stammen. Diese <strong>Matrizen</strong> liegen im .rua-Format vor, d. h. es sind<br />
reelle unsymmetrische zusammengesetzte 17 <strong>Matrizen</strong> (“real unsymmetric assembled”).<br />
Unsymmetrisch bezieht sich hier nur auf das Format (es sind wirklich alle<br />
Werte abgespeichert), nicht auf die <strong>Matrizen</strong> selber. In der Tat sind unter den ausgewählten<br />
<strong>Matrizen</strong> solche <strong>mit</strong> symmetrischem Aufbau. Diese könnten zwar effizienter<br />
<strong>mit</strong> Algorithmen faktorisiert werden, die die Symmetrie ausnutzen, jedoch dienen<br />
sie nichtsdestotrotz als Indikator <strong>für</strong> die Leistungsfähigkeit eines unsymmetrischen<br />
Faktorisierers. Das .rua-Format ist ein etwas älteres Format, das im Hinblick auf<br />
16 Der T3D läßt nur eine PE-Anzahl p = 2 i zu.<br />
17 im Gegensatz zu nichtzusammengesetzten <strong>Matrizen</strong> (“unassembled”) aus Finite-Elemente Anwendungen,<br />
die aus kleinen dichtbesetzten <strong>Matrizen</strong> zusammengesetzt werden.<br />
58
3.6 Ergebnisse<br />
Matrix n a α b α=nc<br />
agg3 1128 22678 20<br />
pilots 1441 18376 13<br />
ganges 1681 7020 4.2<br />
chr15c 1695 6077 3.6<br />
grow22 1760 11215 6.4<br />
bnl1 1792 26845 15<br />
scfxm2 1940 27546 14<br />
scr12 1992 7950 4.0<br />
maros 2180 34254 16<br />
ganges.ob4 2304 28199 12<br />
pilotwe 2452 49135 20<br />
nesm 2488 39095 16<br />
fit1p 2508 9480 3.8<br />
SM-50a 2723 4301 1.6<br />
SM-50b 2723 4603 1.7<br />
osa030 4279 8532 2.0<br />
hanscom17 4967 14018 2.8<br />
hanscom2 4967 13812 2.8<br />
kamin1809 13542 22968 1.7<br />
stocfor3 16675 51412 3.1<br />
kamin2702 19092 33187 1.7<br />
a Dimension der Matrix<br />
b Anzahl der Nichtnullelemente<br />
c Anzahl der Nichtnullelemente pro Zeile/Spalte<br />
Tabelle 3.1: Testmatrizen aus dem Netlib LP set<br />
numerische Anwendungen in FORTRAN entstand. So sind denn auch die Werte der<br />
Nichtnullelemente nicht <strong>mit</strong>tels strtod(3) lesbar, was die Verwendung dieser <strong>Matrizen</strong><br />
problematisch macht. Es entstand deshalb im Rahmen dieser Arbeit ein kleiner<br />
Wandler rua2wun, der die <strong>Matrizen</strong> in das .wun-Format überträgt. Tabelle 3.2 zeigt<br />
einige Eigenschaften der Harwell-Boeing Testmatrizen.<br />
Wahl der Programmparameter Die default-Werte <strong>für</strong> die frei wählbaren Programmparameter<br />
wurden wie folgt gesetzt. Es werden P = 4 Zeilen oder Spalten<br />
pro gesuchtem Pivotkandidaten durchsucht. Bei p PEs werden maximal c = 4 ⋅ p<br />
Pivotkandidaten pro Eliminationsschritt ausgewählt. Die Pivotkandidaten müssen<br />
59
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
Matrix Disziplin n a α b α=nc symmetrisch<br />
steam2 Ölförderung 600 13760 23 ja<br />
mcfe Astrophysik 765 24382 32 nein<br />
jpwh991 Schaltkreissimulation 991 6027 6.1 nein<br />
sherman1 Ölreservoirmodellierung 1000 3750 3.8 ja<br />
sherman2 Ölreservoirmodellierung 1080 23094 21 ja<br />
sherman4 Ölreservoirmodellierung 1104 3786 3.4 ja<br />
mahindas Wirtschaftsmodellierung 1258 7682 6.1 nein<br />
watt1 Erdöltechnik 1856 11360 6.1 nein<br />
watt2 Erdöltechnik 1856 11550 6.2 nein<br />
west2021 Chemotechnik 2021 7353 3.6 nein<br />
orsreg1 Ölreservoirsimulation 2205 14133 6.4 nein<br />
orani678 Wirtschaftsmodellierung 2529 90158 36 nein<br />
sherman5 Ölreservoirmodellierung 3312 20793 6.3 ja<br />
lns3937 Hydrodynamik 3937 25407 6.5 nein<br />
sherman3 Ölreservoirmodellierung 5005 20033 4.0 ja<br />
a Dimension der Matrix<br />
b Anzahl der Nichtnullelemente<br />
c Anzahl der Nichtnullelemente pro Zeile/Spalte<br />
Tabelle 3.2: Testmatrizen aus der Harwell-Boeing Sammlung<br />
die Threshold-Bedingung (2.16) <strong>mit</strong> u = 0.1 erfüllen. Pivotkandidaten a i j <strong>mit</strong> einer<br />
Markowitz-Zahl M i j > a ⋅ mincount werden nicht in die Menge globaler Pivots<br />
übernommen. a hat defaultmäßig den Wert 4. Darüberhinaus ist frei wählbar, ob nur<br />
Zeilen oder auch Spalten bei der Pivotsuche berücksichtigt werden sollen. Dies wurde<br />
so gehandhabt, daß <strong>für</strong> die Testmatrizen aus dem Netlib LP set sowohl Zeilen als auch<br />
Spalten nach Pivotkandidaten durchsucht werden, bei den <strong>Matrizen</strong> aus der Harwell-<br />
Boeing Sammlung hingegen lediglich Zeilen. Dies liefert durchschnittlich die besten<br />
Ergebnisse.<br />
Sequentielles Programm Das sequentielle Programm wurde in C im Rahmen von<br />
[22] entwickelt und ist stark <strong>für</strong> sehr dünnbesetzte <strong>Matrizen</strong>, wie sie in der linearen<br />
Programmierung auftreten, optimiert. Es arbeitet wesentlich performanter als<br />
“general-purpose” Programme wie MA28 [8] oder Y12M [26], so daß es als Implementierung<br />
des besten bekannten sequentiellen Algorithmus angesehen werden kann.<br />
Diese Sicht wird im folgenden konsequent weiter verfolgt, obwohl sie bei manchen<br />
60
3.6 Ergebnisse<br />
Probleminstanzen nicht ganz zutreffend ist. Dazu später mehr.<br />
3.6.2 Load-Balancing<br />
Um die Wirksamkeit des in 3.5.3 beschriebenen Lastausgleichsverfahrens zu überprüfen,<br />
wurden Versuche <strong>mit</strong> verschiedenen der im letzten Abschnitt aufgeführten<br />
<strong>Matrizen</strong> durchgeführt. Dazu wurde eine Vergleichsimplementierung erstellt, die in<br />
jedem Eliminationsschritt nicht c p = (d p=n2 s )c Pivotkandidaten <strong>mit</strong> d p = |I | ⋅ | J| lokal<br />
auswählt, sondern c p = c=p, wenn p die Anzahl der PEs ist, d. h. keinen Lastausgleich<br />
durch Auswahl verschieden großer Mengen von Pivotkandidaten anstrebt. Während<br />
der Faktorisierung wurde dann <strong>für</strong> jede der Implementierungen die Größe d p von allen<br />
PEs in jedem Eliminationsschritt sowie die Größe n 2 s notiert. d p wird als Größe der<br />
lokalen aktiven Submatrix bezeichnet, n 2 s als Größe der globalen aktiven Submatrix.<br />
Bei einer optimalen Lastverteilung wäre das Verhältnis von d p zu n 2 s genau 1=p auf<br />
allen PEs in jedem Eliminationsschritt, d. h. alle PEs hätten einen gleichgroßen Teil<br />
der globalen aktiven Submatrix. Abbildung 3.10 zeigt einen Ausschnitt 18 der Daten<br />
<strong>für</strong> die Faktorisierung der Matrix orani678 auf 16 PEs im Vergleich zu einer optimalen<br />
Lastverteilung.<br />
Es wurden dabei in jedem Faktorisierungsschritt <strong>für</strong> beide Implementierungen das<br />
Minimum sowie das Maximum der Werte d p er<strong>mit</strong>telt und diese aufgetragen. Eine<br />
kleinere Differenz beider Werte, d. h. ein engeres Anliegen der Kurve am Optimum,<br />
entspricht einer besseren Lastverteilung. Ein ähnliches Bild zeigt sich auch <strong>für</strong> die<br />
meisten anderen untersuchten Testmatrizen. In der Tat sorgt das Lastausgleichsverfahren<br />
<strong>für</strong> eine Verringerung der Lastdifferenzen zwischen den Prozessoren. Besonders<br />
im unteren Teil ist gut zu erkennen, wie das Verfahren einer Divergenz der Kurven<br />
entgegenwirkt, was ohne Lastausgleich nicht der Fall ist.<br />
In einigen wenigen Fällen ist jedoch der Lastausgleich machtlos, wie Abbildung<br />
3.11 zeigt. Hier ist die Faktorisierung der Matrix sherman2 auf 16 PEs dargestellt.<br />
Man erkennt, daß schon zu Beginn der Faktorisierung des Nukleus eine stark ungleiche<br />
Verteilung der Nichtnullelemente auf die PEs vorliegt (die Differenz zwischen<br />
Maximum und Minimum ist sehr groß). Hier “versagt” sozusagen die Grid distribution,<br />
d. h. das in 3.4.4 über die statistische Verteilung von Nichtnullelementen gesagte<br />
trifft hier nicht zu. Die Nichtnullelemente der Matrix sherman2 sind so regelmäßig<br />
verteilt, daß ihre Anordnung der der Grid distribution in etwa entspricht. An dieser<br />
Ungleichverteilung kann auch das passive Load-balancing nichts ändern. Man sieht<br />
18 Am Anfang der Faktorisierung, d. h. außerhalb des Ausschnitts in Richtung größerer aktiver Submatrizen<br />
liegen die Daten sehr eng beieinander.<br />
61
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
150000<br />
<strong>mit</strong> Lastausgleich<br />
ohne Lastausgleich<br />
optimale Lastverteilung<br />
Größe der lokalen aktiven Submatrix<br />
100000<br />
50000<br />
0<br />
0<br />
5.0 ⋅ 10 5 1.0 ⋅ 10 6 1.5 ⋅ 10 6<br />
Größe der globalen aktiven Submatrix<br />
2.0 ⋅ 10 6<br />
Abbildung 3.10: Lastverteilung <strong>mit</strong> und ohne Lastausgleich <strong>für</strong> die Matrix orani678<br />
auf 16 PEs im Vergleich zu optimaler Lastverteilung<br />
zwar, daß im Vergleich zum unausgeglichenen Algorithmus ein Bestreben nach Ausgleich<br />
da ist, was jedoch nicht von Erfolg gekrönt ist, da die initiale Differenz zu<br />
stark war. Dem beschriebenen Verfahren fehlt anscheinend der Handlungsspielraum,<br />
da <strong>mit</strong> maximal 64 (4 ⋅ 16) Pivotkandidaten keine große Umverteilung der Last möglich<br />
ist, zumal auch mehr Fill-in bei höherbelasteten PEs entsteht. Eine Erhöhung der<br />
maximalen Zahl an Pivotkandidaten kann das Problem auch nicht lösen, da in diesem<br />
Falle wesentlich schlechtere Pivotreihenfolgen entstehen, die dann die Zeitkomplexität<br />
dominieren.<br />
Aus diesem Grund wurde <strong>für</strong> die Klasse DSLUFactor noch ein weiteres (statisches)<br />
Lastausgleichsverfahren implementiert, bei dem zu Beginn der Faktorisierung<br />
eine zufällige Permutation der Zeilen und Spalten durchgeführt wird, um so die re-<br />
62
3.6 Ergebnisse<br />
75000<br />
<strong>mit</strong> Lastausgleich<br />
ohne Lastausgleich<br />
optimale Lastverteilung<br />
Größe der lokalen aktiven Submatrix<br />
50000<br />
25000<br />
0<br />
0<br />
2.5 ⋅ 10 5<br />
5.0 ⋅ 10 5<br />
Größe der globalen aktiven Submatrix<br />
7.5 ⋅ 10 5<br />
Abbildung 3.11: Lastverteilung <strong>mit</strong> und ohne Lastausgleich <strong>für</strong> die Matrix sherman2<br />
auf 16 PEs im Vergleich zu optimaler Lastverteilung<br />
gelmäßige Struktur der Matrix “aufzubrechen”. Da zu Beginn der Faktorisierung alle<br />
PEs die gesamte zu faktorisierende Matrix besitzen, ist dies leicht durchzuführen. Es<br />
werden dazu Permutationsarrays σ und τ <strong>mit</strong>tels rand(3) er<strong>mit</strong>telt, die dann bei der<br />
anschließenden Verteilung anhand der Grid distribution die Sicht auf “virtuelle” Zeilen<br />
und Spalten ermöglichen, d. h. will z. B. PE 0 auf Zeile 0 zugreifen (aus der es ja<br />
Nichtnullelemente zugewiesen bekommt), so greift es auf die Zeile σ[0] zu. Ebenso<br />
wird <strong>mit</strong> den Spalten verfahren. Diese Umverteilung muß natürlich beim Zugriff<br />
auf die rechte Seite b berücksichtigt werden, was aber keinen wesentlichen Overhead<br />
erzeugt. An die Werte von σ und τ werden keine besonderen Forderungen gestellt,<br />
sie müssen nur eben “einigermaßen zufällig” sein. In der konkreten Implementierung<br />
wurde sogar aus Einfachheitsgründen σ = τ gewählt. Die Lösung von Ax = b<br />
63
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
bleibt durch diese Permutation unberührt (bis auf Rundungsfehler), da das in Kapitel<br />
2 bezüglich Operationen auf A gesagte gilt. Die Zufallspermutation ist <strong>mit</strong>tels<br />
eines Programmparameters ausschaltbar.<br />
Abbildung 3.12 zeigt wiederum die bei der Faktorisierung von sherman2 auf 16<br />
PEs angefallenen Daten. Diesmal wurde in beiden Fällen der dynamische passive<br />
Lastausgleich aus 3.5.3 eingesetzt, jedoch einmal <strong>mit</strong> vorheriger Zufallspermutation<br />
und einmal ohne.<br />
80000<br />
ohne vorherige Permutation<br />
<strong>mit</strong> vorheriger Permutation<br />
optimale Lastverteilung<br />
Größe der lokalen aktiven Submatrix<br />
60000<br />
40000<br />
20000<br />
0<br />
0<br />
2.0 ⋅ 10 5 4.0 ⋅ 10 5 6.0 ⋅ 10 5<br />
Größe der globalen aktiven Submatrix<br />
8.0 ⋅ 10 5<br />
Abbildung 3.12: Lastverteilung <strong>mit</strong> und ohne initiale Zufallspermutation <strong>für</strong> die Matrix<br />
sherman2 auf 16 PEs im Vergleich zu optimaler Lastverteilung<br />
Man kann deutlich den Einfluß erkennen, den das Verfahren auf die Lastverteilung<br />
hat, die <strong>mit</strong> Zufallspermutation wesentlich ausgeglichener ist. Dies spiegelt sich auch<br />
in der Faktorisierungszeit wider, die bei diesem Problem von 10s auf 7.4s gedrückt<br />
werden konnte.<br />
64
3.6 Ergebnisse<br />
3.6.3 Aufwandsparameter paralleler Algorithmen<br />
Die im nächsten Abschnitt benutzten Begriffe und Metriken, die den Aufwand paralleler<br />
Programme betreffen, sollen hier noch einmal kurz erläutert werden. Ausführlich<br />
sind sie z. B. in [13] oder [11] beschrieben.<br />
Beste sequentielle Zeit Die Funktion T ∗ beschreibt die sequentielle Komplexität<br />
eines Problems. Dies ist der Zeitbedarf des besten bzw. des besten bekannten Algorithmus<br />
und wird im folgenden <strong>mit</strong> der Zeitfunktion der in 3.6.1 erwähnten Implementierung<br />
gleichgesetzt.<br />
Laufzeit und Speedup T p ist die zeitliche Komplexität eines Algorithmus bei der<br />
Ausführung auf p PEs. Zur Bewertung der Laufzeit kann die Maßzahl des Speedup<br />
herangezogen werden, wobei zwischen zwei Varianten unterschieden wird. Der absolute<br />
Speedup<br />
S a p = T ∗<br />
T p<br />
(3.11)<br />
stellt den Vergleich zum besten sequentiellen Algorithmus an, während der relative<br />
Speedup<br />
S r p = T 1<br />
T p<br />
(3.12)<br />
die Laufzeit des parallelen Algorithmus auf p PEs <strong>mit</strong> der auf einem PE vergleicht.<br />
Es gilt im Normalfall 1 ≤ S p ≤ p, wobei natürlich S p ≈ p angestrebt wird. In ungünstigen<br />
Fällen kann der Speedup aber auch kleiner als 1 sein, dann spricht man von<br />
Speeddown. Andererseits kann es aber aufgrund verschiedener Effekte auch zu einem<br />
Speedup kommen, der größer als p ist – man spricht dann von superlinearem<br />
Speedup.<br />
Effizienz<br />
Die Effizienz eines parallelen Programms ist definiert als<br />
E p = T 1<br />
p ⋅ T p<br />
= Sr p<br />
p . (3.13)<br />
Sie sagt also etwas darüber aus, zu welchem Grad der angestrebte Speedup von S r p = p<br />
erreicht wird. Im besten Fall gilt E p = 1. 19<br />
19 Bei superlinearem Speedup natürlich auch mehr.<br />
65
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
Kosten<br />
Die Kosten eines parallelen Programms sind definiert als<br />
C p = p ⋅ T p . (3.14)<br />
Sie erfassen also, wieviel Zeit das Programm insgesamt verbraucht, also auch die Zeit,<br />
die nicht effektiv genutzt werden kann. Sie geben einen Hinweis darauf, wie effizient<br />
die Ressource “CPU-Zeit” genutzt wird.<br />
3.6.4 Faktorisierungszeiten<br />
In diesem Abschnitt werden die Zeiten vorgestellt, die <strong>mit</strong> der in dieser Arbeit entstandenen<br />
Implementierung erreicht wurden. Neben den Ausführungszeiten auf dem Cray<br />
T3D wurden außerdem Zeiten auf einer “herkömmlichen” Uniprozessor-Workstation<br />
er<strong>mit</strong>telt, sowohl <strong>für</strong> die sequentielle Implementierung (s. Seite 60) als auch <strong>für</strong> das<br />
auf DSLUFactor basierende Programm. Beide lassen sich ohne Modifikation <strong>mit</strong>tels<br />
einer Präprozessordirektive sowohl auf dem T3D als auch in einer sequentiellen Umgebung<br />
übersetzen. Bei der Workstation handelt es sich um eine Sun Sparcstation 4,<br />
die über einen 110MHz microSPARC II RISC-Prozessor verfügt.<br />
Tabelle 3.3 zeigt nun die Faktorisierungszeiten <strong>für</strong> die Netlib LP <strong>Matrizen</strong>, jeweils<br />
sequentiell und auf 1, 2, 4, 8 und 16 PEs. Alle Zeiten sind in Sekunden angegeben.<br />
Es fällt auf, daß die parallele Version auf der Workstation (und auf einem PE) <strong>für</strong><br />
alle untersuchten <strong>Matrizen</strong> signifikant langsamer läuft als die sequentielle – nämlich<br />
bis zu einem Faktor von 5.5 bei der Matrix bnl1. Dies ist vornehmlich auf die höhere<br />
Arbeit zurückzuführen, die das parallele Programm leisten muß. Diese entsteht<br />
bei der Überprüfung der Kompatibilität von Pivotkandidaten, der Bereithaltung von<br />
Maximalbeträgen aller Zeilen und dem Aufbau von verteilten Datenstrukturen (Grid<br />
distribution). All dies muß die sequentielle Version nicht leisten.<br />
Für nahezu alle <strong>Matrizen</strong> zeigt sich jedoch ein monoton steigender relativer Speedup<br />
von bis zu 5.5 (bei der Matrix nesm). Vergleichsweise gute Speedups ergeben<br />
sich bei den <strong>Matrizen</strong>, die relativ dichtbesetzt sind, bei denen also ein großer Teil der<br />
Arbeit in der Update-loop verrichtet wird. Leider können diese Geschwindigkeitszuwächse<br />
in fast keinem Fall die Zeitdifferenz bei einem PE wettmachen, wodurch<br />
sich in fast allen Fällen ein absoluter Speeddown ergibt, außer bei den dichtbesetztesten<br />
<strong>Matrizen</strong> (z. B. S16 a = 1.4 bei der Matrix pilotwe <strong>mit</strong> 20 Nichtnullelementen pro<br />
Zeile/Spalte). Auffällig ist auch die teils starke Differenz zwischen den Zeiten auf<br />
der Workstation und dem T3D (die T3D-Zeiten sind durchschnittlich 1.6 mal höher<br />
als die auf der Workstation gemessenen). Hier<strong>für</strong> ist neben dem nicht vorhandenen<br />
2nd level Cache auf dem T3D die bessere Codegenerierung des Compilers auf der<br />
Workstation verantwortlich.<br />
66
3.6 Ergebnisse<br />
Matrix<br />
Workstation<br />
Cray T3D<br />
T ∗ T 1 T ∗ T 1 T 2 T 4 T 8 T 16<br />
agg3 0.39 1.6 0.46 2.1 1.2 0.78 0.54 0.46<br />
pilots 1.3 1.6 1.6 2.1 1.4 1.2 1.0 0.92<br />
ganges 0.06 0.12 0.04 0.18 0.16 0.17 0.18 0.20<br />
chr15c 0.04 0.11 0.06 0.23 0.20 0.20 0.19 0.28<br />
grow22 0.11 0.38 0.15 0.53 0.36 0.31 0.26 0.33<br />
bnl1 0.64 3.5 0.72 4.3 2.5 1.6 1.0 0.79<br />
scfxm2 0.58 2.7 0.67 3.5 2.1 1.3 0.85 0.67<br />
scr12 0.09 0.23 0.11 0.34 0.30 0.28 0.25 0.33<br />
maros 0.64 2.9 0.79 3.6 2.2 1.3 0.87 0.75<br />
ganges.ob4 0.50 1.8 0.56 2.4 1.5 0.93 0.63 0.57<br />
pilotwe 1.2 3.9 1.3 5.1 3.1 1.8 1.16 0.93<br />
nesm 0.85 3.6 0.94 4.6 2.7 1.6 1.0 0.83<br />
fit1p 0.20 0.56 0.25 0.77 0.70 0.55 0.49 0.47<br />
SM-50a 0.03 0.08 0.03 0.19 0.17 0.17 0.17 0.25<br />
SM-50b 0.04 0.09 0.03 0.20 0.17 0.18 0.17 0.23<br />
osa030 0.05 0.14 0.06 0.32 0.25 0.26 0.23 0.28<br />
hanscom17 0.11 0.38 0.15 0.60 0.54 0.52 0.48 0.55<br />
hanscom2 0.13 0.38 0.16 0.65 0.59 0.57 0.50 0.60<br />
kamin1809 0.18 0.65 0.21 1.2 1.1 1.2 1.0 1.0<br />
stocfor3 0.35 1.1 0.37 2.2 1.8 1.7 1.6 1.8<br />
kamin2702 0.28 1.0 0.32 1.9 1.7 1.7 1.6 1.6<br />
Tabelle 3.3: Faktorisierungszeiten der <strong>Matrizen</strong> aus dem Netlib LP set in s<br />
Insgesamt kann gesagt werden, daß sich der Einsatz eines parallelen Faktorisierers<br />
<strong>für</strong> <strong>Matrizen</strong> aus der linearen Programmierung nicht lohnt, zumal die Testmatrizen<br />
schon zu den dichtbesetzteren dort auftretenden <strong>Matrizen</strong> zählen.<br />
Für <strong>Matrizen</strong> aus anderen Anwendungsfeldern gilt dies jedoch nicht, wie die in<br />
Tabelle 3.4 aufgeführten Faktorisierungszeiten zeigen. Vergleicht man zunächst wieder<br />
nur die verschiedenen auf einem PE (bzw. der Workstation) laufenden Versionen,<br />
so fällt wieder auf, daß die sequentielle Version fast immer schneller ist, bis zu einem<br />
Faktor von 3.7 bei der Matrix west2021 und durchschnittlich 1.4-fach schneller. Jedoch<br />
ist die Diskrepanz nicht mehr ganz so stark wie bei den LP-<strong>Matrizen</strong>. In einigen<br />
Fällen (sherman4, orsreg1, watt1, watt2, sherman3) kann die parallele Version die sequentielle<br />
schon auf einem PE überholen. Besonders stark zeigt sich dies bei der Matrix<br />
watt1. Der Grund hier<strong>für</strong> liegt in der “gründlicheren” Suche nach Pivotkandidaten<br />
67
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
Matrix<br />
Workstation<br />
Cray T3D<br />
T ∗ T 1 T ∗ T 1 T 2 T 4 T 8 T 16<br />
steam2 0.55 1.1 0.64 1.4 0.90 1.1 1.1 0.67<br />
mcfe 2.1 2.9 2.5 5.4 4.6 2.5 1.9 1.6<br />
jpwh991 4.5 5.7 5.3 6.9 4.9 2.8 2.0 1.8<br />
sherman1 1.1 1.1 1.1 1.4 1.3 1.0 0.79 0.73<br />
sherman2 21 38 24 57 33 17 11 7.4<br />
sherman4 0.93 0.81 1.1 1.1 1.0 0.77 0.64 0.52<br />
mahindas 0.15 0.31 0.17 0.45 0.30 0.30 0.26 0.28<br />
watt1 40 20 49 26 16 14 9.0 7.8<br />
watt2 33 22 41 27 18 13 10 9.0<br />
west2021 0.10 0.37 0.11 0.48 0.42 0.43 0.41 0.39<br />
orsreg1 21 16 33 20 19 14 10 6.3<br />
orani678 3.3 5.8 3.7 7.8 5.6 3.7 2.4 1.8<br />
sherman5 13 24 16 34 17 13 9.0 8.3<br />
lns3937 41 59 51 75 32 26 16 13<br />
sherman3 93 50 122 62 62 38 26 19<br />
Tabelle 3.4: Faktorisierungszeiten der <strong>Matrizen</strong> aus der Harwell-Boeing Sammlung<br />
in s<br />
<strong>mit</strong> geringer Markowitz-Zahl bei der auf DSLUFactor basierenden Version. Während<br />
die sequentielle nur 1-2 Zeilen oder Spalten nach Pivotkandidaten durchsucht sind<br />
es bei der parallelen Version 4. Dadurch ergibt sich bei der parallelen Version eine<br />
Pivotreihenfolge, die deutlich weniger Fill erzeugt. Grundsätzlich sind viele der Testmatrizen<br />
aus der Harwell-Boeing Sammlung extrem anfällig <strong>für</strong> Änderungen in der<br />
Pivotreihenfolge, so daß schon geringe Änderungen an der Parameterwahl (etwa an<br />
der Größe c der Pivotmenge pro Schritt) dramatische Änderungen an der Faktorisierungszeit<br />
bewirken können. Die in Tabelle 3.5 gezeigten Daten bestätigen diese These<br />
vollauf. Sie zeigt die Anzahl der Nichtnullelemente N p in den Faktoren L und R nach<br />
Beendigung der Faktorisierung. 20<br />
Die Anzahl der Nichtnullelemente bei Ausführung auf dem Cray T3D und der<br />
Workstation sind bei N 1 und N ∗ natürlich gleich, so daß hier nur jeweils ein Wert aufgeführt<br />
ist. Unschwer läßt sich erkennen, daß die sequentielle Version z. B. bei watt2<br />
wesentlich mehr Fill erzeugt als die parallele, was zu der weitaus höheren Faktorisierungszeit<br />
beiträgt. Andererseits gibt es auch Fälle (steam2, sherman5), in denen<br />
20 Natürlich abzüglich der Diagonalelemente von L, die den Wert 1 haben und nicht gespeichert werden.<br />
68
3.6 Ergebnisse<br />
Matrix<br />
N ∗ Anzahl der Nichtnullelemente<br />
N 1 N 2 N 4 N 8 N 16<br />
steam2 21159 27691 29090 41376 35876 35189<br />
mcfe 61081 62966 64566 67555 73170 75378<br />
jpwh991 63716 57856 60658 60857 59319 62525<br />
sherman1 27302 22500 24950 25238 25664 28239<br />
sherman2 166821 256115 256183 269923 233492 241970<br />
sherman4 24029 19373 19530 24218 23088 25297<br />
mahindas 10764 10541 10052 11304 10498 13684<br />
watt1 253568 200715 172279 255394 251497 268170<br />
watt2 251258 171479 184473 273958 255987 280074<br />
west2021 10167 11307 11326 12157 11740 12469<br />
orsreg1 220182 168761 224206 240754 237942 299315<br />
orani678 106383 102709 102409 119087 115897 129680<br />
sherman5 164934 188732 192914 215209 214747 242422<br />
lns3937 361183 377892 349373 387633 397088 391292<br />
sherman3 462220 338805 459085 405611 414707 475670<br />
Tabelle 3.5: Anzahl der Nichtnullelemente in den Faktoren L und R <strong>für</strong> die <strong>Matrizen</strong><br />
aus der Harwell-Boeing Sammlung<br />
die parallele Version eine schlechtere Pivotreihenfolge “erwischt”, was sich in den<br />
Faktorisierungszeiten deutlich niederschlägt.<br />
Bei fast allen <strong>Matrizen</strong> zeigt sich auch hier wieder ein monoton steigender relativer<br />
Speedup (bis zu einem Faktor von 7.7 bei der Matrix sherman2). Im Gegensatz zu den<br />
den LP-<strong>Matrizen</strong> kann hier aber auch ein teils erheblicher absoluter Speedup erzielt<br />
werden (bis zu einem Faktor von 6.3 bei der Matrix watt1 bzw. 3.9 bei lns3937, wenn<br />
man berücksichtigt, daß die sequentielle Zeit bei watt1 schon über der der parallelen<br />
Version bei einem PE lag).<br />
Besonders interessant sind in diesem Zusammenhang zwei Phänomene, die beim<br />
Übergang von einem auf zwei PEs entstehen. Zum einen kann bei der Matrix lns3937<br />
ein superlinearer Speedup beobachtet werden. Dies ist wieder auf eine unterschiedliche<br />
Pivotreihenfolge zurückzuführen. Bei zwei PEs wird eine Pivotreihenfolge gewählt,<br />
die weniger Fill erzeugt als bei einem PE (die Anzahl der Nichtnullelemente<br />
in L und R ist bei einem PE knapp 10% höher), was glücklicherweise den geringeren,<br />
durch die Parallelisierung entstandenen Zeitaufwand noch weiter drückt, wodurch der<br />
superlineare Speedup entsteht.<br />
Andererseits kann auch der gegenteilige Effekt auftreten, der bei sherman3 be-<br />
69
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
sonders gut sichtbar wird. Hier ist die Zeit auf zwei PEs genauso groß wie auf einem<br />
PE, was sich durch die stark erhöhte Zahl von Nichtnullelementen, die durch<br />
eine ungünstige Pivotreihenfolge hervorgerufen wird, erklären läßt. Insgesamt kann<br />
ein Ansteigen der Nichtnullelementeanzahlen bei Erhöhung der PE-Zahl festgestellt<br />
werden (der durchschnittliche Faktor bei 16 PEs im Vergleich zu einem PE liegt bei<br />
1.28). Dies ist auf die größeren Pivotmengen zurückzuführen, die beim Einsatz von<br />
mehr PEs ausgewählt werden. Die schlechte Pivotreihenfolge ist aber nicht allein <strong>für</strong><br />
den Effekt bei sherman3 verantwortlich. Hinzu kommt noch ein Load-balancing Problem,<br />
ähnlich dem in Abbildung 3.11 gezeigten. Wendet man jedoch hier die initiale<br />
Zufallspermutation an, ist zwar die Lastverteilung gut, jedoch ergibt sich eine noch<br />
schlechtere Pivotreihenfolge, so daß die Zeit wiederum nicht besser wird (die Zeit auf<br />
einem PE <strong>mit</strong> Zufallspermutation ist ca. doppelt so hoch wie hier angegeben). 21<br />
Interessant ist auch der Vergleich des in dieser Arbeit entstandenen Algorithmus<br />
<strong>mit</strong> anderen publizierten Implementierungen. In [2] werden z. B. folgende Ergebnisse<br />
<strong>für</strong> eine Implementierung auf dem Cray T3D angegeben:<br />
Matrix T ∗ T 4 T 16<br />
steam2 9.9 3.6 1.9<br />
jpwh991 16 6.7 3.2<br />
sherman1 3.0 1.9 1.4<br />
sherman2 81 23 8.1<br />
lns3937 205 82 29<br />
Dies ergibt natürlich erhebliche relative Speedups (bis zu 10 bei der Matrix sherman2)<br />
und da<strong>mit</strong> auch auch sehr gute Effizienzen (bis zu 62%), die von der in dieser<br />
Arbeit entstandenen Implementierung nicht erreicht werden. Wie bereits in 3.6.3 beschrieben,<br />
muß jedoch immer der Vergleich zum “besten bekannten” sequentiellen<br />
Algorithmus gesucht werden. Legt man die in dieser Arbeit benutzte sequentielle Implementierung<br />
zugrunde, so ist der absolute Speedup und da<strong>mit</strong> die Kosten bei dem<br />
auf DSLUFactor basierenden Programm durchweg besser.<br />
Ein Vergleich zu der in [21] vorgestellten Implementierung (die den gleichen Algorithmus<br />
wie [2] einsetzt) ist schwer möglich, obwohl auch dort Zeiten publiziert<br />
sind. Es wird dort nämlich ein MIMD-<strong>Parallelrechner</strong> eingesetzt, der aus einem Netzwerk<br />
von INMOS T800-20 Transputern aufgebaut ist, wobei die Netzwerktopologie<br />
ein quadratisches Gitter ist. Die <strong>mit</strong> 20MHz getakteten Transputer sind natürlich nicht<br />
<strong>mit</strong> den 150MHz DEC Alpha des T3D vergleichbar, weshalb schon die sequentiellen<br />
Zeiten bei [21] teils hundertfach höher sind. Es wird aber auch dort über das An-<br />
21 “caught between a rock and a hard place” würde der Amerikaner wohl sagen.<br />
70
3.6 Ergebnisse<br />
steigen der Zahl von Nichtnullelementen etwa im gleichen Maße wie bei der hier<br />
vorgestellten Implementierung beim Einsatz von mehr PEs berichtet.<br />
Abbildung 3.13 zeigt abschließend noch einmal den absoluten Speedup, der durch<br />
das auf DSLUFactor basierende Programm erzielt wird.<br />
10<br />
LP <strong>Matrizen</strong><br />
Harwell-Boeing <strong>Matrizen</strong><br />
alle <strong>Matrizen</strong><br />
Maximum<br />
Minimum<br />
optimaler Speedup<br />
absoluter Speedup S a p<br />
1<br />
0.1<br />
1<br />
2<br />
8<br />
16<br />
4<br />
Anzahl der Prozessorelemente p<br />
Abbildung 3.13: absoluter Speedup in Abhängigkeit von der Anzahl an Prozessoren<br />
im Vergleich zum optimalen Speedup<br />
Dabei ist einerseits der durchschnittliche absolute Speedup jeweils <strong>für</strong> die LP-<br />
<strong>Matrizen</strong>, die <strong>Matrizen</strong> aus der Harwell-Boeing Sammlung und <strong>für</strong> alle Testmatrizen<br />
zusammen dargestellt. Außerdem ist der beste absolute Speedup sowie der schlechteste<br />
Speedup von allen Testmatrizen aufgetragen. Hier fällt wieder der “Knick” beim<br />
Übergang von zwei auf vier PEs auf, der durch den superlinearen Speedup bei der<br />
Testmatrix lns3937 und 2 PEs sowie den “unplanmäßigen” Zeitvorsprung schon bei<br />
einem PE bei der Matrix watt1 hervorgerufen wird.<br />
71
3 Parallele <strong>LR</strong>-<strong>Zerlegung</strong><br />
72
4 Parallele Lösung von<br />
Dreieckssystemen<br />
Im vorigen Kapitel wurde ein paralleler Algorithmus zur <strong>Zerlegung</strong> einer Koeffizientenmatrix<br />
eines linearen Gleichungssystems in eine untere Dreiecksmatrix L und eine<br />
obere Dreiecksmatrix R entwickelt. Wie aus Kapitel 2 bekannt, ist es jedoch zur Lösung<br />
des linearen Gleichungssystems Ax = b ⇔ (<strong>LR</strong>)x = b, d. h. zur Bestimmung des<br />
Lösungsvektors x darüberhinaus notwendig, zwei Dreieckssysteme <strong>mit</strong> den <strong>Matrizen</strong><br />
L und R zu lösen, was einfach <strong>mit</strong>tels Vorwärts- und Rückwärtssubstitution erreicht<br />
werden kann. Der zeitliche Aufwand zur Durchführung der Vor- und Rückwärtssubstitution<br />
beträgt nur einen geringen Bruchteil des zur Faktorisierung nötigen (im<br />
Promillebereich). In typischen Anwendungen will man aber viele Lösungen <strong>mit</strong> unterschiedlichen<br />
rechten Seiten berechnen, weshalb der gesamte Aufwand zur Lösung<br />
den zur Faktorisierung sogar überschreiten kann. Ausgehend von einem sequentiellen<br />
Algorithmus soll in diesem Kapitel ein paralleles Programm entwickelt werden, das<br />
aus den Faktoren L und R sowie einer rechten Seite b das Ergebnis x des linearen Gleichungssystems<br />
Ax = b bestimmt. Dabei müssen die Permutationen, die während der<br />
Faktorisierung der Matrix A vorgenommen wurden, beachtet werden, um das richtige<br />
Ergebnis zu erhalten. Sie beschränken sich aber auf die Umsortierung der Elemente<br />
von b bzw. z, so daß sie bei der Entwicklung des Algorithmus keine Rolle spielen und<br />
demzufolge dort auch nicht behandelt werden.<br />
4.1 Sequentieller Algorithmus<br />
Als Ergebnis der <strong>LR</strong>-<strong>Zerlegung</strong> einer Matrix A hat man die untere Dreiecksmatrix L<br />
und die obere Dreiecksmatrix R, wobei A = <strong>LR</strong>. Wegen<br />
b = Ax = (<strong>LR</strong>)x = L(Rx) ⇒ Ly = b, Rx = y<br />
erhält man die Lösung von Ax = b durch Lösung von Ly = b <strong>mit</strong>tels Vorwärtssubstitution<br />
und anschließende Lösung von Rx = y <strong>mit</strong>tels Rückwärtssubstitution.<br />
73
4 Parallele Lösung von Dreieckssystemen<br />
Vorwärtssubstitution<br />
Hier hat man es <strong>mit</strong> einem Gleichungssystem der Form<br />
y 1 = d 1<br />
.<br />
. .. .<br />
(4.1)<br />
l n−1,1 y 1 ++ y n−1 = d n−1<br />
l n1 y 1 ++l y n−1 + y n = d n<br />
nn−1<br />
zu tun, wobei gegenüber dem allgemeinen Fall die Koeffizienten l ii = 1 angenommen<br />
wurden, wie sie bei der <strong>LR</strong>-<strong>Zerlegung</strong> entstehen. Wie bei (2.3) erhält man auch hier<br />
die Lösung durch Einsetzen von Teillösungen, diesmal von oben her, bis y den endgültigen<br />
Lösungsvektor enthält:<br />
y 1 = d 1<br />
y 2 = d 2 − l 21 y 1<br />
(4.2)<br />
.<br />
l nn−1 y n−1<br />
Man kann jedoch in äquivalenter Weise auch ein spaltenweises Vorgehen wählen, bei<br />
dem man zunächst y = d setzt und dann sukzessive Modifikationen an y vornimmt:<br />
y n = d n − l n1 y 1 −:::−<br />
y i = y i − l i1 y 1 i = 2,:::,n<br />
y i = y i − l i2 y 2 i = 3,:::,n<br />
.<br />
(4.3)<br />
y n = y n − l nn−1 y n−1<br />
Nach jedem der Schritte in (4.3) ist ein y i fertig, so daß es im nächsten Schritt zur<br />
Durchführung der Modifikationen bereitsteht. Welche der Lösungsvarianten man bevorzugt,<br />
hängt von der Datenverteilung von L ab: Bei einer spaltenweisen Aufteilung<br />
böte sich letzgenannte Variante an, anderenfalls erstere. Bei einer stark dünnbesetzten<br />
rechten Seite hat das spaltenweise Vorgehen den Vorteil, daß viele unnötige Operationen<br />
<strong>mit</strong> y j = 0 bzw. z j = 0 entfallen, da Zeile j von (4.3) nur gerechnet werden<br />
muß, falls y j ≠ 0. In Algorithmus 4.1 ist der Algorithmus zur sequentiellen Durchführung<br />
der Vorwärtssubstitution nach der Spaltenmethode wie in [18] angegeben. Ein<br />
Algorithmus nach der Zeilenmethode ist z. B. in [12] zu finden.<br />
74
4.1 Sequentieller Algorithmus<br />
y:= d;<br />
for j = 1 to n − 1 do<br />
for i = j + 1 to n do<br />
y i := y i − l i j ⋅ y j ;<br />
end for<br />
end for<br />
Algorithmus 4.1: Sequentielle Vorwärtssubstitution nach der Spaltenmethode<br />
Rückwärtssubstitution Bei der Rückwärtssubstitution wird ein Gleichungssystem<br />
Rz = y der Form (2.2) <strong>mit</strong> der Lösung von (4.1) als rechter Seite gelöst. Dies kann<br />
nach der Zeilenmethode <strong>mit</strong>tels (2.3) geschehen oder aber in Analogie zu (4.3) <strong>mit</strong><br />
Hilfe der Spaltenmethode, wobei zuerst z = y gesetzt wird:<br />
z n = z n<br />
r nn<br />
z i = z i − r in z n i = 1,:::,n<br />
z n−1 =<br />
z n−1<br />
r n−1n−1<br />
z i = z i − r in−1 z n−1 i = 1,:::,n<br />
− 1<br />
− 2<br />
(4.4)<br />
z<br />
z n−2 = n−2<br />
r n−2n−2<br />
.<br />
z 1 = z 1 − r 12 z 2<br />
z 1 = z 1<br />
r 11<br />
In diesem Falle müssen die z i jeweils noch durch r ii geteilt werden, da meist r ii ≠ 1.<br />
Algorithmus 4.2 zeigt den Algorithmus zur Durchführung der Rückwärtssubstitution.<br />
z:= y;<br />
for j = n to 2 do<br />
z j := z j=r j j ;<br />
for i = j − 1 to 1 do<br />
z i := z i − r i j ⋅ z j ;<br />
end for<br />
end for<br />
z 1 := z 1=r 11 ;<br />
Algorithmus 4.2: Sequentielle Rückwärtssubstitution nach der Spaltenmethode<br />
75
4 Parallele Lösung von Dreieckssystemen<br />
4.2 Paralleler Algorithmus<br />
Der parallele Algorithmus zur Umsetzung der Algorithmen 4.1 und 4.2 soll <strong>mit</strong> der in<br />
3.4.1 beschriebenen PCAM-Methode entwickelt werden.<br />
4.2.1 Partitionierung<br />
Auch hier bietet sich, wie in 3.4.2, die Domain decomposition an. Teilt man nämlich<br />
wieder jeweils genau ein Element der <strong>Matrizen</strong> L und R einem Task zu, so lassen sich<br />
jeweils die inneren for-Schleifen der Algorithmen 4.1 und 4.2 parallelisieren. Die<br />
äußeren Schleifen lassen sich aufgrund dieser Aufteilung nicht parallelisieren, da zur<br />
Berechnung von y j der Wert von y j−1 bzw. zur Berechnung von z j der Wert von z j+1<br />
benötigt wird.<br />
375<br />
Die durch die Elimination kompatibler Pivotelemente in L und R entstandene<br />
Struktur macht es jedoch möglich, mehrere Durchläufe der äußeren Schleife zu einem<br />
zusammenzufassen, der parallel abgearbeitet werden kann. L besitzt folgende<br />
Struktur:<br />
264l 11<br />
0 l 22<br />
.<br />
. .. . ..<br />
s 1<br />
. l s1 +1s 1<br />
<br />
+1<br />
0 l s1 +2s 1 +2<br />
.<br />
. .. . ..<br />
0 0<br />
.<br />
00l s1<br />
l s2 s 2<br />
. ..<br />
(4.5)<br />
Die l ii haben alle den Wert 1. Die Zeilen bzw. Spalten 1 bis s 1 sowie s 1 + 1 bis s 2<br />
wurden jeweils in einem Schritt eliminiert. In Algorithmus 4.1 würden die y j <strong>mit</strong><br />
j = 1,:::,s 1 beim Durchlauf der äußeren Schleife von j = 1 bis j = s 1 nicht modifiziert,<br />
d. h. sie sind schon vor Beginn der Schleife fertig. Gleiches gilt <strong>für</strong> die y j <strong>mit</strong><br />
j = s 1 + 1,:::,s 2 nach Beendigung des Schleifendurchlaufs <strong>für</strong> j = s 1 . Mit anderen<br />
Worten, die äußere Schleife von Algorithmus 4.1 kann <strong>für</strong> j = 1 bis j = s 1 sowie <strong>für</strong><br />
j = s 1 + 1 bis j = s 2 usw. parallel ausgeführt werden. Die s i markieren dabei jeweils<br />
einen parallelen Eliminationsschritt. Analog dazu kann man auch bei der Rückwärtssubstitution<br />
verfahren.<br />
76
4.2 Paralleler Algorithmus<br />
4.2.2 Kommunikation<br />
Bei einer wie oben beschriebenen Aufteilung kann man zwei distinkte Kommunikationen<br />
identifizieren:<br />
1. Die von den einzelnen Tasks bei der Durchführung der inneren (parallelen)<br />
Schleife berechneten Modifikationen müssen summiert werden, um den Wert<br />
eines y j bzw. eines z j zu erhalten. Dabei handelt es sich um eine strukturierte,<br />
statische und beidseitig synchrone Kommunikation, die nur zwischen einer<br />
kleinen Menge von Tasks abgewickelt wird, d. h. sie ist auch lokal.<br />
2. Die fertigen y j und z j müssen an die Tasks gesandt werden, die diese bei der<br />
Durchführung “ihrer” inneren Schleife zur Errechnung der Modifikationen benötigen,<br />
also an diejenigen, die Elemente der Spalte j + 1 bzw. j − 1 besitzen.<br />
Dies impliziert ebenfalls eine strukturierte und statische Kommunikation. Sie<br />
ist überdies synchron, da die Berechnung der Modifikationen nicht ohne den<br />
Wert der y j bzw. z j erfolgen kann. Man könnte sich auch eine asynchrone<br />
Kommunikation vorstellen, bei der die Tasks, die den Wert eines y j bzw. eines<br />
z j benötigen, sich diesen von anderen Tasks holen, ohne diese zu informieren.<br />
Jedoch muß der Wert ja fertig sein, so daß es wieder auf eine Synchronisation<br />
hinausläuft. Sie ist auch lokal, da die Werte nur an die Tasks versandt werden<br />
müssen, die diese wirklich brauchen.<br />
4.2.3 Agglomeration und Abbildung<br />
Die idealisierte Herangehensweise in der Partitionierungsphase erweist sich natürlich<br />
als undurchführbar, da eine viel zu feine Granularität gewählt wurde. Besonders bei<br />
der ersteren Kommunikation ergibt dies ein zu hohes Verhältnis zwischen Kommunikation<br />
und Berechnung. Um die Granularität zu erhöhen, bietet es sich an, alle<br />
Tasks einer Zeile, d. h. diejenigen, die Modifikationen an y i bzw. z i <strong>für</strong> ein i berechnen,<br />
zusammenzufassen. So entfällt die erstere Kommunikation, da die Summe der<br />
Modifikationen an einem y i bzw. z i dann von einem Task berechnet wird. Dieser Task<br />
übernimmt auch den Divisionsschritt z j := z j=r j j , der bei der Rückwärtssubstitution<br />
notwendig ist.<br />
Diese Art der Agglomeration läßt sich noch fortführen, wenn man auch noch mehrere<br />
Zeilen auf einem Task vereinigt, die einem Block (ähnlich wie panels bzw. supernodes<br />
in [18]) angehören. Als ein Block wird eine Menge von benachbarten Zeilen<br />
oder Spalten bezeichnet, die eine diagonale Submatrix auf der Diagonalen besitzen.<br />
Also bilden z. B. die Zeilen 1 bis s 1 aus (4.5) einen Block. Durch eine Blockaufteilung<br />
77
4 Parallele Lösung von Dreieckssystemen<br />
ist genau ein Task <strong>für</strong> die y i und z i eines Blocks verantwortlich, wodurch mehrere Instanzen<br />
der zweiten Kommunikation, die in 4.2.2 identifiziert wurde (Versenden von<br />
fertigen y i und z i ), zusammengefaßt werden können, d. h. der <strong>für</strong> die y i bzw. z i eines<br />
Blocks verantwortliche Task verschickt deren Werte an die Tasks, die sie benötigen,<br />
“en bloc”, was Latenzzeiten erspart.<br />
Typischerweise sind mehr Blöcke als PEs vorhanden. Sind bc Blöcke vorhanden,<br />
so werden diese wie folgt auf p PEs verteilt: 1<br />
Block B i → Prozessor q = i mod p, ∀i: i ∈ {0,:::,bc − 1} (4.6)<br />
Die zeilenweise Agglomeration von Tasks legt auch eine zeilenweise Speicherung<br />
der Elemente von L und R sowie den Einsatz einer Zeilenmethode wie in (4.2) nahe.<br />
Dies hat jedoch die oben erwähnten Nachteile bei Gleichungssystemen <strong>mit</strong> sehr <strong>dünnbesetzter</strong><br />
rechter Seite. Deshalb wird eine spaltenweise Speicherung vorgenommen,<br />
bei der die innere Schleife nur bei y j ≠ 0 bzw. z j ≠ 0 durchgeführt werden muß. Um<br />
trotzdem die erstere Kommunikation aus 4.2.2 zu vermeiden, werden die Elemente<br />
zeilenweise auf die PEs verteilt.<br />
Abbildung 4.1 veranschaulicht noch einmal die Blockverteilung exemplarisch <strong>für</strong><br />
ein Beispielsystem Ly = d. Die diagonale Linie symbolisiert die l ii = 1. Der unterlegte<br />
Bereich innerhalb der Matrix symbolisiert den Teil, in dem sich Nichtnullelemente<br />
befinden können. Die Zeilen und Spalten wurden in vier Blöcke unterteilt. Der dunkel<br />
unterlegte Bereich stellt die Zeilen des Blocks B 2 dar, deren Elemente allesamt einem<br />
PE zugeordnet sind. Deshalb berechnet dieses PE alle Modifikationen <strong>für</strong> y B2 (d. h.<br />
alle y i <strong>mit</strong> i ∈ B 2 ). Die Modifikationen werden spaltenweise von links nach rechts<br />
vorgenommen. Um dies geometrisch zu verdeutlichen, wurde der Vektor y zweimal,<br />
rechts und oben, dargestellt. Man kann sich die Vorgehensweise so vorstellen, daß<br />
die Werte der y B j<br />
über der Matrix benötigt werden, um die Modifikationen an den y Bi<br />
rechts neben der Matrix zu berechnen, jeweils beginnend <strong>mit</strong> i = j + 1. Wenn dies <strong>für</strong><br />
alle Spalten eines Blocks B j geschehen ist, so sind die Elemente y Bi <strong>mit</strong> i = j +1 fertig<br />
und können vom da<strong>für</strong> verantwortlichen PE “nach oben” gesandt werden und so im<br />
nächsten Schritt <strong>für</strong> weitere Modifikationen zur Verfügung stehen.<br />
In Algorithmus 4.3 ist der parallele Algorithmus zur Vorwärtssubstitution angegeben.<br />
Der Algorithmus zur Rückwärtssubstitution ist analog, nur daß dort eben die<br />
Blöcke vom letzten zum ersten durchlaufen werden und die z j vor dem Versenden<br />
noch durch r j j geteilt werden müssen.<br />
Eine mögliche Veränderung des Algorithmus besteht darin, die Schleife über m<br />
aufzuteilen in einen Teil <strong>für</strong> m = k + 1, der vor dem Versenden von PE k + 1 mod p<br />
1 Die Blöcke und Prozessoren seien dabei in Hinblick auf eine Implementierung von 0 bis bc − 1 bzw.<br />
von 0 bis p − 1 numeriert.<br />
78
4.2 Paralleler Algorithmus<br />
y B0 y B1 y B2 y B3<br />
y B0<br />
B 0<br />
B 1<br />
y B1<br />
B 2<br />
y B2<br />
B 3<br />
B 0 B 1 B 2 B 3<br />
y B3<br />
Abbildung 4.1: Blockverteilung zur Lösung von Ly = d<br />
ausgeführt wird, und einen <strong>für</strong> die restlichen m, der nach dem Versenden ausgeführt<br />
wird. Auf diese Art werden die anderen PEs schneller <strong>mit</strong> dem Ergebnis von y Bk+1<br />
versorgt und können so früher weiterrechnen. Andererseits muß dann die umgebende<br />
Schleife über j zweifach ausgeführt werden. Außerdem muß sichergestellt sein, daß<br />
die Werte von l i j <strong>für</strong> i ∈ B k+1 zusammenhängend abgespeichert wurden, um sie nicht<br />
noch innerhalb von L suchen zu müssen. Beide Varianten (<strong>mit</strong> und ohne Aufteilung<br />
der Schleife über m) wurden implementiert. Die Variante <strong>mit</strong> Aufteilung der Schleife<br />
brachte aber keine Verbesserung der Performance, so daß sie im folgenden nicht mehr<br />
behandelt wird.<br />
79
4 Parallele Lösung von Dreieckssystemen<br />
y:= d;<br />
for k = 0 to bc − 2 do<br />
Warte auf y Bk<br />
for all j ∈ B k do<br />
if y j ≠ 0 then<br />
{me ist lokales PE, p die Anzahl der PEs}<br />
for all m ∈ {k + 1,:::,bc − 1} <strong>mit</strong> m mod p = me do<br />
for all l i j ≠ 0 <strong>mit</strong> i ∈ B m do<br />
y i := y i − l i j ⋅ y j ;<br />
end for<br />
end for<br />
end if<br />
end for<br />
if verantwortlich <strong>für</strong> Block k + 1 then<br />
versende y Bk+1 an alle PEs<br />
end if<br />
end for<br />
Algorithmus 4.3: Parallele Vorwärtssubstitution<br />
4.3 Implementierung des parallelen Algorithmus<br />
Datenverteilung Die Datenverteilung wird als separate Methode implementiert,<br />
d. h. unabhängig vom eigentlichen Lösungsalgorithmus. Oft will man nämlich viele<br />
Gleichungssysteme <strong>mit</strong> der gleichen Koeffizientenmatrix A aber unterschiedlichen<br />
rechten Seiten b lösen. In diesem Fall muß man die Verteilung der Nichtnullelemente<br />
in L und R nicht jedesmal neu vornehmen, sondern kann dies einmalig zu Beginn der<br />
Lösung machen.<br />
Da die Nichtnullelemente von L und R nach der <strong>LR</strong>-<strong>Zerlegung</strong> auf allen PEs vollständig<br />
repliziert sind, kann die Neuverteilung gemäß (4.6) lokal erfolgen. Die Erfassung<br />
der da<strong>für</strong> nötigen Information über die Blockstruktur wurde in die Faktorisierung<br />
eingebaut, und zwar in Form eines DataArrays, das die einzelnen s i wie in (4.5)<br />
festhält.<br />
Zum Zwecke der Neuverteilung werden die Elemente aus den MVectors lv und<br />
uv (die die Nichtnullelemente von L bzw. R enthalten) in DataArrays von Strukturen,<br />
die ein int (<strong>für</strong> den Zeilen- bzw. Spaltenindex) sowie ein double (<strong>für</strong> den Wert)<br />
enthalten, kopiert. Jedes PE greift sich nur die <strong>für</strong> es relevanten Nichtnullelemente<br />
heraus. Dabei wird gleichzeitig eine explizite Permutation vorgenommen, so daß die<br />
Indexwerte die permutierten <strong>Matrizen</strong> reflektieren.<br />
80
4.3 Implementierung des parallelen Algorithmus<br />
Des weiteren werden die bei der Rückwärtssubstitution benötigten Diagonalelemente<br />
r ii von R extrahiert, d. h. nicht in den DataArrays bei den anderen r i j <strong>mit</strong> i ≠ j<br />
belassen, sondern in einem eigenen DataArray untergebracht, da<strong>mit</strong> man sie bei der<br />
Rückwärtssubstitution nicht erst suchen muß. Es wird auch gleich der reziproke Wert<br />
abgespeichert, da die Fließkommadivision bei dem in den PEs des T3D eingesetzten<br />
Mikroprozessor um ein Vielfaches länger dauert als eine Multiplikation, die dann bei<br />
der (potentiell vielfach durchgeführten) Rückwärtssubstitution anfällt. 2<br />
Die Vektoren d, y und z werden auf jedem PE in demselben Array von doubles<br />
<strong>mit</strong> einer auf allen PEs gleichen Startadresse untergebracht.<br />
Permutationen Die Permutation von b nach d wird wieder explizit durchgeführt,<br />
und zwar so, daß sich jedes PE nur die Werte von d i = b πi der Blöcke in das oben beschriebene<br />
Array kopiert, an denen es Modifikationen ausführt. Bei der Permutation<br />
von z nach x werden alle Werte der z j nach x ρ j<br />
kopiert, da z komplett auf allen PEs<br />
repliziert ist.<br />
Vor- und Rückwärtssubstitution Bei der Vorwärtssubstitution werden die Blöcke<br />
0 und bc − 2 gesondert behandelt. Im Falle von Block 0 entfallen die “Warte”-<br />
statements und bei der Behandlung von Block bc − 2 kann schon die Multiplikation<br />
der y Bbc−1 <strong>mit</strong> den 1=r ii <strong>für</strong> i ∈ B bc−1 erfolgen, die dann schon einen Teil von z bilden.<br />
Besonders kritisch im Hinblick auf die Performance ist die Synchronisation (die<br />
“Warte”-statements in Algorithmus 4.3) sowie das Versenden der fertigen Segmente<br />
von y. Da die Anfangsadressen des y repräsentierenden Arrays auf allen PEs gleich<br />
sind, kann das Versenden <strong>mit</strong> einem <strong>für</strong> alle PEs gleichen Aufruf von shmem_put<br />
erfolgen.<br />
Die Synchronisation gestaltet sich hingegen schwieriger. Es wäre z. B. möglich,<br />
ein weiteres Array zu führen, das <strong>für</strong> jeden Block eine empty/full-Semantik realisiert.<br />
Das bedeutet, daß zu Beginn des Lösens alle Elemente dieses Arrays auf 0 gesetzt<br />
würden, und dann zusätzlich zu den y Bk <strong>mit</strong>tels eines weiteren shmem_put noch ein<br />
von 0 verschiedener Wert in die k-te Stelle des Arrays geschrieben würde, der das<br />
Eintreffen des Segments von y signalisiert. Das Problem bei einem solchen Vorgehen<br />
ist das zweite shmem_put. In [16] wird eine Latenzzeit von wenigstens 1µs <strong>für</strong> die<br />
SHMEM-Routinen angegeben. Das entspricht 150 Taktzyklen, während denen maximal<br />
150 Fließkommamultiplikationen (realistisch etwa 50 Fließkommamultiplikationen)<br />
berechnet werden könnten. Legt man eine sehr dünnbesetzte Struktur der Matrix<br />
L von etwa einem Nichtnullelement pro Zeile oder Spalte zugrunde, so könnten während<br />
der Initiierung eines shmem_put mehr als 150 Spalten abgearbeitet werden. Aus<br />
2 Auf Kosten der Genauigkeit, auf die wieder zugunsten der Effizienz verzichtet wird.<br />
81
4 Parallele Lösung von Dreieckssystemen<br />
diesem Grund wird das Array von y selber zur Synchronisation benutzt. Zu diesem<br />
Zweck wird zu Beginn der Vorwärtssubstitution eine “nicht mögliche” Fließkommazahl<br />
3 an die Stellen des y repräsentierenden Arrays geschrieben, an die später von<br />
anderen PEs <strong>mit</strong>tels shmem_put geschrieben wird, die also nicht innerhalb der dem<br />
lokalen PE zugeordneten Blöcke liegen.<br />
Die “Warte”-statements sind dann Spin-wait-loops auf eine “volatile” deklarierte<br />
Variable 4 , die die Adresse des y j enthält, auf das gewartet werden soll. Dies ist effizienter<br />
als die SHMEM-eigene Spin-wait Funktion shmem_wait. Da ja ganze Segmente<br />
von y versandt werden (die auch in stets derselben Reihenfolge abgearbeitet<br />
werden), wäre es möglich, das NaN nur in das erste der y j eines Blocks zu schreiben<br />
und auch nur auf das erste der y j zu warten (und dann alle weiteren ohne Synchronisation<br />
abzuarbeiten). Dies erwies sich aber als nicht machbar, da die y j u. U. so<br />
langsam ausgeliefert werden, daß zwar das erste der y j (auf das synchronisiert wird),<br />
aber noch nicht alle weiteren eingetroffen sind, obwohl diese schon (aufgrund der<br />
fehlenden Synchronisation) fälschlicherweise gelesen werden. Andererseits könnte<br />
man auch ausschließlich auf das letzte y j eines Blocks synchronisieren, um so sicherzustellen,<br />
daß alle Daten eingetroffen sind. Eine solche Synchronisation wurde noch<br />
nicht implementiert. Es wird aber <strong>für</strong> diese Variante keine Leistungsverbesserung erwartet,<br />
da in diesem Falle die bereits eingetroffenen ersten y j nicht zur Berechnung<br />
von Modifikationen benutzt werden können, solange die anderen noch “unterwegs”<br />
sind.<br />
Die Rückwärtssubstitution wird prinzipiell analog zur Vorwärtssubstitution implementiert.<br />
Dabei muß kein Block gesondert behandelt werden. Die einzelnen PEs<br />
führen vor dem Verschicken die Multiplikation der z j <strong>mit</strong> 1=r j j durch.<br />
4.4 Ergebnisse<br />
Testumgebung Um realistische Zahlen zu erhalten, wurden zwei verschiedene<br />
Tests <strong>mit</strong> den <strong>Matrizen</strong> aus 3.6.1 durchgeführt. Zum einen mußte 500 mal die Lösung<br />
<strong>für</strong> das Gleichungssystem <strong>mit</strong> dichtbesetzter rechter Seite berechnet werden, wobei<br />
b i =p1 + i gesetzt wurde. Zum anderen mußte 500 mal das Gleichungssystem <strong>mit</strong><br />
<strong>dünnbesetzter</strong> rechter Seite gelöst werden, wobei <strong>für</strong> b im j-ten Schritt ein Einheitsvektor<br />
<strong>mit</strong> b j = 1 und b i = 0 <strong>für</strong> alle i≠ j genommen wurde. Dies wurde wieder <strong>für</strong> das<br />
sequentielle Programm aus 3.6.1 und die parallele Implementierung auf der in 3.6.4<br />
beschriebenen Workstation sowie dem T3D durchgeführt.<br />
3 Ein Bitmuster, das keinen reellen Zahlenbereich repräsentiert, auch “Not a Number”, kurz NaN<br />
genannt.<br />
4 Um nicht aus dem Cache oder Registern zu lesen.<br />
82
4.4 Ergebnisse<br />
Laufzeiten Die Ergebnisse bei <strong>dünnbesetzter</strong> rechter Seite <strong>für</strong> die Netlib LP Testmatrizen<br />
sind in Tabelle 4.1 dargestellt. Die parallele Implementierung ist häufig<br />
Matrix<br />
Workstation<br />
Cray T3D<br />
T ∗ T 1 T ∗ T 1 T 2 T 4 T 8 T 16<br />
agg3 3.1 1.5 3.4 1.5 1.5 1.5 1.4 2.1<br />
pilots 5.2 4.6 4.7 5.3 4.9 3.2 3.0 3.5<br />
ganges 1.0 1.1 0.6 0.9 1.0 1.1 1.4 2.0<br />
chr15c 1.1 1.2 0.9 1.1 1.2 1.3 1.5 2.2<br />
grow22 1.2 1.2 1.0 1.1 1.3 1.3 1.4 2.0<br />
bnl1 5.7 2.2 5.1 2.4 2.2 2.0 2.1 2.7<br />
scfxm2 4.9 2.2 4.2 2.2 2.6 2.7 2.4 2.7<br />
scr12 1.3 1.8 1.2 1.8 1.8 1.8 2.0 2.5<br />
maros 4.2 2.3 3.8 2.3 2.3 2.1 2.3 3.6<br />
ganges.ob4 3.1 2.2 2.6 2.1 2.3 2.1 2.5 3.1<br />
pilotwe 6.1 2.8 6.0 2.7 2.7 3.6 3.1 3.3<br />
nesm 4.2 2.6 3.8 2.6 2.6 2.7 2.7 3.4<br />
fit1p 1.5 1.6 1.1 1.4 1.7 1.8 2.2 3.0<br />
SM-50a 1.3 1.4 0.8 1.1 1.2 1.4 1.9 3.4<br />
SM-50b 1.4 1.4 0.9 1.1 1.3 1.4 1.9 2.7<br />
osa030 1.9 2.4 1.5 1.8 1.9 2.0 2.7 3.8<br />
hanscom17 3.1 3.6 3.1 3.3 3.2 3.4 4.0 5.6<br />
hanscom2 3.4 3.8 3.1 3.6 3.6 3.6 4.0 5.9<br />
kamin1809 7.1 8.4 5.9 7.2 8.0 8.2 10.8 14.5<br />
stocfor3 8.9 10.1 7.3 8.5 8.8 9.4 11.2 16.1<br />
kamin2702 10.9 12.1 8.4 12.8 13.0 15.0 19.0 25.4<br />
Tabelle 4.1: Lösungszeiten <strong>für</strong> die Netlib LP <strong>Matrizen</strong> bei <strong>dünnbesetzter</strong> rechter Seite<br />
in s<br />
schon bei einem PE schneller als die sequentielle Implementierung (bis zu einem<br />
Faktor von 2.6 bei der Matrix bnl1), vor allem bei vergleichsweise dichtbesetzter Matrix.<br />
Allerdings kann <strong>für</strong> fast alle <strong>Matrizen</strong> kein relativer Speedup erzielt werden.<br />
Im Gegenteil, es ergibt sich sogar häufig ein relativer Speeddown. Dies ist auf die<br />
Anhäufung von Kommunikationskomplexität bei Hinzunahme von mehr PEs zurückzuführen,<br />
die bei diesen dünnbesetzten <strong>Matrizen</strong> und der dünnbesetzten rechten Seite<br />
die Berechnungskomplexität dominiert. Außerdem wurden zur Lösung die Faktoren<br />
aus der parallelen Faktorisierung benutzt, die eine ansteigende Anzahl von Nichtnullelementen<br />
aufweisen, weshalb bei mehr PEs durchschnittlich mehr Arbeit bei der<br />
83
4 Parallele Lösung von Dreieckssystemen<br />
Lösung verrichtet werden muß.<br />
In Tabelle 4.2 sind die Ergebnisse <strong>für</strong> die Harwell-Boeing Testmatrizen bei <strong>dünnbesetzter</strong><br />
rechter Seite angegeben. Vergleicht man zunächst nur die Zeiten auf einem<br />
Matrix<br />
Workstation<br />
Cray T3D<br />
T ∗ T 1 T ∗ T 1 T 2 T 4 T 8 T 16<br />
steam2 3.1 3.0 2.8 2.6 2.2 2.4 1.9 2.2<br />
mcfe 4.1 8.0 3.9 7.2 5.4 4.2 3.7 4.8<br />
jpwh991 7.7 7.2 8.4 6.1 4.1 3.1 1.9 3.7<br />
sherman1 2.5 2.2 2.2 2.1 2.1 1.7 1.8 2.5<br />
sherman2 21.7 20.2 22 16.0 12.0 7.7 6.2 7.0<br />
sherman4 2.2 1.9 1.8 1.7 1.7 1.3 1.8 2.4<br />
mahindas 1.3 1.6 1.1 1.4 1.3 1.3 1.4 2.0<br />
watt1 30.8 25.2 35 21.8 11.8 10.5 8.6 9.5<br />
watt2 30.0 19.2 33 16.3 13.4 12.3 9.1 9.6<br />
west2021 2.0 2.1 1.8 2.3 2.4 2.2 2.3 3.1<br />
orsreg1 30.7 24.3 37 20.4 18.1 10.7 9.4 10.4<br />
orani678 10.1 9.0 9.1 7.9 6.6 6.1 6.0 7.5<br />
sherman5 10.4 15.6 10 13.0 9.3 7.2 7.8 9.9<br />
lns3937 41.4 47.8 54 38.6 25.7 18.5 14.5 16.0<br />
sherman3 37.9 28.1 43 23.4 23.5 14.0 13.7 14.8<br />
Tabelle 4.2: Lösungszeiten <strong>für</strong> die <strong>Matrizen</strong> aus der Harwell-Boeing Sammlung bei<br />
<strong>dünnbesetzter</strong> rechter Seite in s<br />
Prozessor, so erkennt man, daß die parallele Implementierung bei vielen <strong>Matrizen</strong><br />
schneller ist als die sequentielle (bis zu einem Faktor von 1.6 bei der Matrix watt2).<br />
Dies ist u. a. darauf zurückzuführen, daß die Lösungen jeweils ausgehend von den<br />
Faktorisierungen berechnet wurden, die auch von dem jeweiligen Programm vorgenommen<br />
wurden. Das bedeutet, daß das sequentielle Programm bei manchen <strong>Matrizen</strong><br />
durch die höhere Anzahl von Nichtnullelementen (vgl. Tabelle 3.5) in den Faktoren<br />
L und R mehr Arbeit zu verrichten hat. 5 Das gilt natürlich auch umgekehrt (z. B.<br />
bei der Matrix sherman5).<br />
Bei den <strong>Matrizen</strong> aus der Harwell-Boeing Sammlung ist fast immer ein relativer<br />
Speedup bis zu 8 PEs festzustellen (von bis zu 3.2 bei der Matrix jpwh991). Ab 16 PEs<br />
beginnt wieder der Kommunikationsaufwand die Berechnungszeit zu dominieren.<br />
5 Anders als bei den Netlib LP <strong>Matrizen</strong>, bei denen die Anzahl der Nichtnullelemente in den Faktoren<br />
von sequentieller zu paralleler Implementierung nicht so stark abweicht.<br />
84
4.4 Ergebnisse<br />
Tabelle 4.3 faßt die Ergebnisse <strong>für</strong> die Netlib LP Testmatrizen bei dichtbesetzter<br />
rechter Seite zusammen. Bei dieser Konstellation liegen die parallele und sequenti-<br />
Matrix<br />
Workstation<br />
Cray T3D<br />
T ∗ T 1 T ∗ T 1 T 2 T 4 T 8 T 16<br />
agg3 4.8 5.6 5.5 5.1 3.8 2.8 2.5 2.8<br />
pilots 8.3 8.2 8.4 8.0 5.8 4.5 4.1 4.4<br />
ganges 1.9 2.2 2.0 2.8 2.7 2.7 2.9 3.3<br />
chr15c 2.1 2.4 2.2 2.6 2.5 2.3 2.4 2.9<br />
grow22 3.0 3.3 2.9 3.7 3.3 3.0 2.9 3.4<br />
bnl1 6.8 7.3 6.5 7.3 5.4 4.3 3.9 4.1<br />
scfxm2 6.5 7.0 6.5 7.1 5.3 4.4 4.0 4.4<br />
scr12 2.9 3.3 3.1 3.5 3.2 2.9 3.0 3.9<br />
maros 7.7 8.2 7.6 8.0 6.1 4.7 4.3 4.7<br />
ganges.ob4 6.6 6.8 6.5 7.1 5.5 4.6 4.4 5.0<br />
pilotwe 12.1 10.9 10.6 10.3 7.2 5.8 5.1 5.6<br />
nesm 8.5 9.3 8.9 9.2 6.7 5.4 5.0 5.4<br />
fit1p 2.6 3.2 2.5 3.3 3.6 3.4 3.9 4.5<br />
SM-50a 1.9 2.1 1.8 2.3 2.8 3.0 3.5 4.2<br />
SM-50b 2.0 2.4 2.0 2.5 3.0 3.1 3.6 4.3<br />
osa030 3.3 3.4 3.4 3.7 4.3 4.4 5.2 6.2<br />
hanscom17 6.5 6.7 7.5 9.0 8.5 8.3 8.8 10.2<br />
hanscom2 6.4 6.7 7.4 9.3 8.7 8.4 8.7 10.3<br />
kamin1809 11.9 11.5 12.5 15.0 16.9 17.6 20.2 24.3<br />
stocfor3 22.9 21.6 21.7 26.9 25.5 24.6 25.2 29.5<br />
kamin2702 17.7 16.6 17.8 22.0 24.5 25.9 29.7 36.6<br />
Tabelle 4.3: Lösungszeiten <strong>für</strong> die Netlib LP <strong>Matrizen</strong> bei dichtbesetzter rechter Seite<br />
in s<br />
elle Implementierung bei einem Prozessor nahezu gleichauf, wobei die sequentielle<br />
meist leicht schneller ist. Interessant sind dabei die letzten drei Zeilen. Obwohl die<br />
parallele Implementierung bei allen drei <strong>Matrizen</strong> (kamin1809, stocfor3, kamin2702)<br />
auf der Workstation geringfügig schneller ist als die sequentielle Version, ist sie auf<br />
dem T3D und einem PE deutlich langsamer. Das sequentielle Programm ist dabei im<br />
Vergleich zur Workstation bei der Matrix stocfor3 sogar schneller. Beim parallelen<br />
Programm haben hier die (unnötigen, aber trotzdem ausgeführten) Synchronisationsabfragen<br />
sowie weitere kleine redundante Operationen (die auf der Workstation wie<br />
die Synchronisation <strong>mit</strong>tels Präprozessordirektive entfernt wurden) einen starken Ein-<br />
85
4 Parallele Lösung von Dreieckssystemen<br />
fluß.<br />
Anders als bei der dünnbesetzten rechten Seite ergeben sich hier häufig relative<br />
sowie absolute Speedups bis zu einer PE-Anzahl von 8 (relative Speedups von bis zu<br />
2.0 bei den <strong>Matrizen</strong> agg3 und pilotwe, die gleichzeitig die dichtbesetztesten sind).<br />
Bei den sehr dünnbesetzten <strong>Matrizen</strong> (z. B. kamin2702) ergeben sich allerdings nur<br />
Speeddowns. Dies liegt wieder am hohen Verhältnis zwischen Kommunikation und<br />
Berechnung, das ab 16 PEs auch bei den “guten” <strong>Matrizen</strong> dominant wird.<br />
In Tabelle 4.4 sind die Ergebnisse <strong>für</strong> die Testmatrizen aus der Harwell-Boeing<br />
Sammlung bei dichtbesetzter rechter Seite aufgeführt. Betrachtet man zunächst nur<br />
Matrix<br />
Workstation<br />
Cray T3D<br />
T ∗ T 1 T ∗ T 1 T 2 T 4 T 8 T 16<br />
steam2 4.5 5.0 4.0 5.2 3.6 3.3 2.5 2.7<br />
mcfe 10.6 11.6 9.8 10.5 7.0 5.3 4.6 5.2<br />
jpwh991 11.6 11.6 11.6 9.8 6.7 4.7 3.9 4.4<br />
sherman1 5.4 5.3 5.0 4.5 3.6 2.8 2.7 3.1<br />
sherman2 31.1 50.0 28.1 39.7 23.2 14.6 9.1 8.8<br />
sherman4 5.2 4.0 4.7 3.9 3.1 2.8 2.6 3.1<br />
mahindas 2.6 2.7 2.3 3.0 2.5 2.4 2.4 2.9<br />
watt1 46.8 40.0 49.4 32.8 17.7 15.6 11.0 10.9<br />
watt2 46.8 33.5 47.3 28.5 18.8 16.5 11.1 11.1<br />
west2021 3.1 3.7 3.0 4.8 4.3 3.9 4.0 4.6<br />
orsreg1 43.4 34.2 47.7 29.2 23.0 15.4 11.4 11.9<br />
orani678 21.2 20.8 18.2 18.2 12.9 11.2 9.4 9.4<br />
sherman5 34.0 36.8 32.7 31.7 20.2 14.6 11.6 12.4<br />
lns3937 67.5 74.5 73.3 65.3 37.9 28.0 19.3 18.3<br />
sherman3 82.9 64.3 91 56.7 45.4 26.3 19.2 19.1<br />
Tabelle 4.4: Lösungszeiten <strong>für</strong> die <strong>Matrizen</strong> aus der Harwell-Boeing Sammlung bei<br />
dichtbesetzter rechter Seite in s<br />
wieder die Performance auf der Workstation, so ergibt sich ein sehr differenziertes<br />
Bild. Keines der beiden Programme ist durchschnittlich schneller als das andere, aber<br />
<strong>für</strong> einzelne <strong>Matrizen</strong> ergeben sich starke Unterschiede. Bei der Matrix sherman2<br />
z. B. ist die sequentielle Implementierung 1.6 mal schneller, bei der Matrix watt2<br />
dagegen 1.4 mal langsamer. Dies hängt u. a. wiederum <strong>mit</strong> der stark unterschiedlichen<br />
Anzahl von Nichtnullelementen in den Faktoren L und R zusammen, die sich hier<br />
noch ausgeprägter als bei der dünnbesetzten rechten Seite bemerkbar macht.<br />
Aufgrund der erheblich höheren Berechnungskomplexität durch die dichtbesetzte<br />
86
4.4 Ergebnisse<br />
rechte Seite kann mehr Parallelität genutzt werden, weshalb sich beim T3D relative<br />
Speedups von bis zu 4.5 bei der Matrix sherman2 ergeben. Bei den <strong>Matrizen</strong> sherman2<br />
und lns3937 kann sogar ein Speedup bis zu einer PE-Anzahl von 16 erreicht<br />
werden.<br />
Zum Abschluß soll die <strong>mit</strong> dem in dieser Arbeit entwickelten Programm zur Vorund<br />
Rückwärtssubstitution erzielte Skalierung grafisch dargestellt werden. Dies ist in<br />
Abbildung 4.2 <strong>für</strong> die Lösung <strong>mit</strong> <strong>dünnbesetzter</strong> rechter Seite und in Abbildung 4.3<br />
<strong>für</strong> die Lösung <strong>mit</strong> dichtbesetzter rechter Seite geschehen.<br />
10<br />
absolute Verbesserung von Np=Tp<br />
1<br />
0.1<br />
1<br />
LP <strong>Matrizen</strong><br />
Harwell-Boeing <strong>Matrizen</strong><br />
alle <strong>Matrizen</strong><br />
Maximum<br />
Minimum<br />
optimale Verbesserung<br />
2<br />
4<br />
Anzahl der Prozessorelemente p<br />
8<br />
16<br />
Abbildung 4.2: absolute Verbesserung der Anzahl der abgearbeiteten Nichtnullelemente<br />
pro Zeit bei der Lösung <strong>mit</strong> <strong>dünnbesetzter</strong> rechter Seite in Abhängigkeit von<br />
der Anzahl an Prozessoren im Vergleich zur optimalen Verbesserung<br />
Um den Einfluß der verschiedenen Anzahlen an Nichtnullelementen in den Faktoren<br />
L und R zu berücksichtigen, wurde hier nicht der absolute Speedup aufgetragen,<br />
87
4 Parallele Lösung von Dreieckssystemen<br />
sondern die Verbesserung der Anzahl der abgearbeiteten Nichtnullelemente pro Zeit<br />
(N p=T p ). Als Verbesserung wird hier das Verhältnis (N p=T p )=(N ∗=T ∗ ) definiert.<br />
10<br />
absolute Verbesserung von Np=Tp<br />
1<br />
LP <strong>Matrizen</strong><br />
Harwell-Boeing <strong>Matrizen</strong><br />
alle <strong>Matrizen</strong><br />
Maximum<br />
Minimum<br />
optimale Verbesserung<br />
0.1<br />
1<br />
2<br />
8<br />
16<br />
4<br />
Anzahl der Prozessorelemente p<br />
Abbildung 4.3: absolute Verbesserung der Anzahl der abgearbeiteten Nichtnullelemente<br />
pro Zeit bei der Lösung <strong>mit</strong> dichtbesetzter rechter Seite in Abhängigkeit von<br />
der Anzahl an Prozessoren im Vergleich zur optimalen Verbesserung<br />
Interessant ist hierbei der Verlauf der maximalen Verbesserung, der bei der dünnbesetzten<br />
rechten Seite steiler verläuft, obwohl durchschnittlich schlechtere Verbesserungen<br />
erzielt werden. Dies wird hauptsächlich durch die bessere Performance der<br />
parallelen Implementierung bei vergleichsweise dichtbesetzten <strong>Matrizen</strong> (agg3, bnl1,<br />
sherman2 etc.) hervorgerufen.<br />
Insgesamt kann gesagt werden, daß sich der Einsatz eines parallelen Lösers desto<br />
mehr lohnt, je dichtbesetzter die Koeffizientenmatrix und die rechte Seite sind. Speziell<br />
<strong>für</strong> <strong>Matrizen</strong> aus der linearen Programmierung lohnt sich der Einsatz also nicht,<br />
88
4.4 Ergebnisse<br />
da keine Geschwindigkeitsvorteile erzielt werden können. Dies liegt an der geringen<br />
Berechnungskomplexität, die nicht genügend Potential an Parallelität bietet, um nicht<br />
von der vergleichsweise hohen Kommunikationskomplexität aufgewogen zu werden.<br />
Bei <strong>Matrizen</strong> aus anderen Anwendungsfeldern lohnt sich der Einsatz hingegen schon,<br />
wie die <strong>mit</strong> den <strong>Matrizen</strong> aus der Harwell-Boeing Sammlung erzielten Ergebnisse<br />
belegen.<br />
89
4 Parallele Lösung von Dreieckssystemen<br />
90
5 Zusammenfassung<br />
In dieser Arbeit wurde gezeigt, daß sich der Einsatz eines parallelen Programms zur<br />
Lösung von linearen Gleichungssystemen <strong>mit</strong> sehr <strong>dünnbesetzter</strong> Koeffizientenmatrix<br />
wie sie in der linearen Programmierung auftreten, d. h. <strong>mit</strong> etwa weniger als 10<br />
Nichtnullelementen pro Zeile oder Spalte vor und nach der Faktorisierung, <strong>mit</strong> heute<br />
verfügbaren massiv-parallelen Rechnern nicht lohnt. Dies ist auf das bei diesen Probleminstanzen<br />
zu hohe Verhältnis von Kommunikations- zu Berechnungskomplexität<br />
zurückzuführen.<br />
Bei “durchschnittlich” dünnbesetzten <strong>Matrizen</strong> aus anderen Disziplinen (z. B. der<br />
Ölreservoirmodellierung) [10], d. h. <strong>mit</strong> etwa 100 Nichtnullelementen pro Zeile oder<br />
Spalte vor und nach der Faktorisierung, können dagegen <strong>mit</strong> dem in dieser Arbeit entwickelten<br />
Programm erhebliche absolute Speedups erzielt werden, die teils deutlich<br />
höher als die einer anderen vergleichbaren Publikation sind [2].<br />
Der Einsatz einer Pivotauswahl <strong>mit</strong> dynamischem, passivem Load-balancing [23]<br />
in Verbindung <strong>mit</strong> einer Grid distribution zur Verringerung der Kommunikationskomplexität<br />
und zum statischen Lastausgleich hat sich als erfolgreich erwiesen, bietet<br />
jedoch noch experimentellen Spielraum. Bei wenigen Problemen kann das passive<br />
Load-balancing die durch eine bezüglich der Grid distribution ungünstige Struktur<br />
der Matrix hervorgerufene schlechte Lastverteilung nicht wiedergutmachen. Die in<br />
dieser Arbeit vorgeschlagene initiale Zufallspermutation versucht dem entgegenzuwirken.<br />
Auch die Verwendung einer stark asynchronen, nicht kooperativen Kommunikationsstruktur<br />
in der L- und Update-loop hat sich als sehr effizient erwiesen.<br />
Das in dieser Arbeit entwickelte Programm zur Vor- und Rückwärtssubstitution<br />
nutzt die Blockstruktur der Dreiecksmatrizen aus und versucht, eine hohe Effizienz<br />
durch Vermeidung und Integration von Kommunikationsvorgängen zu erreichen. Es<br />
ist häufig auf einem Prozessor schon schneller als eine hochoptimierte sequentielle<br />
Implementierung [22]. Trotz der sehr geringen Berechnungskomplexität lassen sich<br />
<strong>für</strong> “durchschnittlich” dünnbesetzte <strong>Matrizen</strong> und rechte Seiten gute Speedups erzielen.<br />
Aufgrund der durchweg positiven Ergebnisse der Implementierung <strong>für</strong> <strong>Matrizen</strong><br />
aus anderen Bereichen als der linearen Programmierung wird dem geplanten Einsatz<br />
91
5 Zusammenfassung<br />
<strong>für</strong> die Lösung partieller Differentialgleichungen <strong>mit</strong> hohen Erwartungen entgegengesehen.<br />
92
Literaturverzeichnis<br />
[1] ALAGHBAND, G.: Parallel Pivoting Combined with Parallel Reduction and<br />
Fill-in Control. Parallel Computing, 11:201–232, 1989.<br />
[2] ASENJO, R. und E. L. ZAPATA: Sparse LU factorization on the Cray T3D.<br />
In: HERTZBERGER, BOB und GIUSEPPE SERAZZI (Herausgeber): High-Performance<br />
computing and networking: International Conference and Exhibition,<br />
Milan, Italy, May 3–5, 1995: proceedings, Lecture Notes in Computer Science,<br />
Seiten 690–696. Springer-Verlag, 1995.<br />
[3] BARRIUSO, RAY und ALLAN KNIES: SHMEM User’s Guide for C. Cray Research<br />
Inc., August 1994. Revision 2.2.<br />
[4] BROWNE, SHIRLEY, JACK DONGARRA, ERIC GROSSE und TOM ROWAN:<br />
The Netlib Mathematical Software Repository. D-Lib magazine: the magazine<br />
of the Digital Library Forum, September 1995. Online-Version:<br />
http://www.dlib.org/dlib/september95/netlib/09browne.html.<br />
[5] CHVÁTAL, VAŠEK: Linear Programming. W. H. Freeman and Company, New<br />
York, 1983.<br />
[6] DAVIS, T. A. und P.-C. YEW: A Nondeterministic Parallel Algorithm for General<br />
Unsymmetric Sparse LU Factorization. SIAM Journal on Matrix Analysis<br />
and Applications, 11:383–402, 1990.<br />
[7] DEUFLHARD, P. und A. HOHMANN: Numerische Mathematik. de Gruyter,<br />
Berlin, 1991.<br />
[8] DUFF, I. S.: MA28–A Set of FORTRAN Subroutines for Sparse Unsymmetric<br />
Linear Equations. Report R.8730, AERE, London, 1977.<br />
[9] DUFF, I. S., A. M. ERISMAN und J. K. REID: Direct Methods for Sparse<br />
Matrices. Clarendon Press, Oxford, UK, 1986.<br />
93
Literaturverzeichnis<br />
[10] DUFF, I. S., R. G. GRIMES und J. G. LEWIS: User’s Guide for the Harwell-<br />
Boeing Sparse Matrix Test Problems Collection. Technical Report RAL-92-086,<br />
Computing and Information Systems Department, Rutherford Appleton Laboratory,<br />
Didcot, UK, 1992.<br />
[11] FOSTER, IAN: Designing and building parallel programs - concepts and tools<br />
for software engineering. Addison-Wesley Publishing Company, 1995. Online-<br />
Version: http://www.mcs.anl.gov/dbpp.<br />
[12] JEGGLE, HANSGEORG: Numerische Mathematik I <strong>für</strong> Ingenieure. Skript zur<br />
Vorlesung, Technische Universität Berlin, 1992.<br />
[13] JÁJÁ, JOSEPH: An Introduction to Parallel Algorithms. Addison Wesley, 1992.<br />
[14] MARKOWITZ, H. M.: The Elimination Form of the Inverse and its Application<br />
to Linear Programming. Management Science, 3:255–269, 1957.<br />
[15] MESSAGE PASSING INTERFACE FORUM: MPI: A Message-Passing Interface<br />
Standard, June 1995. Online-Version:<br />
ftp://ftp.mcs.anl.gov/pub/mpi/mpi-1.jun95/mpi-report.ps.<br />
[16] OED, WILFRIED: The Cray Research Massively Parallel Processor System<br />
CRAY T3D. Technical Report, Cray Research GmbH, München, Germany, November<br />
1993.<br />
[17] RAYMOND, ERIC und GUY L. STEELE (Herausgeber): The New Hacker’s Dictionary.<br />
MIT Press, Cambridge, MA, USA, zweite Auflage, 1993. Online-<br />
Version: http://www.ccil.org/jargon/jargon.html.<br />
[18] ROTHBERG, EDWARD: Alternatives for solving sparse triangular systems on<br />
distributed-memory multiprocessors. Parallel Computing, 21:1121–1136, 1995.<br />
[19] SMART, D. und J. WHITE: Reducing the parallel solution time of sparse circuit<br />
matrices using reordered Gaussian elimination and relaxation. In: Proc. IEEE<br />
Internat. Symp. Circuits and Systems, Seiten 627–630, 1988.<br />
[20] STROUSTRUP, B.: The C++ Programming Language. Addison Wesley, zweite<br />
Auflage, Juni 1991.<br />
[21] VAN DER STAPPEN, A. FRANK, ROB H. BISSELING und JOHANNES G. G.<br />
VAN DE VORST: Parallel Sparse LU Decomposition on a Mesh Network of<br />
Transputers. SIAM Journal on Matrix Analysis and Applications, 14(3):853–<br />
879, Juli 1993.<br />
94
Literaturverzeichnis<br />
[22] WUNDERLING, ROLAND: Paralleler und Objektorientierter Simplex-<br />
Algorithmus. Technical Report TR 96-09, Konrad-Zuse-Zentrum <strong>für</strong> Informationstechnik<br />
Berlin, 1996. (to appear).<br />
[23] WUNDERLING, ROLAND, HANS-CHRISTIAN HEGE und MARTIN GRAMMEL:<br />
On the Impact of Communication Latencies on Distributed Sparse LU Factorization.<br />
Preprint SC 93-28, Konrad-Zuse-Zentrum <strong>für</strong> Informationstechnik Berlin,<br />
December 1993. Online-Version:<br />
ftp://ftp.zib.de/pub/zib-publications/reports/SC-93-28.ps.<br />
[24] YANNAKAKIS, M.: Computing The Minimum Fill-In is NP-Complete. SIAM J.<br />
on Algebraic and Discrete Methods, Seiten 77–79, März 1981.<br />
[25] ZLATEV, ZAHARI: On some pivotal strategies in Gaussian elimination by sparse<br />
technique. SIAM Journal on Numerical Analysis, 17(1):18–30, 1980.<br />
[26] ZLATEV, ZAHARI, JERZY WASNIEWSKI und KJELD SCHAUMBURG: Y12M –<br />
solution of large and sparse systems of linear algebraic equations: documentation<br />
of subroutines, Band 121 der Reihe Lecture Notes in Computer Science.<br />
Springer-Verlag Inc., New York, NY, USA, 1981.<br />
95