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

18.11.2013 Aufrufe

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

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

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!