Skript zur Vorlesung:
Theorie Paralleler und Verteilter Systeme
1110
1010
1111
1011
1101
1100
1000
0111
0110
...
105 downloads
906 Views
504KB Size
Report
This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Report copyright / DMCA form
Skript zur Vorlesung:
Theorie Paralleler und Verteilter Systeme
1110
1010
1111
1011
1101
1100
1000
0111
0110
1001
0010
0011
0100
0000
0101
0001
Sommersemester 2006
Prof. Dr. Till Tantau Aufbauend auf Skripten von Andreas Jakoby, Maciej Li´skiewicz und Thomas Zeugmann
Institut fu ¨r Theoretische Informatik Universit¨at zu Lu ¨beck
Inhaltsverzeichnis 1 Einf¨ uhrung
5
2 Grundlagen paralleler Algorithmen 2.1 Parallele Modelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1.1 Gerichtete azyklische Graphen . . . . . . . . . . . . . . . . 2.1.2 Shared-Memory Modell . . . . . . . . . . . . . . . . . . . . 2.1.3 Netzwerk Modell . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Performance Paralleler Algorithmen . . . . . . . . . . . . . . . . . 2.2.1 Speedup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2.2 Kosten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2.3 Die Arbeit-Zeit Repr¨ asentation eines parallelen Algorithmus 2.2.4 Optimalit¨ at von parallelen Algorithmen . . . . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
5 . 5 . 6 . 7 . 8 . 9 . 9 . 11 . 11 . 12
3 Grundlegende parallele Algorithmen 3.1 Ein optimaler Algorithmus f¨ ur Pr¨afix-Summen . . . . . . . . . 3.2 Pointer Jumping . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3 Divide and Conquer . . . . . . . . . . . . . . . . . . . . . . . . 3.3.1 Die konvexe H¨ ulle . . . . . . . . . . . . . . . . . . . . . 3.3.2 Partitionierung . . . . . . . . . . . . . . . . . . . . . . . 3.4 Pipelining . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.4.1 Einf¨ ugen eines Elements in 2-3-B¨aumen . . . . . . . . . 3.4.2 Einf¨ ugen einer Sequenz in 2-3-B¨aumen . . . . . . . . . . 3.5 Accelerated Cascading . . . . . . . . . . . . . . . . . . . . . . . 3.5.1 Ein einfacher optimaler Algorithmus f¨ ur das Maximum . 3.5.2 Berechnung in konstanter Zeit . . . . . . . . . . . . . . 3.5.3 Berechnung in doppelt-logarithmischer Zeit . . . . . . . 3.5.4 Ein schneller optimaler Algorithmus f¨ ur das Maximum . 3.6 Aufbrechen von Symmetrien . . . . . . . . . . . . . . . . . . . . 3.6.1 Verbesserung einer F¨ arbung . . . . . . . . . . . . . . . . 3.6.2 Ein sehr schneller F¨ arbungsalgorithmus . . . . . . . . . 3.6.3 Ein optimaler 3-F¨ arbungsalgorithmus . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
12 12 14 15 15 17 18 19 19 21 21 21 22 22 22 23 23 24
4 Reihenfolge in Listen 4.1 List-Ranking mit Hilfe von Pointer-Jumping . . . . . . . . . . 4.2 Ein O(log(n))-zeitbeschr¨ ankter arbeitsoptimaler Algorithmus 4.3 Sortierte Reihenfolge von Bl¨ attern . . . . . . . . . . . . . . . 4.4 Baumkontraktion . . . . . . . . . . . . . . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
25 25 25 31 34
. . . .
5 Verschmelzen und Sortieren 35 5.1 Bitonischer Merge-Algorithmus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 5.2 Cole’s Merge-Sort Algorithmus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 6 Boolesche Schaltkreise 6.1 Das Schaltkreismodell . . . . . . . . . . 6.2 Addition in Tiefe o(log n) . . . . . . . . 6.3 Die Division und das Newton-Verfahren 6.3.1 Das Newton-Verfahren . . . . . . 6.3.2 Die Division . . . . . . . . . . . .
. . . . .
. . . . .
2
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
42 42 44 46 46 47
1
Einfu ¨ hrung
In viele Lehrb¨ uchern u ur solche Systeme u ¨ber parallele und verteilte Systeme wird der Bedarf f¨ ¨ber die physikalischen Schranken sequentieller Computer begr¨ undet. Diesen Begr¨ undungen kann man einfach entgegen halten, dass sequentielle Computer trotz dieser Schranken in den letzten Jahrzehnten immer schneller und leistungsf¨ ahiger geworden sind und leistungsstarke parallele System oft viel teurer sind als sequentielle. Einige Gr¨ unde, sich trotzdem mit parallelen und verteilten Systemen zu besch¨aftigen, sind: 1. Eine elementare Form paralleler Systeme sind die Schaltkreise. Da jeder Computer aus Schaltkreisen aufgebaut ist, lassen sich verschiedene Ergebnisse aus dem Bereich der parallelen Systeme auf Konstruktion sequentieller Rechner u ¨bertragen. 2. Viele Probleme k¨ onnen sehr effizient mit Hilfe paralleler Programme gel¨ost werden. Stehen uns f¨ ur die L¨osung dieser Probleme mehrere Rechner zur Verf¨ ugung, so sollten wir einige grundlegende Techniken aus dem Bereich der parallelen Algorithmen kennen. 3. In viele Bereichen treffen wir auf verteilte Systeme. Dieses beginnt bei der Elektronik in einem modernen Auto und endet beim Internet. In dieser Vorlesung wollen wir verschiedene Fragestellungen aus dem Bereich der parallelen und verteilten Systeme untersuchen. Die wesentlichen Punkte sind hierbei: • Parallele Algorithmen • Effiziente Schaltkreise • Grenzen effizienter paralleler Systeme • Synchronisation und verwandte Probleme • Beschreibung verteilter Systeme Die ersten Kapitel basieren auf dem Lehrbuch [JaJa92]. Die Analyse des bitonischen Merge-Verfahrens ist an die Analyse in [Leig92] angelehnt. Das Kapitel 6 ist an das Vorlesungsskript von Herrn Zeugmann [Zeug03] angelehnt.
2
Grundlagen paralleler Algorithmen
Bevor wir uns mit dem Entwurf von parallelen Algorithmen auseinander setzen k¨onnen m¨ ussen wir zun¨achst ein ad¨ aquates Maschinenmodell definieren. Im Gegensatz zu sequentiellen Maschinen, wo sich f¨ ur die Beschreibung von Programmen Maschinen mit einem Random-Access Speicher, zu einem allgemein akzeptierten Standard entwickelt haben, fehlt ein solches Modell f¨ ur parallele Systeme. Im Bereich der parallelen Systeme werden unter anderem Schaltkreise (azyklische gerichtete Graphen), Netzwerke, Shared-Memory Rechner, alternierende Turing-Maschinen (eine Verallgemeinerung von nichtdeterministischen Turing-Maschinen) untersuchen. Jedes dieser Modelle hat seine St¨arken und Schw¨achen. Ein paralleler Computer ist im Wesentlichen eine Kollektion von (identischen) Prozessoren, die sich u ¨ber eine Verbindungsstruktur koordinieren und Daten austauschen. Typischerweise gehen wir davon aus, dass diese Prozessoren nahe zusammen liegen. Im Gegensatz hierzu besteht ein verteiltes System aus vielen unterschiedlichen Komponenten und Prozessoren, die r¨aumlich weit getrennt sein k¨onnen. Parallele Computer lassen sich bez¨ uglich ihrer Verbindungstopologie, des zur Verf¨ ugung stehenden Befehlssatzes, der Form der Programmierung, der Organisation des Speichers, der Koordinierung des Programmablaufs u.s.w. klassifizieren. Im Folgenden wollen wir uns zun¨achst nicht um diese Feinheiten k¨ ummern. Unser Ziel wird zun¨ achst die Analyse effizienter Algorithmen sein.
2.1
Parallele Modelle
Ein paralleles Modell soll folgende Eigenschaften erf¨ ullen: • Einfach: Das Modell soll es erlauben einfach parallele Algorithmen zu beschreiben und zu analysieren. Hierbei soll das Modell von verschiedenen Hardwareeigenschaften abstrahieren. • Implementierbar: Algorithmen sollen einfach implementiert werden k¨onnen. 5
2.1.1
Gerichtete azyklische Graphen
Gerichtete azyklische Graphen (DAG) als paralleles Rechnermodell finden wir vor allem bei Schaltkreisen, Sortiernetzen und Entscheidungsb¨ aumen. Sie repr¨asentieren verschiedene Formen von Berechnungen auf eine sehr nat¨ urliche Art und Weise. Sei G = (V, E) ein DAG, dann stellen die Knoten aus V , die keine Vorg¨anger besitzen (Eingrad ist 0), d.h. die Quellen von G, die Eingaben des DAGs dar. Knoten ohne Nachfolger, d.h. die Senken, sind sogenannte Ausgabeknoten. Der maximale Eingrad der Knoten ist 2. Alle Knoten v ∈ V sind mit einer Operation gv verkn¨ upft. Um den DAG auszuwerten, berechnen wir Werte der Knoten v durch Auswerten der Operationen gv startend von den Eingabeknoten. Im uniformen Kostenmaß gehen wir davon aus, dass das Auswerten jeder Operation genau einen Takt ben¨otigt. Der minimale Zeitbedarf einer Berechnung ist somit gleich der Tiefe des DAGs. Da die L¨ ange einer Eingabe eines DAGs durch die Anzahl der Quellen festgelegt ist, betrachten wir im Allgemeinen Familien von DAGs {Gi | i ∈ N}, wobei Gi f¨ ur Eingaben der L¨ange i verantwortlich ist. Damit dieses Modell nicht zu m¨ achtig wird, m¨ ussen wir noch gewisse Konstruierbarkeitsanforderungen an die einzelnen Familien stellen, auf die wir jedoch sp¨ater im Abschnitt u ¨ber Schaltkreise n¨aher eingehen werden. Im Zentrum des DAG-Modells stehen die Datenabh¨angigkeiten in einem Problem. Die Beschreibung eines Algorithmus erfolgt vollkommen unabh¨angig von der Hardware. Beispiel 1 Ein typisches Beispiel f¨ ur einen DAG erhalten wir bei der Berechnung der Summe von n Zahlen a1 , a2 , . . . , an . Zwei verschiedene DAGs f¨ ur dieses Problem sind Abbildung 1 dargestellt. + + a5
+
a1
+ +
a4
+ +
a6
+
+
a3 a1
a2
+
a2
a3
a5
a6
a4
Abbildung 1: Links ein DAG, der die Summe in Zeit n − 1 berechnet und rechts ein DAG, der dieses Problem in logarithmischer Zeit l¨ ost. Ein Algorithmus, der im DAG-Modell geschrieben wurde, kann mit Hilfe einer Schedule auf einen Parallel-Rechner u ugbaren Prozessoren, dann ist eine Schedule ¨bertragen werden. Sei P die Menge der verf¨ S = {(v, Pi , t) | v ∈ V, t ∈ N, Pi ∈ P } eine Menge, die die folgenden Bedingungen erf¨ ullt: 1. F¨ ur alle v ∈ V existiert ein Tupel (v, Pi , t) ∈ S. 2. F¨ ur alle Tupel (v, Pi , t), (v 0 , Pj , t0 ) ∈ S gilt Pi = Pj ∧ t = t0
=⇒
v = v0 .
3. F¨ ur alle (u, v) ∈ E und alle (v, Pi , t) ∈ S existiert ein Tupel (u, Pj , t0 ) ∈ S mit t0 ≤ t − 1. Der Zeitbedarf T (S) einer Schedule S ist definiert als T (S) = max{t | (v, Pi , t) ∈ S}. und der Zeitbedarf Tp (n) eines DAGs Gn ist Tp (n) = min{T (S) | S ist eine Schedule f¨ ur Gn mit maximal p Prozessoren} . 6
2.1.2
Shared-Memory Modell
Das Shared-Memory Modell ist eine nat¨ urliche Erweiterung des sequentiellen Modells. Ein SharedMemory Computer besteht aus p Prozessoren, die alle Zugriff auf ihren jeweiligen lokalen und auf einen globalen Speicher haben. Der globale Speicher kann von den Prozessoren zur Synchronisation und zum Datenaustausch genutzt werden. Die Prozessoren unterscheiden sich im Wesentlichen durch ihre Prozessor Ids. Wir unterscheiden zwischen dem synchronen Modell, bei dem alle Prozessoren gem¨aß einem gemeinsamen Takt operieren, und dem asynchronen Modell, bei dem jeder Prozessor u ¨ber einen eigenen Takt verf¨ ugt. Eine synchrone Shared-Memory Maschine wird auch PRAM, d.h. parallele random-access Maschine, genannt. Ein weiteres Unterscheidungmerkmal ist die Form des Kontroll- und Datenflusses. Hier unterscheiden wir zwischen: • single-instruction multiple-data, SIMD: Alle Prozessoren f¨ uhren gleichzeitig eine bestimmte Operation auf unterschiedlichen Daten aus. • multiple-instruction single-data, MISD: Alle Prozessoren f¨ uhren gleichzeitig verschiedene Operationen auf einer bestimmten Variable aus. • multiple-instruction multiple-data, MIMD: Die Prozessoren arbeiten alle ihr eigenes Programm auf individuellen Daten ab. Im Folgenden wollen wir uns auf MIMD- bzw. SIMD-Maschinen beschr¨anken. Um auf den globalen Speicher zuzugreifen, verf¨ ugt eine PRAM u ¨ber die Operationen: • global read(x, y), womit der Wert der Variablen x aus dem globalen Speicher in der lokalen Variablen y gespeichert wird. • global write(y, x), womit der Wert der lokalen Variablen y in der globalen Variablen x gespeichert wird. Zur Vereinfachung erlauben wir es, dass mit einer read/write-Operation ein ganzer Datenblock verschoben wird. Der Rechenaufwand ist dann die Gr¨oße des Blocks. Beispiel 2 Wir betrachten nun einen SIMD-Algorithmus zur Matrix-Vektor Multiplikation f¨ ur einen Prozessor Pi auf eine n × n-Matrix M und einen n-elementigen Vektor v: Algorithm 1 Matrix-Vektor Multiplikation(i) Eingabe: M , v im globalen Speicher Ausgabe: w = M · v im globalen Speicher 1: global read(v, x) 2: Berechne r = dn/pe 3: global read(M [(i − 1) · r + 1 . . . min{i · r, n}, 1 . . . n], A) 4: Berechne u = A · v 5: global write(u, w[(i − 1) · r + 1 . . . i · r])
Beispiel 3 Wir betrachten nun einen MIMD-Algorithmus zur Berechnung der Summe S = f¨ ur einen gegebenen Vektor A der L¨ ange n:
Pn
i=1
A[i]
Betrachten wir den obigen Algorithmus, so sehen wir, dass es schnell zu einem Konflikt kommen kann, wenn zwei Prozessoren in die gleiche Speicherzelle schreiben wollen. Die erste M¨oglichkeit diesen Konflikt zu vermeiden, ist es, diesen zu verbieten. Wir unterscheiden die folgenden Varianten von PRAMs: • exclusive read owner write (EROW) PRAM: Diese PRAM ordnet jeder Speicherzelle einen Besitzer zu, der als einziger den Wert dieser Zelle ver¨andern darf. Ferner d¨ urfen keine zwei Prozessoren gleichzeitig den Wert einer Zelle lesen. • exclusive read exclusive write (EREW) PRAM: Diese PRAM erlaubt weder das gleichzeitige Lesen noch das gleichzeitige Schreiben zweier Prozessoren von bzw. auf eine Speicherzelle. 7
Algorithm 2 Summe(i) Eingabe: A im globalen Speicher Ausgabe: S im globalen Speicher 1: global read(A(i), a) 2: global write(a, B(i)) 3: for h = 1 to log n do 4: if i ≤ n/2h then 5: global read(B(2i), a) 6: global read(B(2i − 1), b) 7: Berechne c = a + b 8: global write(c, B(i)) 9: end if 10: end for 11: if i = 1 then global write(c, S) end if
• concurrent read exclusive write (CREW) PRAM: Diese PRAM erlaubt zwar das gleichzeitige Lesen doch nicht das gleichzeitige Schreiben zweier Prozessoren von bzw. auf eine Speicherzelle. • concurrent read concurrent write (CRCW) PRAM: Diese PRAM erlaubt sowohl das gleichzeitige Lesen als auch das gleichzeitige Schreiben zweier Prozessoren von bzw. auf eine Speicherzelle. Wir unterscheiden in diesem Modell weiter zwischen der – common CRCW PRAM: Bei dieser PRAM m¨ ussen alle Prozessoren, die auf die gleiche Speicherzelle schreiben wollen, den gleichen Wert schreiben. – arbitrary CRCW PRAM: Nichtdeterministisch setzt sich beim Schreiben einer der Prozessoren, die auf die gleiche Speicherzelle schreiben wollen, mit dessen Wert durch. – priority CRCW PRAM: Beim Schreiben in eine Speicherzelle setzt sich der Prozessor mit der h¨ ochsten Priorit¨ at durch, d.h. der Wert, der von dem Prozessor mit der niedrigsten Prozessor Id geschrieben wird, befindet sich nach dem Schreiben in der Speicherzelle. Es gibt zwar noch einige weitere Varianten von PRAMs, wir werden auf diese jedoch nicht weiter eingehen. 2.1.3
Netzwerk Modell
Ein Netzwerk ist ein Graph G = (P, E), wobei die Elemente in P die Prozessoren und die Kanten in E bidirektionale Verbindungen zwischen den Prozessoren darstellen. Jeder Prozessor besitzt einen lokalen Speicher, jedoch steht kein globaler Speicher zur Verf¨ ugung. Die Eingabe befindet sich zu Beginn der Berechnung auf wohl definierten Positionen des lokalen Speichers einiger Prozessoren. Analog befindet sich auch die Ausgabe nach Abschluß der Berechnung im lokalen Speicher der Prozessoren. Die Prozessoren k¨ onnen entweder synchron oder asynchron arbeiten. Um Daten auszutauschen, k¨onnen die Prozessoren zwei Operationen benutzen: 1. send(x, i): Diese Operation sendet eine Kopie von x an Prozessor Pi . Diese Operation ist nicht blockierend, d.h. der Prozessor f¨ uhrt nach dem Senden sein Programm fort, ohne darauf zu warten, ob die Nachricht auch gelesen wird. 2. receive(x, i): Diese Operation testet, ob eine ungelesene Nachricht von Pi vorliegt. Ist dieses der Fall, so liest er die ¨ alteste ungelesene Nachricht von Pi und f¨ uhrt seine Berechnung fort. Liegt jedoch keine ungelesene Nachricht von Pi vor, so wartet der Prozessor auf eine Nachricht von Pi . Die Empfangsoperation ist somit blockierend. Zur Vereinfachung erlauben wir hierbei auch eine relative Adressierung (z.B.: left f¨ ur vom linken Nachbarn). Die Topologie von Netzwerken kann von unterschiedlicher G¨ ute sein. Wir untersuchen daher bei Netzwerken • den Durchmesser, d.h. die maximale L¨ange eines k¨ urzesten Pfads zwischen zwei Prozessoren,
8
• den Grad, d.h. die maximale Anzahl der Nachbarn eines Prozessors, • den Zusammenhang, d.h. die minimale Anzahl der Prozessoren, die man l¨oschen muss, damit das Netzwerk nicht mehr zusammenh¨angend ist, und • den Kanten-Zusammenhang, d.h. die minimale Anzahl der Kanten, die man l¨oschen muss, damit das Netzwerk nicht mehr zusammenh¨angend ist. Beispiel 4 Lineares Prozessor-Array und der Prozessor-Ring Ein lineares Prozessor-Array besteht aus p Prozessoren P1 , . . . , Pp , welche u ¨ber die Kanten {Pi , Pi+1 } f¨ ur i ∈ {1, . . . , p − 1} verbunden sind. Bei einem Ring liegt zudem noch die Kante {P1 , Pp } vor. Beispiel 5 2-dimensionales Gitter Ein 2-dimensionales Gitter besteht aus n2 Prozessoren P1,1 , . . . , Pn,n , die u ¨ber die Kanten E = {{Pi,j , Pi+1,j }, {Pj,i , Pj,i+1 } | i ∈ {1, . . . , p − 1}, j ∈ {1, . . . , j}} miteinander verbunden sind. Beispiel 6 n-dimensionaler Hyperw¨ urfel Ein n-dimensionaler Hyperw¨ urfel besteht aus 2n Prozessoren Pw mit w ∈ {0, 1}n , welche u ¨ber die Kanten {Pw , Pw(i) } f¨ ur w, w(i) ∈ {0, 1}n miteinander verbunden sind, wobei sich bin¨ are Zeichenketten w und w(i) nur an der i-ten Position unterscheiden. lin. Prozessor-Array Ring 2-dim. Gitter n-dim. Hyperw¨ urfel
p n n n2 2n
Durchmesser n−1 d(n − 1)/2e 2(n − 1) n
Grad 2 2 4 n
Zusammenhang 1 2 2 n
Kanten-Zusammenhang 1 2 2 n
Einer der bekanntesten Algorithmen auf einem 2-dimensionalen Gitter ist der systolische Algorithmus zur Matrixmultiplikation. Beispiel 7 Systolischer Algorithmus zur Matrixmultiplikation Wir betrachten die Multiplikation zweier n × n-Matrizen A, B auf einem 2-dimensionalen n × n-Gitter. Wir gehen davon aus, dass zum Beginn der Berechnung die ersten Prozessoren jeder Zeile Pi,1 die Zeilen A[i, 1 . . . n] und die ersten Prozessoren jeder Spalte P1,i die Spalten B[1 . . . n, i] der zweiten Matrix kennen.1 Nach Abschluss der Berechnung soll jeder Prozessor Pi,j den Wert C[i, j] der resultierenden Matrix C = A · B kennen. Die grundlegende Idee hinter diesem Algorithmus ist in der Abbildung 2 illustriert.
2.2 2.2.1
Performance Paralleler Algorithmen Speedup
Um die Performance eines parallelen Algorithmus analysieren zu k¨onnen, betrachten wir dessen Speedup. Definition 1 Sei Q ein Problem und T ∗ (n) die sequentielle Zeitkomplexit¨ at2 von Q auf Eingaben der L¨ ange n. Sei A ein paralleler Algorithmus, der Q in Zeit Tp (n) auf einem Computer mit p Prozessoren l¨ ost. Dann erreicht A einen Speedup von Sp (n) =
T ∗ (n) Tp (n)
1 Unter dem systolischen Algorithmus zur Matrixmultiplikation versteht man im Allgemeinen ein Verfahren, in dem die Randprozessoren die Matrix in der gew¨ unschten Reihenfolge empfangen. Es ist jedoch einfach zu erkennen, dass unser vorgestellter Algorithmus ¨ aquivalent zu diesem Verfahren ist. 2 Es gibt ein sequentielles Programm, welches Q in Zeit T ∗ (n) l¨ ost, und wir k¨ onnen zeigen, dass auch T ∗ (n) Schritte ben¨ otigt werden.
9
Algorithm 3 systolischer Algorithmus(i, j) 1: Initialisiere C[i, j] = 0 2: for h = 1 to n do 3: if j = 1 then 4: setze a = A[i, h] 5: else 6: receive(a, Pi,j−1 ) 7: end if 8: if i = 1 then 9: setze b = B[h, j] 10: else 11: receive(b, Pi−1,j ) 12: end if 13: Berechne C[i, j] = C[i, j] + a · b 14: if j < n then send(a, Pi,j+1 ) end if 15: if i < n then send(b, Pi+1,j ) end if 16: end for
B[4, 1] B[3, 1] B[2, 1] B[1, 1] A[1, 4] A[1, 3] A[1, 2] A[1, 1]
A[2, 4] A[2, 3] A[2, 2] A[2, 1]
A[3, 4] A[3, 3] A[3, 2] A[3, 1]
A[4, 4] A[4, 2] A[4, 2] A[4, 1]
B[4, 2] B[3, 2] B[2, 2] B[1, 2]
B[4, 3] B[3, 3] B[2, 3] B[1, 3]
B[4, 4] B[3, 4] B[2, 4] B[1, 4]
P1,1
P1,2
P1,3
P1,4
P2,1
P2,2
P2,3
P2,4
P3,1
P3,2
P3,3
P3,4
P4,1
P4,2
P4,3
P4,4
Abbildung 2: Der systolische Algorithmus zur Multiplikation zweier 4 × 4 Matrizen. und eine Effizienz von
T1 (n) . p · Tp (n)
Ep (n) =
Hierbei ist T1 der Zeitbedarf von A auf einer Ein-Prozessor Maschine. Eine nahelegende Beobachtung ist: Sp (n) ≤ p . Um dieses zu beweisen, nehmen wir an, dass Sp (n) > p ist. Dann gilt p · Tp (n) < T ∗ (n). Eine sequentielle Simulation des parallelen Verfahrens kann dann Q in Zeit p·Tp (n) gel¨ost werden. Dieses w¨ urde bedeuten, dass T ∗ (n) nicht der sequentielle Zeitkomplexit¨at von Q entspricht. Unser Ziel ist es somit, einen parallelen Algorithmus zu finden, der f¨ ur alle p einen m¨oglichst großen Speedup, d.h. Sp (n) ∼ p, erzielt. Ein solcher Speedup ist jedoch nicht f¨ ur aller Werte von p zu erreichen. Sp¨atestens f¨ ur p > T ∗ (n) erhalten wir einen Speedup Sp (n) < p, da ansonsten die Laufzeit des parallelen Verfahrens kleiner 1 ist. Es existiert somit ein von n unabh¨angiger minimaler Wert T∞ (n) von A. F¨ ur die Effizienz gilt: T1 (n) Ep (n) ≤ . p · T∞ (n) 10
Um parallelen Algorithmen zu beschreiben, benutzen wir eine Pseudo-Programmiersprache im uniformen Kostenmaß. Im uniformen Kostenmaß gehen wir davon aus, dass jeder Befehl unabh¨angig vom Wert der betrachteten Variablen einen Takt Zeit ben¨otigt. Ein weiteres oft betrachtetes Kostenmaß ist das logarithmische Kostenmaß. Die Kosten f¨ ur eine Operation sind hierbei logarithmisch in der Gr¨ oße der betrachteten Werte. Hierbei m¨ ussen wir auch die Kosten ber¨ ucksichtigen, die durch die Adressierung der Variablen entstehen. 2.2.2
Kosten
Ein weiteres Kostenmaß, welches f¨ ur uns vom großem Interesse ist, ist das Zeit-Prozessor Produkt C(n) = T (n) · P (n), das wir auch als die Kosten des Algorithmus bezeichnen. Theorem 1 Die folgenden Aussagen u otigten Ressourcen einer PRAM zum L¨ osen eines ¨ber die ben¨ Problems Q sind ¨ aquivalent: 1. Q kann in Zeit T (n) auf P (n) Prozessoren gel¨ ost werden. 2. Q kann in Zeit T (n) bei Kosten C(n) = P (n) · T (n) gel¨ ost werden. 3. Q kann in Zeit O(T (n) · P (n)/p) auf p ≤ P (n) Prozessoren gel¨ ost werden. 4. Q kann in Zeit O(C(n)/p + T (n)) auf p Prozessoren f¨ ur beliebiges p gel¨ ost werden. ¨ Beweis: Die Aquivalenz der ersten beiden Aussagen ist offensichtlich. Betrachten wir die vierte Aussage, so sehen wir O(C(n)/p + T (n))
= O(max{C(n)/p, T (n)}) = O(max{T (n) · P (n)/p, T (n)}) =
O(T (n) · P (n)/p) f¨ ur p ≤ P (n) O(T (n)) f¨ ur p > P (n).
Somit sind die dritte Aussage und die vierte Aussage ¨aquivalent. Ferner erkennen wir bei einer Wahl von p = P (n) unmittelbar, dass aus der dritten Aussage die ersten Aussage folgt. Es verbleibt noch zu zeigen, dass aus der zweiten Aussage die dritte Aussage folgt. Dieses k¨onnen wir durch eine einfache Simulation zeigen. Wir betrachten eine Schedule S f¨ ur P (n) Prozessoren. Sei St die Menge der Operationen die in S zum Zeitpunkt t ausgef¨ uhrt werden. Unsere p Prozessor-Schedule beginnt mit der Berechnung der Operationen zum Zeitpunkt dP (n)/pe · (t − 1) + 1. Hierbei partitionieren wir die Menge St in ` = d|St |/pe ≤ dP (n)/pe Teilmengen St1 , . . . , St` der Gr¨oße ≤ p und schedulen die Operationen Sti auf den Zeitpunkt dP (n)/pe · (t − 1) + i. 2.2.3
Die Arbeit-Zeit Repr¨ asentation eines parallelen Algorithmus
Im Folgenden werden wir parallele Algorithmen oft in einer openMP-artigen Notation3 beschreiben. Diese Notation erlaubt es uns die Algorithmen als eine Art sequentielles Programm aufzuschreiben, wobei wir Schleifen parallelisieren k¨ onnen. Wir benutzen hierbei Kommandos der Form: for i = 1 to n par do Teilprogramm end for Diese Operation besagt, dass f¨ ur alle i ∈ {1, . . . , n} das Teilprogramm parallel ausgef¨ uhrt werden kann – unabh¨ angig von den vorhandenen Prozessoren. Ein solches Programm, welches von der Anzahl der Prozessoren unabh¨ angige parallelisierende Operationen benutzt, nennen wir auch Arbeit-Zeit Repr¨ asentation eines parallelen Programms, kurz WT-Repr¨ asentation. In anderen Worten: Die WT-Repr¨ asentation eines parallelen Programms beschreibt die Operationen eines Algorithmus in einer Sequenz von Zeitschritten, wobei in jedem Zeitschritt eine beliebige Anzahl von parallelen Operationen ausgef¨ uhrt werden kann. Wir definieren: Definition 2 Die Arbeit W (x) eines parallelen Algorithmus A ist die Anzahl aller Operationen, die A auf Eingabe x ausf¨ uhrt. Ferner sei W (n) = max{W (x) | x ist eine Eingabe der L¨ ange n} . 3 Eine
Spezifikation von openMP findet man unter http://www.openmp.org/specs/.
11
Der wesentliche Vorteil der WT-Repr¨ asentation eines parallelen Programms liegt darin, dass wir uns bei der Beschreibung keine Gedanken u ¨ber das Scheduling bzw. die Prozess-Prozessor-Allocation machen m¨ ussen. Dieses Problem k¨ onnen wir auf das Scheduling-Verfahren abw¨alzen: Das WT-Scheduling Prinzip: Sei Ot (n) die Menge aller Operationen, die in einer WT-Repr¨asentation eines parallelen Programms A zum Zeitpunkt t ausgef¨ uhrt werden. Partitioniere diese Operationen in ` = d |Otp(n)| e Teilmengen Ot1 (n), . . . , Ot` (n) der maximalen Gr¨oße p. Schedule diese Teilmengen so, dass f¨ ur alle i ≤ ` die Operationen aus Oti (n) parallel auf p Prozessoren ausgef¨ uhrt werden. Beobachtung 1 Mit Hilfe des WT-Scheduling Prinzip kann eine WT-Repr¨ asentation eines parallelen Programms mit Zeitbedarf T (n) und Arbeit W (n) in Zeit X |Ot (n)| X |Ot (n)| W (n) ≤ +1 ≤ + T (n) Tp (n) ≤ p p p t≤T (n)
t≤T (n)
auf einer p-Prozessor PRAM ausgef¨ uhrt werden. Arbeit und Kosten sind nahe miteinander verwandt. Es gilt: Beobachtung 2 Sei A die WT-Repr¨ asentation eines parallelen Programms mit Zeitbedarf T (n) und Arbeit W (n), dann gilt f¨ ur die WT-Schedule auf einer p-Prozessor PRAM Cp (n) = Tp (n) · p = O(W (n) + T (n) · p) . Aus dieser Beobachtung folgt, dass f¨ ur p = O(W (n)/T (n)) Arbeit und Kosten asymptotisch gleich sind. Ein letztes Komplexit¨ atsmaß, welches wir im Rahmen dieser Vorlesung betrachten wollen, ist die Kommunikationskomplexit¨ at. Die Kommunikationskomplexit¨at eines PRAM-Algorithmus ist die Anzahl der Schreib- und Leseoperationen der PRAM auf bzw. vom globalen Speicher. 2.2.4
Optimalit¨ at von parallelen Algorithmen
Wir nennen einen parallelen Algorithmus optimal, wenn unabh¨angig von der parallelen Zeit T (n) gilt: W (n) = Θ(T ∗ (n)). F¨ ur einen optimalen parallelen Algorithmus gilt: Tp (n)
= O(T ∗ (n)/p + T (n))
Sp (n)
p·T (n) T (n) = O( T ∗ (n)/p+T (n) ) = O( T ∗ (n)+p·T (n) ) .
∗
∗
Somit gilt f¨ ur p = O(T ∗ (n)/T (n)) Sp (n) = Θ(p). Wir erhalten ein asymptotisch optimales Speedup. Wir nennen einen parallelen Algorithmus WT-optimal bzw. optimal im strengen Sinne, wenn T (n) minimal ist, d.h. es existiert kein anderer optimaler Algorithmus, der die Laufzeit von T (n) unterbietet.
3
Grundlegende parallele Algorithmen
In diesem Absatz sollen einige grundlegende Techniken f¨ ur den Entwurf effizienter paralleler Algorithmen vorgestellt werden.
3.1
Ein optimaler Algorithmus fu afix-Summen ¨ r Pr¨
Wir betrachten zun¨ achst das folgende Problem: Definition 3 Pr¨ afix-Summen Problem Gegeben ist eine Sequenz von n Elementen x1 , . . . , xn einer Menge Σ und eine bin¨ are assoziative Operation ⊗ : Σ × Σ → Σ. Bestimme die Folge s1 , . . . , sn mit si = x1 ⊗ x2 ⊗ . . . ⊗ xi .
12
Mit Hilfe einer Pr¨ afix-Summe k¨ onnen wir die Addition zweier Bin¨arzahlen cn+1 cn cn−1 . . . c0 = an an−1 . . . a0 + bn bn−1 . . . b0 berechnen. Hierf¨ ur betrachten wir Σ = {(0, 0), (0, 1), (1, 0), (1, 1)} und setzen xi = (ai , bi ). Die assoziative Operation ⊗ definieren wir wie folgt: y wenn y ∈ {(0, 0), (1, 1)} x⊗y = x sonst. Das Ergebnis der Addition 1 1 1 ci = 1 0
erhalten wir durch folgende Transformation: wenn 0 ≤ i ≤ n und si ∈ {(0, 1), (1, 0)} wenn 0 < i ≤ n, si−1 = (0, 0) und (ai , bi ) ∈ {(0, 1), (1, 0)} wenn 0 < i ≤ n, si−1 = (1, 1) und (ai , bi ) ∈ {(0, 0), (1, 1)} wenn i = n + 1 und sn = (1, 1) sonst.
Algorithmus 4 l¨ ost das Pr¨ afix-Summen Problem f¨ ur Sequenzen der L¨ange n = 2k mit k ∈ N: Algorithm 4 Praefix-Summe-Rekursiv Eingabe: x1 , . . . , xn Ausgabe: s1 , . . . , sn 1: if n = 1 then 2: s1 = x1 ; 3: else 4: for i = 1 to n par do yi = x2i−1 ⊗ x2i end for 5: rekursive f¨ uhre Praefix-Summe-Rekursiv auf y1 , . . . , yn/2 aus 6: sei z1 , . . . , zn/2 das Ergebnis dieser Berechnung 7: for i = 1 to n par do 8: if i ist gerade then si = zi/2 9: else if i = 1 then s1 = x1 10: else si = z(i−1)/2 ⊗ xi end if 11: end for 12: end if 13: return s1 , . . . , sn Theorem 2 Algorithmus 4 l¨ ost das Pr¨ afix-Summen Problem f¨ ur Sequenzen der L¨ ange n = 2k mit k ∈ N in Zeit T (n) = O(log n) und Arbeit W (n) = O(n). Beweis: Der Beweis erfolgt u andige Induktion u ¨ber vollst¨ ¨ber k, wobei n = 2k ist. F¨ ur k = 0 wird der Algorithmus nicht rekursiv aufgerufen, somit f¨ uhrt der Algorithmus nur eine konstante Anzahl von Operationen aus. Sei t der Zeitbedarf von Algorithmus 4 bei k ≥ 1 ohne Ausf¨ uhrung des rekursiven Aufrufs. Da wir sowohl die Aufarbeitung der Ausgabe, als auch die Berechnung der einzelnen Positionen yi und si parallel berechnen, ist t konstant. Wir nehmen nun an, dass f¨ ur ein k ∈ N der komplette Algorithmus in Zeit t · log n und Arbeit 2 · t · n ausgef¨ uhrt wird. F¨ ur k + 1 wird dann der Algorithmus in Zeit t · log n f¨ ur den rekursiven Aufruf plus den t Schritten auf der obersten Stufe der Rekursion ausgef¨ uhrt. In der Summe ben¨otigt der Algorithmus somit t · (1 + log n) = t · log 2k+1 Schritte. Betrachten wir nun die Arbeit, die der Algorithmus ausf¨ uhrt. Wie zuvor vernachl¨assigen wir zun¨achst den rekursiven Aufruf. Da der Algorithmus dann t Schritte ben¨otigt, und bei k + 1 maximal 2 · n Prozessoren aktiv arbeiten, ben¨ otigen wir hierf¨ ur eine Arbeit von 2 · t · n. Durch den rekursiven Aufruf erh¨oht sich dieser Aufwand noch einmal um 2 · t · n. In der Summe ist die Arbeit 4 · t · n = 2 · t · 2k+1 . Es verbleibt noch zu zeigen, dass der Algorithmus das korrekte Ergebnis berechnet. Dieses folgt ebenfalls aus der Induktion. Die Induktionshypothese impliziert, dass z1 , . . . , zn die Pr¨afix-Summe von y1 , . . . , yn ist. Da yi = x2i−1 ⊗ x2i ist, erhalten wir zi = y1 ⊗ . . . ⊗ yi = x1 ⊗ x2 ⊗ . . . ⊗ x2i−1 ⊗ x2i = s2i . 13
F¨ ur die ungeraden Stellen in der Sequenz s1 , . . . , s2i gilt (zi−1 ⊗ x2i−1 ) = (x1 ⊗ x2 ⊗ . . . ⊗ x2i−3 ⊗ x2i−2 ) ⊗ x2i−1 = s2i−1 . Der Algorithmus berechnet somit die Pr¨ afix-Summe korrekt. Um das WT-Scheduling durchzuf¨ uhren ist es jedoch sinnvoll die Rekursion aufzul¨osen. Algorithm 5 Praefix-Summe Eingabe: x1 , . . . , xn Ausgabe: s1 , . . . , sn 1: for i = 1 to n par do B(0, i) = xi end for 2: for i = 1 to log n do 3: for j = 1 to n/2i par do B(i, j) = B(i − 1, 2j − 1) ⊗ B(i − 1, 2j) end for 4: end for 5: for i = log n to 0 do 6: for j = 1 to n/2i par do 7: if j ist gerade then C(i, j) = C(i + 1, j/2) 8: else if j = 1 then C(i, 1) = B(i, 1) 9: else C(i, j) = C(i + 1, (j − 1)/2) ⊗ B(i, j) end if 10: end for 11: end for 12: return s1 , . . . , sn = C(0, 1), . . . , C(0, n)
3.2
Pointer Jumping
Oft lassen sich Probleme f¨ ur parallele Berechnungen auf das Finden der Wurzeln der B¨aume in einem gerichteten Wald4 reduzieren. Wir betrachten hierbei die folgende Datenstruktur zur Darstellung eines Waldes F mit n Knoten v1 , . . . , vn : Gegeben sei ein Array P der L¨ange n. Der Wald wird in P wie folgt dargestellt: F¨ ur alle Wurzeln vi setzen wir P (i) = i und f¨ ur alle Kanten von einem Knoten vi zu einem Knoten vj setzen wir P (i) = j. Die Aufgabe ist es nun zu jedem Knoten vi die dazugeh¨orige Wurzel vk zu finden und im Array P zu speichern, d.h. S(i) = k zu setzen. Einen einfachen sequentiellen Algorithmus erhalten wir, indem wir zun¨achst alle Wurzeln identifizieren, und dann mit Hilfe einer Breiten- bzw. Tiefensuche alle Knoten mit den Wurzeln der entsprechenden B¨aume markieren. Ein effizientes paralleles Programm erhalten wir, indem wir zun¨achst S(i) = P (i) setzen, und dann sukzessive S(i) auf den jeweiligen Nachfolger des Nachfolgers zu setzen. Die Entfernung, die wir mit unserem Nachfolgerzeiger zeigen, verdoppelt sich somit von Runde zu Runde, d.h. in Runde ku ucken wir mit dem Zeiger S(i) eine Distanz von 2k Knoten – es sei denn, wir haben die Wurzel ¨berbr¨ erreicht. Algorithm 6 Pointer-Jumping Eingabe: Wald in P (1), . . . , P (n) Ausgabe: S(1), . . . , S(n), wobei S(i) die Wurzel des Baums mit vi ist. 1: for i = 1 to n par do 2: S(i) = P (i) 3: while S(i) 6= S(S(i)) do 4: S(i) = S(S(i)) 5: end while 6: end for Wir betrachten nun die folgende Verallgemeinerung des Wurzel-Finde-Problems: Gegeben ist ein gerichteter Wald mit Knotenmenge V = {v1 , . . . , vn }, Kantenmenge E ⊆ {(vi , vj )|vi , vj ∈ V } und Knotengewichten W (vi ) ∈ N f¨ ur alle vi ∈ V . F¨ ur einen Knoten 4 Wir
gehen davon aus, dass alle B¨ aume von den Bl¨ attern zur Wurzel hin gerichtet sind.
14
vi sei Πi die Knotenmenge auf dem Pfad von vi zur entsprechenden Wurzel. Unter dem Parallel-Pr¨ afix Problem aufPeinem gerichteten Wald verstehen wir das Problem f¨ ur jeden Knoten vi den Wert von v∈Πi W (v) zu berechnen. Dieses Problem kann durch folgende Verallgemeinerung des oben angegebenen Algorithmus gel¨ost werden: Algorithm 7 Wald-Parallel-Praefix Eingabe: Wald in P (1), . . . , P (n) Ausgabe: S(1), . . . , S(n), wobei S(i) die Wurzel des Baums mit vi ist. 1: for i = 1 to n par do 2: S(i) = P (i) 3: while S(i) 6= S(S(i)) do 4: W (i) = W (i) + W (S(i)) und S(i) = S(S(i)) 5: end while 6: end for ¨ Aus der zu Beginn dieses Absatzes angestellten Uberlegung folgt unmittelbar: Theorem 3 Die Algorithmen 6 und 7 finden zu jedem Knoten vi die entsprechenden Wurzeln bzw. l¨ osen das Parallel-Pr¨ afix Problem auf einem gerichteten Wald in Zeit O(log h) und Arbeit O(n · log h), wobei n die Anzahl der Knoten und h die maximale Tiefe der B¨ aume angibt.
3.3
Divide and Conquer
Im Wesentlichen besteht jeder Divide-and-Conquer Algorithmus aus den folgenden drei Schritten: 1. Teile die Eingabe in mehrere etwa gleichgroße Teile. 2. L¨ose das Problem auf jeder der Teileingaben. 3. Verkn¨ upfe die Ergebnisse der Teilprobleme zu einer L¨osung f¨ ur die ganze Eingabe. Da der zweite Schritt auf sehr einfache Art und Weise parallel abgearbeitet werden kann, h¨angt der Erfolg der Parallelisierung von der effizienten Bearbeitung des ersten und dritten Schritts ab. Wir werden als Beispiel f¨ ur ein Problem, welches wir mit Hilfe der Divide-and-Conquer Methode l¨osen k¨onnen, das Problem die konvexe H¨ ulle zu bestimmen heranziehen. 3.3.1
Die konvexe H¨ ulle
Definition 4 Konvexe H¨ ulle in der Ebene Gegeben sei eine Menge S von n Punkten v1 , . . . , vn in einer Ebene. Die konvexe H¨ ulle ist das kleinste konvexe Polygon, welches alle Punkte beinhaltet. Ein Polygon Q nennen wir konvex, wenn f¨ ur alle Punkte p, q auf dem Polygon das Segment mit den Endpunkten p, q vollst¨ andig in Q (oder auf Q) liegt. Das Problem die konvexe H¨ ulle zu bestimmen besteht darin eine geordnete Folge CH(S) einer Teilmenge von S zu finden, so dass CH(S) die konvexe H¨ ulle im Uhrzeigersinn entlang l¨ auft. F¨ ur dieses Problem ist bekannt, dass T ∗ (n) = Θ(n log n) ist. Einen optimalen sequentiellen Algorithmus k¨onnen wir mit Hilfe einer Divide-and-Conquer Strategie finden. Im Folgenden wollen wir einen parallelen Algorithmus f¨ ur dieses Problem untersuchen. Zur Vereinfachung gehen wir im Folgenden davon aus, dass keine zwei Knoten die gleichen x- und y-Koordinaten haben. Wir teilen zun¨ achst das Problem in zwei Teile, indem wir den Knoten p mit minimaler x-Koordinate und den Knoten q mit maximaler x-Koordinate suchen. Diese beiden Knoten liegen auf der konvexen H¨ ulle und teilen diese in zwei Teile: die obere H¨ ulle und die untere H¨ ulle.5 Sei OH(S) der Anteil der konvexen H¨ ulle, der oberhalb des Segments (p, q) liegt, und UH(S) der Anteil der konvexen H¨ ulle, der 5 p und q k¨ onnen wir in logarithmischer paralleler Zeit bei Arbeit O(n log n) durch Sortieren der Punkte nach deren x-Koordinate auf einer EREW-PRAM l¨ osen. Auf das Problem des Sortierens werden wir sp¨ ater im Detail eingehen.
15
unterhalb von (p, q) liegt. Im Folgenden werden wir uns darauf beschr¨anken OH(S) zu bestimmen. Die Berechnung von UH(S) verl¨ auft analog. In einem ersten Schritt teilen wir die Menge S gem¨aß ihrer x-Koordinaten in zwei Teile und bestimmen deren oberen H¨ ulle: Sei v1 , . . . , vn die nach den x-Koordinaten sortierte Folge der Punkte, d.h. f¨ ur alle i < n gilt: Die x-Koordinate von vi ist kleiner als die x-Koordinate von vi+1 . Sei S1 = {v1 , . . . , vn/2 } und S2 = {vn/2+1 , . . . , vn } und OH(S1 ) = (p1 , . . . , ps ) und OH(S2 ) = (p01 , . . . , p0t ) die entsprechenden oberen H¨ ullen. Im n¨achsten Schritt vereinigen wir diese oberen H¨ ullen: Finde einen Punkt pi aus OH(S1 ) und einen Punkt p0j aus OH(S2 ), so dass alle Punkte aus OH(S1 ) und alle Punkte aus OH(S2 ) unterhalb dieses Segments liegen. Dieses Problem k¨onnen wir sequentiell in logarithmischer Zeit mit einer bin¨aren Suche l¨osen. ullen als auch die Wir erhalten OH(S) = (p1 , . . . , pi , p0j , . . . , p0t ). Sind sowohl die L¨angen s, t der oberen H¨ onnen wir dieses Problem aus einer CREW-PRAM in konstanter Indizes der Punkte pi , p0j bekannt, so k¨ Zeit l¨osen. Theorem 4 Der oben dargestellte Algorithmus berechnet die obere H¨ ulle korrekt. Er ben¨ otigt O(log2 n) Schritte und Arbeit O(n · log n). Beweis: Die Korrektheit folgt aus einer einfachen Induktion u ¨ber n. Wir wollen daher hier nur auf seine Zeitkomplexit¨ at T (n) und Arbeitskomplexit¨at W (n) eingehen. Diese Gr¨oßen k¨onnen wir mit Hilfe von Rekursionsformeln einfach beschreiben. Es gilt: 1. F¨ ur eine Eingabe von konstanter Gr¨oße ben¨otigen wir nur eine konstante Anzahl von Operationen. 2. Die Aufteilung in die Teilmengen S1 und S2 durch paralleles Sortieren ben¨otigen wir Zeit O(log n) und Arbeit O(n · log n). 3. Die Bestimmung von OH(S1 ) und OH(S2 ) erfolgt parallel in Zeit T (N/2) und Arbeit W (n/2). 4. Um pi und p0j zu berechnen, ben¨ otigen wir sequentielle logarithmische Zeit. 5. Das Kombinieren der beiden oberen Teilh¨ ullen erfolgt parallel in konstanter Zeit und bei Arbeit O(n). Fassen wir zusammen, so sehen wir T (n) ≤ T (n/2) + a · log n und W (n) ≤ 2 · W (n/2) + b · n · log n . F¨ ur die Zeitkomplexit¨ at erhalten wir unmittelbar T (n) = O(log2 n). Mit Hilfe des Master Theorems6 erhalten wir W (n) = O(n · log n). F¨ ur die konvexe H¨ ulle gilt: Korollar 1 Die konvexe H¨ ulle kann parallel in Zeit O(log2 n) und Arbeit O(n · log n) bestimmt werden. 6 Sei H(n) = a · H(n/2) + f (n), dann gilt f¨ ur f (n) ∈ o(nlogb a ) H(n) ∈ Θ(nlogb a ), f¨ ur f (n) ∈ Θ(nlogb a ) H(n) ∈ Θ(nlogb a log n) und f¨ ur f (n) ∈ ω(nlogb a ) H(n) ∈ Θ(nlogb a log n) mit a · f (n/b) < c · f (n) f¨ ur eine Konstante c < 1 H(n) ∈ Θ(f (n)).
16
3.3.2
Partitionierung
Beim Divide-and-Conquer haben wir bisher untersucht, inwieweit eine Aufteilung eines Problems in Teilprobleme zu einem effizienten parallelen Algorithmus f¨ uhren kann. Hierbei liegt im Allgemeinen die wesentliche Problematik in dem Kombinieren der Teill¨osungen. Nahe verwandt zu dieser Strategie ist die Strategie der Partitionierung. Hierbei teilen wir ein Problem in p nahezu gleichgroße Teilprobleme, wobei p die Anzahl der zur Verf¨ ugung stehenden Prozessoren angibt. Als Beispiel f¨ ur eine solche Strategie wollen wir das Zusammenf¨ ugen zweier sortierter Listen betrachten: Definition 5 Merging Problem Gegeben seien zwei sortierte Listen A = (a1 , . . . , an ) und B = (b1 , . . . , bm ) u ¨ber einer linear geordneten Menge Σ.7 Bestimme die sortierte Liste C = (c1 , . . . , cn+m ) der Elemente aus A und B. Um das Merging Problem zu l¨ osen, bestimmen wir zun¨achst den Rang eines Elements x ∈ Σ in einer (unsortierten) Liste X = (x1 , . . . , xn ): rank(x : X) = |{ i | xi ≤ x }| . F¨ ur zwei Listen X = (x1 , . . . , xn ) und Y = (y1 , . . . , ym ) sei rank(Y : X) = (rank(y1 : X), . . . , rank(ym : X)) . Seien A = (a1 , . . . , an ) und B = (b1 , . . . , bm ) zwei sortierte Listen. Zur Vereinfachung nehmen wir an, dass alle Elemente in A und B verschieden sind. Um eine sortierte Liste aus A, B zu generieren, m¨ ussen wir den Rang jedes Elements x aus der Vereinigung A∪B der Elemente aus A und B in A∪B bestimmen. Es gilt: rank(x : A ∪ B) = rank(x : A) + rank(x : B) . Da der Rang der Elemente aus A in A bei einer sortierten Liste A durch dessen Position in A gegeben ist, m¨ ussen wir in erster Linie die Listen rank(A : B) = (r1 , . . . , rn ) rank(B : A) = (s1 , . . . , sm ) bestimmen. Die Position eines Elements ai in C ergibt sich dann durch ri + i. Analog erhalten wir die Position eines Elements bi durch si + i: Algorithm 8 Merge-I Eingabe: sortierte Listen A = (a1 , . . . , an ) und B = (b1 , . . . , bm ) Ausgabe: sortierte Listen C = (c1 , . . . , cn+m ) 1: for i = 1 to n par do ¨ 2: Uber bin¨ are Suche bestimme ri = rank(ai : B) und setze cri +i = ai . 3: end for 4: for i = 1 to m par do ¨ 5: Uber bin¨ are Suche bestimme si = rank(bi : A) und setze csi +i = bi . 6: end for F¨ ur den Algorithmus 8 gilt T (n, m) = O(log n + log m) und W (n, m) = O(n log n + m log m). Ein sequentielles Verfahren zum Vereinigen zweier sortierter Listen ben¨otigt jedoch nur O(n) Schritte. Das oben angegebene Verfahren ist somit nicht optimal. Um einen optimalen Algorithmus zu erhalten, partitionieren wir die beiden Listen A und B in Teillisten Ai , Bi mit i ∈ {1, . . . , k(m)} f¨ ur k(m) = m/ log m, s.d. sich die sortierte Liste C aus den zusammengef¨ ugten Teilen Ci = merge(Ai , Bi ) zusammensetzt.8 Um diese Partitionierung zu erhalten, verfahren wir wie folgt: Eine einfache Analyse dieses Algorithmus zeigt: 7 Eine Menge Σ nennen wir linear geordnet, wenn eine Relation ≤ existiert, s.d. f¨ ur alle a, b ∈ Σ gilt a ≤ b oder b ≤ a. Diese Relation muss reflexiv, antisymmetrisch und transitiv sein. 8 Zur Vereinfachung nehmen wir an, dass log m und m/ log m nat¨ urliche Zahlen sind.
17
Algorithm 9 Partition Eingabe: sortierte Listen A = (a1 , . . . , an ) und B = (b1 , . . . , bm ) Ausgabe: k(m) = m/ log m Partitionen Ai , Bi 1: Setze t0 = 0 und tk(m) = n. 2: for i = 1 to k(m) − 1 par do ¨ 3: Uber bin¨ are Suche bestimme ti = rank(bi log m : A). 4: end for 5: for i = 0 to k(m) − 1 par do 6: Setze Bi := (bi log m+1 , . . . , b(i+1) log m ). 7: Setze Ai := (ati +1 , . . . , ati+1 ) (Ist ti = ti+1 , so ist Ai leer). 8: end for
Lemma 1 Algorithmus 9 generiert eine Partitionierung der Listen A und B in A1 , . . . , Ak(m) und B1 , . . . , Bk(m) , wobei die folgenden Eigenschaften erf¨ ullt sind: 1. |Bi | = log m f¨ ur alle i ∈ {1, . . . , log m} und 2. Sei Ci die Folge, die wir durch die Vereinigung der Listen Ai und Bi erhalten, dann ist C = (C1 , . . . , Ck(m) ). Die Laufzeit dieses Verfahrens ist O(log n) bei Arbeit9 O(n + m) und Prozessorzahl O(m/ log m). Ein Algorithmus, der nun die Listen Ai und Bi zur sortierten Liste Ci sequentiell verschmelzen w¨ urde, h¨atte ein Problem, wenn |Ai | ∈ Θ(n) liegt. Daher f¨ uhren wir den Algorithmus 9 parallel f¨ ur alle Partitionen Bi , Ai aus, wobei wir die Rollen von A und B P innerhalb des Algorithmus vertauschen. Dieses kann in Zeit O(log |Bi |) = O(log log m) und Arbeit O( i |Ai | + |Bi |) = O(n + m) geschehen. Die resultierenden Teillisten Ai,j , Bi,j haben alle eine L¨ange von O(log(n + m)). F¨ uhren wir parallel auf allen Paaren Ai,j , Bi,j P eine sequentielle Merge-Operation aus, so ben¨otigen wir maximal O(log(n + m)) Schritte und Arbeit O( i,j |Ai,j |+|Bi,j |) = O(n+m). Sei Ci,j das Resultat einer solchen Verschmelzung zweier Teillisten Ai,j , Bi,j , dann gilt: C
= (C0,0 , . . . , C0,k(n)−1 , C0,1 , . . . , Ck(m)−2,k(n)−1 , C0,k(n)−1 , . . . , Ck(m)−1,k(n)−1 ) .
Fassen wir zusammen, so sehen wir: Theorem 5 Seien A und B zwei sortierte Listen der L¨ ange n bzw. m, dann k¨ onnen wir diese Listen zu einer neuen sortierten Liste parallel in Zeit O(log(n + m)) bei Arbeit O(n + m) verschmelzen.
3.4
Pipelining
Im Wesentlichen besteht das Pipelining aus folgender Strategie: Unterteile ein Verfahren A in m Teile A1 , . . . , Am , welche sequentiell ausgef¨ uhrt werden k¨onnen, d.h. f¨ ur eine Eingabe w erhalten wir A(w) = Am (· · · (A1 (w)) · · · ). Hierbei ist darauf zu achten, dass f¨ ur den Fall, dass k Eingaben w1 , . . . , wk anliegen, die einzelnen Teile A1 , . . . , Am des Verfahrens zeitlich versetzt auf die Eingaben angewendet werden k¨onnen. Unser Ziel ist es, dass zum Zeitpunkt t folgende Teile parallel ausgef¨ uhrt werden: • A1 auf wt , • A2 auf A1 (wt−1 ), • A3 auf A2 (A1 (wt−2 )), usw. Das Konzept des Pipelinings kann an folgenden Beispiel gut illustriert werden. 9 Zur Bestimmung der Arbeit m¨ ussen wir darauf achten, dass m log n/ log m < m log(n + m)/ log m ≤ n + m f¨ ur alle n, m ≥ 4 ist.
18
3.4.1
Einf¨ ugen eines Elements in 2-3-B¨ aumen
Ein 2-3-Baum ist ein Baum, in dem alle Bl¨ atter die gleiche Tiefe h haben, und jedes Nicht-Blatt (internen Knoten) 2 oder 3 Nachfolger besitzt. Es folgt sofort: Beobachtung 3 Ein 2-3-Baum der H¨ ohe h hat zwischen 2h und 3h Bl¨ atter. Folglich hat ein 2-3-Baum mit n Bl¨ attern die H¨ ohe Θ(log n). Ein 2-3-Baum kann uns als Datenstruktur dienen, sofern diese effiziente Algorithmen zum Suchen, Einf¨ ugen und L¨ oschen von Elementen zur Verf¨ ugung stellen soll. Die Datenstruktur ist wie folgt aufgebaut: Die n Bl¨ atter des Baums speichern n Elemente s1 < s2 < · · · < sn in einer Von-Links-Nach-Rechts Ordnung. Jeder interne Knoten v speichert zwei bis drei maximale Werte seiner Unterb¨aume. Hierbei ist • L[v] der maximale Wert des linken Unterbaums TL [v], • M [v] der maximale Wert des mittleren Unterbaums TM [v] und • R[v] der maximale Wert des rechten Unterbaums TR [v] (sofern dieser existiert). Existieren nur zwei Nachfolger, so setzten wir TR [v] = TM [v] und R[v] = ∞. Der Einfachheit halber identifizieren wir einen Unterbaum mit dessen Wurzel. Suchen in einem 2-3-Baum: Das Problem, ein Element b in einem 2-3-Baum mit Wurzel v zu finden, kann rekursiv wie folgt gel¨ ost werden: v f¨ ur ein Blatt v finde(TL [v], b) f¨ ur b ≤ L[v] finde(v, b) = finde(TM [v], b) f¨ ur L[v] < b ≤ M [v] finde(TR [v], b) sonst. Das Ergebnis dieser Suche ist ein Blatt v, f¨ ur dessen Wert si ≤ b < si+1 gilt. Die Laufzeit dieses Verfahrens ist O(log n). Einf¨ ugen in einen 2-3-Baum: Um ein Element b in einen 2-3-Baum mit Wurzel vr einzuf¨ ugen, bestimmen wir zun¨ achst das Blatt vi , f¨ ur dessen Wert si ≤ b < si+1 gilt. Ist b = si , so sind wir fertig. Ansonsten generiere ein neues Blatt v 0 mit Wert b und f¨ uge dieses hinter vi bei dessen Vorg¨anger v in der Nachfolgerliste ein. Durch diese Aktion k¨onnen die Daten, die in v bez¨ uglich dessen Nachfolger gespeichert sind, durcheinander geraten. Daher f¨ uhren wir auf v die Prozedur Bereinige aus: Man beachte, dass die F¨ alle mit h > 4 bei einem Einf¨ ugen eines Elements nicht auftreten k¨onnen. Diese F¨alle m¨ ussen jedoch ber¨ ucksichtigt werden, wenn wir eine Folge von k Elementen parallel einf¨ ugen wollen. 3.4.2
Einf¨ ugen einer Sequenz in 2-3-B¨ aumen
Wir betrachten nun den Fall, dass k Elemente b1 < b2 < . . . < bk in einen 2-3-Baum eingef¨ ugt werden sollen. Ohne Beschr¨ ankung der Allgemeinheit gehen wir davon aus, dass s1 < b1 und bk < sn gilt. Sind diese Voraussetzungen nicht erf¨ ullt, so f¨ ugen wir zun¨achst seriell die Elemente b1 und bk in den Baum ein und verfahren mit den verbleibenden Elementen entsprechend dem nun folgendenden Verfahren. In einem ersten Schritt unterteilen wir die Folge b1 < b2 < . . . < bk in n Intervalle B1 , . . . , Bn−1 , wobei f¨ ur alle i ∈ {1, . . . , n − 1} und alle b ∈ Bi si ≤ b < si+1 . Man beachte, dass wir die Bl¨ ocke und die Bl¨atter, die zu den Bl¨ocken geh¨oren in logarithmischer Zeit und mit O(k log n) Operationen berechnen k¨onnen. Wir unterscheiden zun¨achst zwischen den beiden folgenden F¨ allen: 1. F¨ ur alle i ∈ {1, . . . , n} gilt |Bi | ≤ 1, d.h. f¨ ur alle bj existieren zwei Elemente si , si+1 mit bj−1 ≤ si ≤ bj < si+1 ≤ bj+1 – sofern die Elemente bj−1 und bj+1 existieren. 2. Es existieren Intervalle Bi mit |Bi | > 1.
19
Algorithm 10 Bereinige Eingabe: Knoten v in einem Baum, der den Bedingungen eines 2-3-Baums nicht mehr gen¨ ugt Ausgabe: 2-3-Baum 1: v1 , . . . , vh sei die Folge der Nachfolger von v. 2: m1 , . . . , mh seien die maximalen Werte der in den Unterb¨ aumen von v1 , . . . , vh 3: Es gelte m1 < . . . < mh . 4: if h = 3 then 5: Setze L[v] = m1 , M [v] = m2 , R[v] = m3 , TL [v] = v1 , TM [v] = v2 und TR [v] = v3 6: else if h = 4 then 7: Generiere zwei neue Knoten v 0 , v 00 8: Ersetze v in der Nachfolgerliste des Vorg¨angers v 000 von v durch v 0 , v 00 9: Setze L[v 0 ] = m1 , M [v 0 ] = m2 , TL [v 0 ] = v1 , TM [v 0 ] = TR [v 0 ] = v2 10: Setze L[v 00 ] = m3 , M [v 00 ] = m4 , TL [v 00 ] = v3 , TM [v 00 ] = TR [v 00 ] = v4 11: Bereinige(v”’) 12: else if h = 5 then 13: Generiere zwei neue Knoten v 0 , v 00 14: Ersetze v in der Nachfolgerliste des Vorg¨angers v 000 von v durch v 0 , v 00 15: Setze L[v 0 ] = m1 , M [v 0 ] = m2 , R[v 0 ] = m3 , TL [v 0 ] = v1 , TM [v 0 ] = v2 , TR [v 0 ] = v3 16: Setze L[v 00 ] = m4 , M [v 00 ] = m5 , TL [v 00 ] = v4 , TM [v 00 ] = TR [v 00 ] = v5 17: Bereinige(v”’) 18: else 19: Generiere zwei neue Knoten v 0 , v 00 20: Ersetze v in der Nachfolgerliste des Vorg¨angers v 000 von v durch v 0 , v 00 21: Setze L[v 0 ] = m1 , M [v 0 ] = m2 , R[v 0 ] = m3 , TL [v 0 ] = v1 , TM [v 0 ] = v2 , TR [v 0 ] = v3 22: Setze L[v 00 ] = m4 , M [v 00 ] = m5 , R[v 00 ] = m6 , TL [v 00 ] = v4 , TM [v 00 ] = v5 , TR [v 00 ] = v6 23: Bereinige(v”’) 24: end if
Im ersten Fall k¨ onnen wir alle neuen Elemente bi parallel in den Baum einf¨ ugen. Hierbei verdoppeln wir maximal die Anzahl der Vorfahren eines internen Knotens. Verfahren wir hierbei wie Algorithmus 10, so splitten wir diesen internen Knoten in maximal zwei neue Knoten v 0 und v 00 . Da dieses mit jedem Knoten, der der Vorfahre eines Blattes ist, geschehen kann, verdoppelt sich maximal die Anzahl der Nachkommen eines jeden Knotens der H¨ ohe 2 im Baum. Wir k¨onnen f¨ ur diese Knoten somit wieder die Strategie aus Algorithmus 10 anwenden, u.s.w. Zusammenfassend erhalten wir: Algorithm 11 Bereinige-Parallel(`) Eingabe: H¨ohe ` in einem Baum Ausgabe: 2-3-Baum 1: for alle Knoten v im Baum der H¨ ohe ` par do 2: F¨ uhre Bereinige(v) aus, wobei wir die rekursiven Aufrufe in Zeile 11, 17 und 23 vernachl¨assigen 3: end for 4: Bereinige-Parallel(` + 1) Bei diesem Algorithmus haben wir zur Vereinfachung die Sonderf¨alle, die wir bei der Betrachtung der Wurzel ber¨ ucksichtigen m¨ ussten, vernachl¨ assigt. Beobachtung 4 Gilt f¨ ur alle Bl¨ ocke Bi |Bi | ≤ 1, so k¨ onnen die Elemente der Folge b1 , . . . , bk in Zeit O(log n) und Arbeit O(k log n) in einen bestehenden Baum eingef¨ ugt werden. Betrachten wir nun den Fall, dass einige Intervalle Bi mehr als zwei Elemente beinhalten. F¨ ugen wir zun¨achst die mittleren Elemente der Intervalle Bi in die Datenstruktur ein, so halbieren wir in diesem Schritt die L¨ange der noch einzuf¨ ugenden Intervalle und verdoppeln im ung¨ unstigsten Fall die Anzahl der Intervalle. Man beachte, dass die Position der neuen Intervalle durch die Wahl der soeben eingef¨ ugten Elemente und durch die alte Position der alten Intervalle fest bestimmt ist. Wir k¨onnen somit nach
20
dem Einf¨ ugen der mittleren Elemente mit dem Einf¨ ugen der neuen Intervalle unmittelbar fortfahren. Die ben¨otigte Zeit, um alle Elemente einzuf¨ ugen, ist somit O(log k · log n) und die Arbeit O(k · log n). Um diesen Zeitbedarf weiter zu reduzieren, wollen wir das Pipelining-Prinzip anwenden. Betrachten wir einen Einf¨ ugendurchlauf, so erkennen wir, dass die Knoten, welche zu einem Zeitpunkt aktiv ver¨andert werden, wie eine Welle von den Bl¨ attern zur Wurzel laufen. Aufgrund dieser Beobachtung k¨onnen wir mit dem zweiten Einf¨ ugedurchlauf beginnen, wenn die erste Welle Knoten der H¨ohe 2 erreicht hat. Die dritte Welle k¨ onnen wir starten, wenn die zweite Welle die Knoten der H¨ohe 2 erreicht hat, u.s.w. Wir reduzieren somit den Zeitbedarf des Verfahrens auf O(log n + log k) ohne dabei die Arbeit zu erh¨ohen. Theorem 6 Eine sortierte Folge b1 < . . . < bk von k Elementen kann in Zeit O(log n) und Arbeit O(log k + log n) in einen bestehenden 2-3-Baum eingef¨ ugt werden.
3.5
Accelerated Cascading
Unter dem Accelerated Cascading verstehen wir das Verschmelzen von einem schnellen und einem optimalen Algorithmus zu einem Algorithmus, welcher sowohl schnell als auch optimal ist. Die generelle Strategie hierbei ist: 1. Starte mit einem optimalen Algorithmus und reduziere mit diesem die Problemgr¨oße unter einen vorgegebenen Threshold. 2. Auf dieser reduzierten Problemgr¨ oße starte den schnellen (nicht-optimalen) Algorithmus. Als Beispiel f¨ ur das Accelerated Cascading wollen wir das Bestimmen des Maximums aus einer unsortierten Folge von Werten betrachten. 3.5.1
Ein einfacher optimaler Algorithmus f¨ ur das Maximum
Einen optimalen parallelen Algorithmus erhalten wir, indem wir u ¨ber die Eingabe x1 , . . . , xn einen balancierten Bin¨ arbaum spannen. Dieser hat eine Tiefe von log n und n−1 interne Knoten. Die MaximumsBestimmung erfolgt nun von den Bl¨ attern zur Wurzel, indem wir in jedem internen Knoten das Maximum der Werte seiner beiden Vorg¨ anger bestimmen. Wir erhalten somit einen einfachen und optimalen Algorithmus f¨ ur das Maximum. Beobachtung 5 Das Maximum einer unsortierten Folge von n Werten kann parallel in Zeit O(log n) und Arbeit W (n) = O(n) bestimmt werden. Ferner gilt T ∗ (n) ∈ Θ(n). 3.5.2
Berechnung in konstanter Zeit
Wir wollen nun einen Algorithmus vorstellen, der das Maximum in konstanter Zeit berechnet. Bei diesem Verfahren werden wir jedoch eine quadratische Anzahl von Prozessoren benutzen. Das Verfahren ist somit alles andere als optimal. Algorithm 12 Maximum Eingabe: Folge von n Werten x1 , . . . , xn Ausgabe: max = max{x1 , . . . , xn } 1: for 1 ≤ i ≤ n par do 2: setze M (i) = 1 3: end for 4: for 1 ≤ i, j ≤ n par do 5: if xi < xj oder (xi = xj und i > j) then setze M (i) = 0 end if 6: end for 7: for 1 ≤ i ≤ n par do 8: if M (i) = 1 then setze max= i end if 9: end for Es gilt: Beobachtung 6 Das Maximum einer Folge von n Werten kann parallel in Zeit O(1) und Arbeit W (n) = O(n2 ) auf einer CRCW-PRAM bestimmt werden. 21
3.5.3
Berechnung in doppelt-logarithmischer Zeit
Das Level eines Knotens u in einem Baum ist die Anzahl der Kanten von u zur Wurzel des Baums. Einen k Baum mit doppelt-logarithmischer Tiefe von n = 22 Bl¨attern erhalten wir, indem jeder Knoten k−`−1 eines Levels ` < k 22 Nachfolger hat. Knoten im kten Level haben zwei Nachfolger. Mit Hilfe einer Induktion k¨ onnen wir zeigen: k
Beobachtung 7 Sei n = 22 und T ein Baum doppelt-logarithmischer Tiefe mit n Bl¨ attern, dann hat 2k −2k−` 2k−1 T in jedem Level ` < k 2 Knoten. Im kten Level hat T 2 = n/2 Knoten. Folglich ist die Tiefe von T 1 + log log n = k + 1. Basierend auf diesem Baum k¨ onnen wir nun das Maximum in doppelt-logarithmischer Zeit berechnen, indem wir davon ausgehen, dass die Eingabefolge an den Bl¨attern eines Baums doppelt-logarithmischer Tiefe anliegt, und wir das Maximum bottom-up von den Bl¨attern zur Wurzel laufend bestimmen. Zur Auswertung eines Knotens wenden wir Algorithmus 12 an. Die Laufzeit dieses Verfahrens ist somit O(log log n). Die Arbeit dieses Verfahrens ist etwas aufw¨andiger zu bestimmen. Wir erhalten: 1. Zur Auswertung eines Knotens v im Level ` ben¨otigen wir O((22 2. Es gibt 22
k
−2k−`
k−`−1
)2 ) = O(22
k−`
) Operationen.
Knoten im Level `.
3. Zur Auswertung aller Knoten im Level ` ben¨otigen wir O(22 Operationen.
k−`
· 22
k
−2k−`
k
) = O(22 ) = O(n)
4. Zur Auswertung aller Knoten ben¨ otigen wir O(n log log n) Operationen. Beobachtung 8 Das Maximum einer Folge von n Werten kann parallel in Zeit O(log log n) und Arbeit O(n log log n) auf einer CRCW-PRAM bestimmt werden. 3.5.4
Ein schneller optimaler Algorithmus f¨ ur das Maximum
¨ Uber den Accelerated Cascading Ansatz wollen wir nun versuchen den optimalen logarithmisch-zeitbeschr¨ankten Algorithmus und den schnellen doppelt-logarithmisch-zeitbeschr¨ankten Algorithmus zusammen zu f¨ uhren. Hierzu unterteilen wir zun¨ achst die Eingabe in Bl¨ocke der L¨ange log log n und bestimmen das Maximum innerhalb der Bl¨ ocke basierend auf vollst¨andigen bin¨aren B¨aumen. Dieser Schritt kann in Zeit O(log log log n) und Arbeit O(n) erfolgen. Das Ergebnis interpretieren wir als Eingabe der L¨ange n0 = n/ log log n f¨ ur die zweite Phase der Maximumsbestimmung. In dieser Phase wenden wir das Verfahren, welches auf einem Baum mit doppelt-logarithmischer Tiefe basiert an. Die Laufzeit dieser Phase ist O(log log n0 ) = O(log log n), und f¨ ur die Arbeit erhalten wir W (n0 ) = O(n0 log log n0 ) = O(n/ log log n · log log n) = O(n) . Theorem 7 Das Maximum einer unsortierten Folge von n Werten kann parallel in Zeit O(log log n) und Arbeit O(n) auf einer CRCW-PRAM bestimmt werden. Es zeigt sich, dass dieses Ergebnis nicht wesentlich verbessert werden kann. So kann man zeigen, dass das Maximum auf einer O(n)-Prozessor CRCW-PRAM nicht in Zeit o(log log n) bestimmt werden kann. Ferner ist Ω(log n) eine untere Grenze f¨ ur die Laufzeit einer CREW-PRAM – unabh¨angig von der Anzahl der Prozessoren und der erlaubten Arbeit.
3.6
Aufbrechen von Symmetrien
Symmetrien stellen ein wesentliches Problem bei parallelen Algorithmen dar. Wie solche Symmetrien effizient aufgebrochen werden k¨ onnen, werden wir im Folgenden an einem Beispiel diskutieren. Sei G = (V, E) ein gerichteter Kreis, dessen Nachfolgerrelation in einem Feld S gespeichert ist, d.h. gegeben ist ein Feld S mit S(i) = j genau dann, wenn (vi , vj ) ∈ E ist. Unser Ziel ist es eine k-F¨arbung von G zu generieren. Wir suchen also eine Funktion c : {1, . . . , |V |} → {0, . . . , k − 1}, so dass f¨ ur alle Knoten vi ∈ V gilt c(i) 6= c(S(i)) . 22
Ein naheliegender sequentieller Algorithmus durchl¨auft alle Knoten auf dem Kreis und weist jedem Knoten eine Farbe zu. Hierbei berechnet er die L¨ange der bisherigen Tour modulo k. Bis auf f¨ ur den letzten Knoten erf¨ ullt die so generierte F¨arbung die Bedingungen einer k-F¨arbung. Den letzten Knoten m¨ ussen wir unter Umst¨ anden gesondert behandeln. Zusammenfassend k¨onnen wir f¨ ur dieses Problem sagen T ∗ (n) = Θ(n). Wir wollen nun versuchen eine F¨ arbung parallel zu generieren. Betrachten wir jedoch den oben angegebenen Algorithmus, so erhalten wir aus diesem sequentielle Verfahren keine Hinweise f¨ ur ein effizientes paralleles Verfahren. 3.6.1
Verbesserung einer F¨ arbung
Wir betrachten nun ein Verfahren, welches die Anzahl der Farben in einer bestehenden F¨arbung reduziert. Sei i ∈ N und ih−1 . . . i0 die Bin¨ardarstellung von i, dann bezeichnen wir mit [i]k = ik das k.-niederwertigste Bit von i. Um eine F¨ arbung zu verbessern, benutzen wir den folgenden Algorithmus: Algorithm 13 New-Color Eingabe: ein gerichteter Kreis G = (V, E) und eine F¨arbung c Ausgabe: F¨arbung c0 1: for 1 ≤ i ≤ n par do 2: Suche die Position k des niederwertigsten Bits, wo sich c(i) und c(S(i)) unterscheiden. 3: Setze c0 (i) := 2k + [c(i)]k . 4: end for
Lemma 2 Ist c eine g¨ ultige F¨ arbung, so generiert New-Color in Zeit O(1) und Arbeit O(n) eine g¨ ultige F¨ arbung. Beweis: Der Zeitbedarf und die Anzahl der ausgef¨ uhrten Operationen h¨angen von der Wahl des PRAMModells ab. Erlauben wir jedoch einer PRAM sowohl k als auch 2k + [c(i)]k in konstanter Zeit zu bestimmen, so folgen die Zeit- und Arbeitskomplexit¨at von New-Color unmittelbar. Die G¨ ultigkeit einer neuen F¨ arbung folgt aus einer Widerspruchsannahme. Wir nehmen also an, dass f¨ ur eine Kante (vi , vj ) ∈ E gilt 2k + [c(i)]k
= c0 (i) = c0 (j) = 2h + [c(j)]h ,
wobei k und h die Positionen sind, die in New-Color f¨ ur die F¨arbungen der Knoten vi und vj bestimmt wurden. Da [c(i)]k und [c(j)]h nur das niederwertigste Bit der neuen F¨arbung beeinflussen, muss vor allem k = h sein. Somit gilt auch [c(i)]k = [c(j)]h = [c(j)]k . Dieses steht im Widerspruch zu der Voraussetzung, dass sich c(i) und c(j) an der Position k unterscheiden m¨ ussen. 3.6.2
Ein sehr schneller F¨ arbungsalgorithmus
Zun¨achst wollen wir analysieren, wie weit Algorithmus 13 die gegebene F¨arbung c ver¨andert. Sei t ∈ N die maximale Zahl an Bitpositionen, die wir ben¨otigen, um die Farben der F¨arbung c darzustellen. Ist t > 3, so reduziert sich die Anzahl der ben¨otigten Bitpositionen der F¨arbung c0 auf dlog te + 1 < t. F¨ ur t = 3 bewirkt ein weiterer Aufruf von Algorithmus 13, dass nur noch 6 Farben m¨oglich sind. Um eine beliebige F¨ arbung auf 6 Farben zu reduzieren, m¨ ussen wir den Algorithmus O(log∗ t) mal aufrufen. ∗ Hierbei ist die Funktion log t wir folgt definiert: Sei log n f¨ ur i = 1 (i) log n = log log(i−1) n f¨ ur i > 1 , dann ist log∗ n = min{ i | log(i) n ≤ 1 } . Aus Lemma 2 k¨ onnen wir nun folgern:
23
Lemma 3 Ist c eine g¨ ultige F¨ arbung, so k¨ onnen wir durch iteriertes Aufrufen von New-Color in Zeit O(log∗ n) und Arbeit O(n log∗ n) eine g¨ ultige 6-F¨ arbung generieren. Als Initialf¨ arbung k¨ onnen wir jedem Knoten vi seinen Index u ¨bergeben. Aus Lemma 3 folgt nun, dass wir eine 6-F¨ arbung in Zeit O(log∗ n) und Arbeit O(n log∗ n) generieren k¨onnen. Wir wollen der Frage nachgehen, wie wir aus einer 6-F¨ arbung eine 3-F¨arbung in konstanter Zeit und mit O(n) Operationen generieren k¨ onnen. Aus der Konstruktion folgt, dass die Farben in c {0, 1, 2, 3, 4, 5} sind. F¨ ur die Farben c ∈ {3, 4, 5} verfahren wir jetzt wie folgt: 1: 2: 3: 4: 5: 6: 7: 8:
for i = 3 to 5 do for all vj ∈ V par do if c(j) = i then Bestimme eine Farbe h ∈ {0, 1, 2}, die nicht in der Nachbarschaft von vj benutzt wird. Setze c(j) = h end if end for end for Es folgt:
Theorem 8 Eine g¨ ultige 3-F¨ arbung eines gerichteten Kreises kann in Zeit O(log∗ n) und Arbeit O(n log∗ n) generiert werden. 3.6.3
Ein optimaler 3-F¨ arbungsalgorithmus
Zwar ist der im letzten Abschnitt angegebene Algorithmus sehr schnell, jedoch aufgrund des log∗ n Faktors nicht optimal. Diesen Faktor k¨ onnen wir jedoch mit Hilfe des letzten oben angegebenen ProgrammFragments los werden. Bevor wir jedoch den Algorithmus vorstellen, ben¨otigen wir noch eine Bemerkung u ¨ber das parallele Sortieren bei einer beschr¨ anketen Anzahl von verschiedenen Werten. Beobachtung 9 Benutzen wir eine Kombination des Radix-Sort-Algorithmus und des Pr¨ afix-SummenAlgorithmus, so k¨ onnen wir n ganzen Zahlen aus der Menge {0, . . . , O(log n)} in Zeit O(log n) und Arbeit O(n) parallel sortieren. Algorithm 14 Color Eingabe: ein gerichteter Kreis G = (V, E) Ausgabe: 3-F¨ arbung c 1: for 1 ≤ i ≤ n par do 2: Setze c0 (i) = i 3: end for 4: Setzte c =New-Color(G, c0 ). 5: Sortiere alle Knoten nach ihrer Farbe. 6: for i = 3 to 2dlog ne do 7: for all vj ∈ V par do 8: if c(j) = i then 9: Bestimme eine Farbe h ∈ {0, 1, 2}, die nicht in der Nachbarschaft von vj benutzt wird. 10: Setze c(j) = h 11: end if 12: end for 13: end for Man beachte, dass die einmalige Anwendung von New-Color aus c0 eine F¨arbung mit O(log n) Farben generiert. Somit ist das Sortieren nach diesen Farben effizient nach Beobachtung 9 m¨oglich. Es folgt: Theorem 9 Eine g¨ ultige 3-F¨ arbung eines gerichteten Kreises kann in Zeit O(log n) und Arbeit O(n) generiert werden. 24
Wir sollten hier noch kurz diskutieren, warum der Sortierschritt im Algorithmus 14 n¨otig ist. Ohne die Knoten nach der Farbe zu sortieren, w¨are es nur schwer m¨oglich in den einzelnen Schleifendurchl¨aufen in Zeile 6 nur so viele Prozessoren zu aktivieren, wie Knoten einer Farbe i > 2 vorhanden sind. Liegen die Knoten jedoch nach Farben sortiert vor, so k¨onnen wir jeden Schleifendurchlauf in konstanter Zeit und mit O(ni ) Operationen – wobei ni die Anzahl der Knoten mit Farbe i ist – bearbeiten. Der Arbeitsaufwand ergibt sich somit durch 2dlog ne X W (n) = O(n + ni ) = O(n) . i=3
4
Reihenfolge in Listen
Wir wollen uns nun einem weiteren algorithmischen Problem zuwenden, dem Bestimmen der Reihenfolge in einer Liste. Bei verschiedenen Anwendungen, wie zum Beispiel dem Berechnen einer Pr¨afixsumme, wurde von uns bisher implizit angenommen, dass die durch die Liste gegebene Reihenfolge auch der Ordnung der Elemente entspricht. Im Folgenden werden wir der Frage nachgehen, wie wir die Reihenfolge der Elemente bestimmen k¨ onnen, wenn die Ordnung u ¨ber eine Folge von Zeigern gegeben ist. In diesem Kapitel wollen wir uns mit der Bestimmung der R¨ange der Elemente in einer doppelt verketteten Liste besch¨aftigen. Gegeben ist die Folge der Knoten 1, 2, . . . , n, eine Vorg¨angerfunktion P und eine Nachfolgerfunktion S. Besitzt ein Element i keinen Vorg¨anger, dann ist P (i) = 0. Analog ist S(i) = 0, wenn i keinen Nachfolger hat. Im List-Ranking-Problem geht es darum, den Abstand R(i) jedes Elements i vom Ende der Liste zu bestimmen: 0 f¨ ur S(i) = 0 R(i) := 1 + R(S(i)) sonst.
4.1
List-Ranking mit Hilfe von Pointer-Jumping
Eine naheliegende L¨ osung erhalten wir u ¨ber die Pointer-Jumping-Technik (Algorithmus 6): Algorithm 15 List-Ranking-PJ Eingabe: Eine doppelt verkettete Liste mit Vorg¨anger- und Nachfolgerfunktion P und S Ausgabe: Die Abst¨ ande R(1), . . . , R(n) der Listenelemente vom Ende der Liste. 1: for i = 1 to n par do 2: Q(i) = S(i) 3: if S(i) 6= 0 then R(i) := 1 else R(i) = 0 end if 4: end for 5: for i = 1 to n par do 6: while Q(i) > 0 oder Q(Q(i)) > 0 do 7: R(i) = R(i) + R(Q(i)), Q(i) = Q(Q(i)) 8: end while 9: end for
Beobachtung 10 Algorithmus 15 l¨ ost das List-Ranking-Problem in Zeit O(log(n)) und mit Hilfe von O(n log(n)) Operationen.
4.2
Ein O(log(n))-zeitbeschr¨ ankter arbeitsoptimaler Algorithmus
Um das List-Ranking-Problem in logarithmischer Zeit zu l¨osen, ohne dabei die Anzahl der auszuf¨ uhrenden Operationen um mehr als einen konstanten Faktor zu erh¨ohen, m¨ ussen wir eine Strategie zum Verkleinern der Eingabeliste einf¨ uhren. Hierbei verfahren wir wie folgt: 1. Verkleinere die Liste, bis die Liste nur noch O(n/ log(n)) Elemente enth¨alt. 2. F¨ uhre Algorithmus 15 auf der reduzierten Liste aus.
25
3. Expandiere die Liste durch das Wiedereinf¨ ugen der im ersten Schritt gel¨oschten Elemente. Bestimme hierbei den Rang der jeweiligen Elemente. Eine Methode, um eine Liste parallel zu verkleinern, basiert auf dem L¨oschen einer unabh¨angigen Menge I von Knoten. Wir nennen eine Menge von Knoten I (paarweise) unabh¨ angig, wenn f¨ ur alle i ∈ I gilt S(i) 6∈ I. Wir wollen uns hier auf das Problem der Listenverkleinerung beschr¨anken. Im Wesentlichen arbeitet diese Strategie wie folgt: Jedem Prozessor Pi mit 1 ≤ i ≤ logn n weisen wir einen Block Bi = [(i − 1) · log(n) + 1, . . . , i · log(n)] zu. Ausgehend von den Knoten j ∈ Bi dieses Blocks versucht Pi die jeweiligen Nachfolger S(j) aus der Liste zu entfernen. Zur Indizierung des jeweils aktuellen Werts von j benutzt Pi einen Zeiger p(i), der mit p(i) := (i − 1) · log(n) + 1 initialisiert wird. Einen im Schritt t auf diese Weise indizierten Knoten nennen wir aktiv (im Schritt t) und alle anderen Knoten passiv (im Schritt t). Ist ein Knoten i aktiv im Schritt t und seine Nachbarn S(i) und P (i) passiv, so nennen wir i isoliert. Da jeder isolierte Knoten in einem Schritt t unabh¨angig von jedem anderen im Schritt t aktiven Knoten ist, k¨onnen wir diese Knoten im Schritt t entfernen und die dazugeh¨ origen Zeiger auf p(i) := p(i) + 1 setzen. In der Regel werden jedoch einige aktive Knoten Ketten bilden und k¨onnen daher nicht so einfach entfernt werden. Diese aktiven nichtisolierten Knoten nennen wir Kettenknoten. Um einige dieser Kettenknoten dennoch entfernen zu k¨ onnen, werden wir uns einer (2 log(log(n)) + 2)-F¨arbung bedienen. Man beachte, dass eine solche F¨ arbung mit Hilfe von zwei Aufrufen der Prozedur New-Color in konstanter Zeit und mit O(nt ) Operationen bestimmt werden kann – nt ist hierbei die Anzahl der im Schritt t aktiven Knoten. Wir markieren nun die jeweils ersten Knoten jeder Kette sowie alle lokalen Minima in der F¨arbung als Hirten und die verbleibenden Knoten innerhalb einer Kette als Schafe. Den zweiten und den letzten Knoten einer Kette markieren wir immer als Schaf. F¨ ur einen Hirten i sei H(i) die Menge der als Schafe markierten Knoten zwischen i und dem n¨achsten Hirten in der Folge. Wir wollen diesen Schritt im Folgenden als HS-Schritt bezeichnen. Beobachtung 11 F¨ ur jeden Hirten i gilt 1 ≤ |H(i)| ≤ 4 log(log(n)) + 2. Beobachtung 12 Die Menge der ersten Schafe hinter den Hirten zusammen mit der Menge der isolierten Knoten bildet eine unabh¨ angige Menge. Jeder Hirte entfernt die ihm zugeordneten Schafe. Jedes Schaf, dessen Nachfolger nicht aktiv oder ein Hirte ist, markieren wir als letztes Schaf. Da jedes Schaf genau einem Hirten zugeordnet ist und alle isolierten Knoten entfernt wurden, setzen wir die Zeiger in den Bl¨ocken weiter, in denen wir ein Schaf oder einen isolierten Knoten gefunden haben. Ein Hirte ist daher im Sinne der Suche von weiteren Knoten einer unabh¨ angigen Menge kein aktiver Knoten mehr. Wir markieren ihn daher als passiv. Um eine Liste zu verkleinern, benutzen wir den Algorithmus List-Contraction. Lemma 4 Der Algorithmus List-Contraction verkleinert eine Liste durch das Entfernen von isolierten Knoten und jeweils eines Schafs pro Hirte. Aktive Knoten werden entweder entfernt oder in Schafe oder Hirten umgewandelt, wobei jedem Schaf ein Hirte und jedem Hirten bis zu O(log log n) Schafe zugeordnet sind. Hirten und Schafe sind passiv. Nach Ablauf der Prozedur ist in jedem nicht leeren Block ein Knoten aktiv oder ein Hirte vorhanden. Die Laufzeit der Prozedur ist O(1) und die Anzahl der ben¨ otigten Operationen O(n/ log(n)). Beim ersten Aufruf gehen wir davon aus, dass alle Elemente, auf die ein Zeiger zeigt, aktiv sind.
26
Algorithm 16 List-Contraction Eingabe: Eine doppelt verkettete Liste mit Vorg¨anger- und Nachfolgerfunktion P und S Ausgabe: Die Abst¨ ande R(1), . . . , R(n) der Listenelemente vom Ende der Liste. 1: Phase 1: Entferne die Schafe, die hinter Hirten stehen 2: for 1 ≤ i ≤ n/ log(n) par do 3: if p(i) ist ein Hirte then 4: Speichere die Knoteninformation hS(p(i)), S(S(p(i))), p(i), R(S(p(i)))i lokal auf einem Stack 5: if S(p(i)) ist letztes Schaf then 6: Entferne Hirtenmarkierung von p(i) und markiere p(i) als aktiv 7: end if 8: Markiere S(p(i)) als gel¨ oscht 9: R(p(i)) = R(p(i)) + R(S(p(i))), S(p(i)) = S(S(p(i))), P (S(p(i))) = p(i) 10: end if 11: end for 12: Phase 2: Entferne isolierte Knoten 13: for 1 ≤ i ≤ n/ log(n) par do 14: if p(i) ist aktiv, S(p(i)) und P (p(i)) sind passiv then 15: Speichere die Knoteninformation hp(i), S(p(i)), P (p(i)), R(p(i))i lokal auf einem Stack 16: Markiere p(i) als gel¨ oscht 17: R(P (p(i))) = R(P (p(i))) + R(p(i)), P (S(p(i))) := P (p(i)), S(P (p(i))) := S(p(i)) 18: p(i) = p(i) + 1 19: end if 20: end for 21: Phase 3: Bestimme die neuen Schafe und Hirten 22: Wende New-Color mit der Startf¨ arbung c(p(i)) := i zweimal auf alle aktiven Knoten an 23: Bestimme die lokalen Minima dieser F¨ arbung 24: for 1 ≤ i ≤ n/ log(n) par do 25: if c(p(i)) ist lokales Minimum und P (p(i)) ist nicht der erste Knoten und p(i) ist nicht der letzte Knoten einer aktiven Folge then 26: Markiere p(i) als Hirte und passiv 27: else if p(i) ist erster Knoten in einer aktiven Folge then 28: Markiere p(i) als Hirte und passiv 29: else if p(i) ist aktiv then 30: Markiere p(i) als Schaf 31: if p(i) ist letzter Knoten in einer aktiven Folge oder S(p(i)) ist ein Hirte then 32: Markiere p(i) als letztes Schaf 33: end if 34: end if 35: if p(i) ist ein Schaf oder letztes Schaf then p(i) = p(i) + 1 end if 36: end for 37: Phase 4: Abbruchbedingung und Aktivieren von Knoten 38: for 1 ≤ i ≤ n/ log(n) par do 39: if p(i) > i log(n) then exit da Blockgrenze von Bi u ¨berschritten 40: else if p(i) ist kein Hirte then markiere p(i) als aktiv end if 41: end for Sollte p(i) bzw. S(p(i)) der erste oder letzte Knoten der Liste sein, so m¨ ussen wir die Zeilen 9 und 17 entsprechend modifizieren. Man beachte, dass jeder Knoten jedes Blocks entfernt werden kann. Es gilt: R¨ ucken wir einen Z¨ahler weiter, so wurde der letzte Knoten entweder entfernt, oder er ist als Schaf markiert und wird entfernt. Wir erg¨anzen nun noch die Menge der Hirten in den jeweiligen Ketten der aktiven Knoten um die Knoten pi , die bez¨ uglich ihrer Position innerhalb der Bl¨ocke Bi ein lokales Maximum bilden. Sei b(pi ) die Position des Knotens pi innerhalb der Blocks zu dem pi geh¨ort, dann erg¨anzen wir Phase 3 von List-Contraction, so dass aktive Knoten, die ein lokales Maximum bez¨ uglich ihrer Blockposition bilden, zu Hirten werden:
27
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
Phase 3: Bestimme die neuen Schafe und Hirten Wende New-Color mit c(p(i)) = i zweimal auf alle aktiven Knoten an for 1 ≤ i ≤ n/ log(n) par do if p(i), S(p(i)) sind aktiv und P (i) ist passiv then if b(p(i)) > b(S(pi )) then c(pi ) = −1 end if else if p(i), P (p(i)) sind aktiv und S(i) ist passiv then if b(p(i)) > b(P (pi )) then c(pi ) = −1 end if else if p(i), S(pi ) und P (pi ) sind aktiv then if b(p(i)) ≤ min{b(S(pi )), b(P (pi ))} und b(p(i)) < max{b(S(pi )), b(P (pi ))} then c(pi ) = n end if if b(p(i)) > min{b(S(pi )), b(P (pi ))} und b(p(i)) ≥ max{b(S(pi )), b(P (pi ))} then c(pi ) = −1 end if end if Bestimme die lokalen Minima der resultierenden F¨arbung if c(p(i)) ist lokales Minimum then Markiere p(i) als Hirte und passiv else if p(i) ist aktiv then Markiere p(i) als Schaf end if if p(i) ist ein Schaf then p(i) = p(i) + 1 end if end for S
S S H
H
H
Abbildung 3: Markierung Hirten versus Schafe, wobei die Bl¨ ocke als Spalten dargestellt sind. Einem Hirten ordnen wir nun sowohl Schafe auf seiner linken als auch auf seiner rechten Seite zu. Hierbei z¨ahlen die Schafe links und rechts von einem Hirten zu diesem, so lange deren Blockpositionen eine streng monoton fallende Folge bilden. Wird ein Minimum bez¨ uglich der Folge der Blockpositionen erreicht, so ordnen wir dieses Schaf dem Hirten auf dessen linker Seite zu. Beide Herden enden an dieser Stelle. Wir k¨ onnen nun wie im urspr¨ unglichen Fall die letzten Schafe der jeweiligen Hirten markieren. Hat ein Hirte das letzte Schaf seiner Herde bearbeitet, so wird der entsprechende Hirte wieder aktiv. Um dieses zu gew¨ ahrleisten, m¨ ussen wir die erste und dritte Phase noch einmal u ¨berarbeiten. Da es sich hierbei jedoch nur um einige technische Details handelt, sind diese Ausf¨ uhrungen dem Leser u ¨berlassen. Die Markierung als Hirte und Schaf wird in der Abbildung 3 illustriert. Man beachte, dass der F¨arbungsalgorithmus dazu dient, lange Ketten, die bez¨ uglich ihrer Blockposition monoton sind, bzw. große Herden aufzuteilen. Diese sind in Abbildung 3 in Boxen zusammen gefasst. Da einem Hirten jetzt sowohl Schafe auf seiner linken als auch auf seiner rechten Seite zugeordnet sind, und zudem potentielle Hirten, deren Blockposition ein lokales Minimum bilden, zu den Schafen gerechnet werden, kann sich die Herde eines Hirten vergr¨oßern. Da zwischen zwei lokalen Minima jedoch ein lokales Maximum liegen muss, ist das Wachstum der Herde durch den Faktor 4 beschr¨ankt. Die Gr¨oße der Herde ist also weiterhin in O(log log n). Ein Sonderfall muss hier noch behandelt werden: Ein Hirte hat von Beginn an keine Herde, da er sich am Rand befindet. Ein solcher Hirte kann jedoch wie ein isolierter Knoten behandelt und entsprechend Phase 2 entfernt werden. 28
Beobachtung 13 Ein Hirte besitzt nur dann von Beginn an keine Herde, wenn er am Rand einer aktiven Kette liegt, und dessen Nachbar ein lokales Minimum bez¨ uglich dessen Blockposition ist. Um zu beweisen, dass die Anzahl der Knoten, die nach O(log n) Wiederholungen der ver¨anderten Prozedur List-Contraction in der Liste verbleiben, in O(n/ log n) ist, wollen wir eine neue Beweismethode einf¨ uhren, bei der wir jedem Listenelement abh¨angig von dessen Blockposition ein Gewicht geben, und das Gesamtgewicht der kompletten Liste nach jedem Schritt analysieren. Sei q=
1 log log n
und ∀i ∈ {1, . . . , n} : wi := (1 − q)b(i) .
Zu Beginn geben wir jedem Knoten i das Gewicht wi , somit ist das Initialgewicht eines Blocks logX n −1
(1 − q)i <
i=0
1 q
n ankt. und das Gewicht der Liste durch q·log n beschr¨ Das Gewicht eines Schafes wird in der dritten Phase durch die jeweiligen Hirten festgelegt.
Lemma 5 Bei jedem Aufruf der Prozedur List-Contraction wird das Gesamtgewicht der Liste um einen Faktor von zumindest 1 − 4q reduziert. Beweis: Im Folgenden wollen wir das Gewicht der einzelnen Bl¨ocke analysieren. Hierbei z¨ahlen wir zu dem Blockgewicht nicht nur die Gewichte der verbleibenden Elemente des Blocks, sondern auch das Gewicht der Schafe, dessen Hirte zu diesem Block gez¨ahlt wird – d.h. das Gewicht dieser Schafe wird nicht dem Block zugeordnet, zu dem das Schaf geh¨ort. Es k¨onnen die folgenden F¨alle auftauchen: 1. Ein aktiver Knoten p(i) ist isoliert und wird in Phase 2 entfernt. Da in diesem Block alle Knoten, die vor diesem isolierten Knoten in der urspr¨ unglichen Liste entweder als Schafe markiert sind und daher f¨ ur einen anderen Block z¨ahlen, oder entfernt wurden, ist das Gewicht nach dieser Operation logX n −1
logX n −2
j=b(i)+1
j=b(i)
(1 − q)i = (1 − q)
log n −2 log n −1 q X q X (1 − q)i ≤ (1 − ) (1 − q)i < (1 − ) (1 − q)i . 4 4 j=b(i)
j=b(i)
2. Aktive Knoten, die in Phase 3 zu Hirten und Schafen werden. F¨ ur einen Hirten p(i1 ) sei p(i2 ), . . . , p(ik ) die dazugeh¨orige Herde. Vor der Zuordnung von Schafen und Hirten ist das Gewicht der dazugeh¨origen Bl¨ocke Bi ,Bi1 ,. . . , Bik durch Q =
k log(n)−1 X X
(1 − q)`
j=1 `=b(ij )
gegeben.10 Aus der Analyse der geometrischen Reihe folgt log(n)−1
X
(1 − q)` <
`=b(ij )
1 (1 − q)b(ij ) . q
Da b(i1 ) nicht der minimale Index in der Folge b(i1 ), . . . , b(ik ) ist, gilt k
k
1X 2X Q < (1 − q)b(ij ) ≤ (1 − q)b(ij ) q j=1 q j=2 10 Zur Erinnerung: Die Schafe in diesen Bl¨ ocken, die sich weiter vorne in den Bl¨ ocken befinden k¨ onnen, werden nicht zu diesen Bl¨ ocken gerechnet.
29
und somit k
q·Q 1X < (1 − q)b(ij ) . 4 2 j=2 Betrachten wir nun das Gewicht dieser Bl¨ocke nach diesem Schritt: Wir gewichten die Schafe so, dass die Summe der Gewichte der Schafe gegen¨ uber den Gewichten der urspr¨ unglichen aktiven Knoten genau halbiert wird. Ein Schaf an einer Position b(j) bringt somit das Gewicht (1−q)b(j) /2 in die Summe ein. Da Schafe nur das halbe Gewicht der jeweiligen aktiven Knoten haben, ist das Gewicht der Bl¨ ocke Bi ,Bi1 ,. . . , Bik nach dem Schritt gegeben durch Q−
k q 1X qQ (1 − q)b(ij ) ≤ Q − = Q 1− 2 j=2 4 4
beschr¨ ankt. Aus technischen Gr¨ unden drehen wir die Zuordnung der Gewichte zu den Schafen um, so dass die Gewichte ausgehend vom Hirten abnehmend angeordnet sind. 3. Der aktuelle Knoten ist ein Hirte und bearbeitet ein Schaf. Seien i2 , . . . , ik die dem Hirten zugeordneten Schafe, dann ist entsprechend der obigen Modifikation das Gewicht des Blocks des Hirtens p(i) Q =
logX n −1
k
(1 − q)` +
`=b(i)
1X (1 − q)b(ij ) ≤ 2 j=2
logX n −1
(1 − q)` +
`=b(i)
k (1 − q)minj∈{2,...,k} b(ij ) . 2
Durch die Anwendung des F¨ arbungsalgorithmus k¨onnen wir garantieren, dass die Gr¨oße jeder Herde durch log log n = 1q beschr¨ ankt ist. Somit gilt: Q ≤
logX n −1
(1 − q)` +
`=b(i)
1 (1 − q)minj∈{2,...,k} b(ij ) . 2q
Aus der Betrachtung der geometrischen Reihe folgt wiederum: logX n −1
(1 − q)` <
`=b(i)
1 1 (1 − q)b(i) ≤ (1 − q)minj∈{2,...,k} b(ij ) q q
und somit:
3 (1 − q)minj∈{2,...,k} b(ij ) . 2q Da wir die Gewichte der Herde so angeordnet haben, dass wir das Schaf mit dem gr¨oßten Gewicht in diesem Schritt entfernen k¨ onnen, ist das Gewicht dieses Blocks nach dieser Operation: q·Q q 1 < 1− Q. Q − (1 − q)minj∈{2,...,k} b(ij ) < Q − 2 3 4 Q <
Aus Lemma 5 k¨ onnen wir nun schließen: Lemma 6 Nach O(log n) Iterationen der Prozedur List-Contraction verbleiben noch O(n/ log(n)) Elemente in der Liste. Beweis: Wie wir bereits oben gesehen haben, ist das Startgewicht der Liste beschr¨ankt durch: n . q log n Wenden wir die Prozedur List-Contraction 6 log n mal auf diese Liste an, so ist das verbleibende Gewicht nach Lemma 5 beschr¨ ankt durch: n q · (1 − )6 log n . q log n 4 30
F¨ ur hinreichend große Werte n gilt: 1−
q 6 log n 4 q
< (1 − q)log n .
(1)
Somit ist das verbleibende Gewicht kleiner als n · (1 − q)log n . log n Das kleinste Gewicht eines Knotens ist jedoch (1 − q)log n benden Elemente in der Liste beschr¨ ankt durch 2
−1
/2 und folglich ist die Anzahl der verblei-
n (1 − q)log n n n = 2 · · (1 − q) < 2 . log n (1 − q)log n −1 log n log n
Um den Beweis von Lemma 6 abzuschließen, verbleibt uns noch, die Ungleichung 1 zu beweisen. F¨ ur hinreichend großes n gilt: 6 log n 1 − 4q 6q log n ≤ 2− 4 −log2 q q und f¨ ur q = 1/ log log n: 2−
6q log n −log2 (q) 4
≤ e− ln(2)
6−ε 4 q
log n
f¨ ur jede Konstante ε > 0. Auf der anderen Seite gilt (1 − q)log n ≥ e−q log n . Die Ungleichung 1 gilt, da ln(2)
6−ε >1 4
f¨ ur ε < 0, 2 ist. Benutzen wir nun die Prozedur List-Contraction, um eine Liste zu verkleinern, und bestimmen dann den Rang der verbleibenden Elemente mit Hilfe des Pointer-Jumping-Verfahrens, so folgt: Theorem 10 Das List-Ranking-Problem kann in Zeit O(log n) und mit Hilfe von O(n) Operationen auf einer EREW-PRAM gel¨ ost werden.
4.3
Sortierte Reihenfolge von Bl¨ attern
Gegeben sei die Adjazenzliste eines Baums. Unsere Aufgabe ist es, die Bl¨atter des Baums in einer von rechts nach links laufenden Reihenfolge auszugeben. Da der Baum mit Hilfe seiner Adjazenzlisten gegeben ist, liegt f¨ ur jeden Knoten v des Baums eine Liste L(v) = (u0 , . . . , ud−1 ) seiner Nachbarn vor. Eine Euler-Tour durch einen Baum durchl¨auft jede Kante des Baums in jede Richtung genau einmal. Dieses k¨onnen wir dadurch erreichen, dass wir jede Kante {v, u} durch zwei Knoten [v, u] und [u, v] repr¨asentieren und die Nachfolgefunktion S durch S([ui , v]) = [v, u(i+1) mod d ] definieren. Da diese Operationen alle parallel ausgef¨ uhrt werden k¨onnen, erhalten wir: Theorem 11 Sei T ein Baum vom Grad d, der durch seine Adjazenzlisten gegeben ist, dann kann eine Euler-Tour durch T in Zeit O(d) und Arbeit O(d · n) erstellt werden. Mit Hilfe des List-Ranking Algorithmus k¨onnen wir die Kanten (und somit auch die Knoten) in der Reihenfolge ausgeben, in der sie auf der Euler-Tour besucht werden. Gewichten wir die Bl¨atter des Baums mit 1 und die inneren Knoten mit 0, so k¨ onnen wir mit Hilfe des Algorithmus f¨ ur die Pr¨afix-Summe auch die Folge der Bl¨ atter in der Reihenfolge von links nach rechts ausgeben. Theorem 12 Sei T ein Bin¨ arbaum, der durch seine Adjazenzlisten gegeben ist, dann kann die Liste seiner Bl¨ atter in der Reihenfolge von links nach rechts in Zeit O(log n) und Arbeit O(n) ausgegeben werden. 31
gerichteter Baum
ungerichteter Baum
0
0
1
4
5
6
7
2
8
1
4
3
5
6
2
8
7
3
Abbildung 4: Ein gerichteter Baum und der dazugeh¨ orige ungerichtete Baum. Die Abbildungen 4 bis 6 illustrieren, wie man aus den Adjazenzlisten eines Baums eine sortierte Reihenfolge seiner Bl¨ atter generiert.
32
Adjazenzliste 0 1 5 1 0 4 6 2 3 5 8 3 2 4 1 5 0 2 7 6 1 7 5 8 2 Liste der Kanten [0,1] [0,5] [1,0] [1,4] [1,6] [2,3] [2,5] [2,8] [3,2] [4,1] [5,0] [5,2] [5,7] [6,1] [7,5] [8,2] Euler-Tour u ¨ber S [0,1] [0,5] [1,0] [1,4] [1,6] [2,3] [2,5] [2,8] [3,2] [4,1] [5,0] [5,2] [5,7] [6,1] [7,5] [8,2]
List-Ranking auf Euler-Tour 0
10
11
15
13
6
4
8
5
14
1
9
3
12
2
7
[0,1] [0,5] [1,0] [1,4] [1,6] [2,3] [2,5] [2,8] [3,2] [4,1] [5,0] [5,2] [5,7] [6,1] [7,5] [8,2]
0
Sortierte Euler-Tour 0
1
2
3
4
5
6
7
8
10
9
11
12
13
14
15
[0,1] [5,0] [7,5] [5,7] [2,5] [3,2] [2,3] [8,2] [2,8] [5,2] [0,5] [1,0] [6,1] [1,6] [4,1] [1,4]
ungerichteter Baum mit Euler-Tour 0 1
4
5
6
2
8
7
3
Abbildung 5: Das Generieren einer sortierten Euler-Tour.
33
Sortierte Euler-Tour mit Gewichten: Ist in [u, v] der Knoten u ein Blatt, dann w[u,v] = 1, sonst 0. 0
0
1
0
0
1
0
1
0
0
0
0
1
0
1
0
[0,1] [5,0] [7,5] [5,7] [2,5] [3,2] [2,3] [8,2] [2,8] [5,2] [0,5] [1,0] [6,1] [1,6] [4,1] [1,4] Pr¨ afix-Summe der Gewichten 0
0
1
1
1
2
2
3
3
3
3
3
4
4
5
5
[0,1] [5,0] [7,5] [5,7] [2,5] [3,2] [2,3] [8,2] [2,8] [5,2] [0,5] [1,0] [6,1] [1,6] [4,1] [1,4] Ausgabe der Bl¨ atter an der Position der Pr¨ afix-Summe 7 3 8 6 4
Abbildung 6: Das Generieren einer sortierten Bl¨ atterfolge aus einer sortierten Euler-Tour.
4.4
Baumkontraktion
Unser Ziel bei der Baumkontraktion ist das Finden eines Verfahrens zur schnellen parallelen Auswertung von arithmetischen Ausdr¨ ucken. Hierbei gehen wir davon aus, dass jedes Blatt mit einer Konstanten gewichtet ist, und jeder interne Knoten eine arithmetische Operation repr¨asentiert. Der Baum ist hierbei durch eine Vorg¨ angerfunktion p gegeben, d.h. f¨ ur alle Knoten v bis auf den Wurzelknoten existiert genau ein Vorg¨angerknoten, der durch p(v) gegeben ist. F¨ ur einen Bin¨arbaum T mit einer Wurzel r und einem Knoten v 6= r definiere g(v) als den Geschwisterknoten von v, d.h. p(v) = p(g(v)). Im Zentrum unseres Verfahrens f¨ ur die Baumkontraktion ist die rake-Operation, die ein gegebenes Blatt v mit p(v) 6= r und den Knoten p(v) aus dem Baum l¨oscht, und an Stelle von p(v) den Knoten g(v) als Nachfolger von p(p(v)) einsetzt. Dieses geschieht mit Hilfe des folgenden Algorithmus: Algorithm 17 Tree Contraction Eingabe: ein Bin¨ arbaum T mit Hilfe der Funktionen p und g Ausgabe: der zusammengezogene Bin¨ arbaum von T , der aus drei Knoten besteht 1: Speichere die Bl¨ atter von T in der Ordnung von links nach rechts im Array A 2: Entferne das am weitesten links und das am weitesten rechts stehende Blatt aus A 3: for i = 1 to dlog(n + 1)e do 4: Sei Aeven die Folge der Bl¨ atter von A, die an geraden Positionen stehen 5: Sei Aodd die Folge der Bl¨ atter von A, die an ungeraden Positionen stehen 6: for alle v in Aodd , die linke Bl¨ atter sind par do 7: F¨ uhre die rake-Operation auf v aus 8: end for 9: for alle verbleibenden v in Aodd par do 10: F¨ uhre die rake-Operation auf v aus 11: end for 12: Setze A = Aeven 13: end for Theorem 13 Algorithmus 17 kontrahiert einen Baum korrekt in O(log n) Schritten und mit Arbeit O(n). Beweis: Zun¨ achst m¨ ussen wir untersuchen, ob es bei der Ausf¨ uhrung von Algorithmus 17 zu einer parallelen Kontraktion von zwei Bl¨ attern kommt, deren Vorfahren miteinander verbunden sind. Da wir jedoch nur die linken und sp¨ ater die rechten Vorfahren aus Aodd parallel kontrahieren, ist dieses nicht der Fall. Das Verfahren arbeitet also korrekt. Ferner reduzieren wir in jedem Durchlauf der ¨außeren for-Schleife die Bl¨ atter in A von mi auf mi+1 = bmi /2c. Somit gen¨ ugen dlog(n + 1)e Durchl¨aufe, um alle Bl¨atter aus A zu l¨ oschen. Die dabei verrichtete Arbeit ist dlog(n+1)e
W (n) = O(
X
dlog(n+1)e
mi ) = O(
i=1
X i=1
34
n/2i ) = O(n) .
Um einen arithmetischen Ausdruck u ¨ber die Operationen {+, ·} auszuwerten, gewichten wir jeden Knoten v mit einem Paar (av , bv ). Diese Werte dienen dazu, Zwischenergebnisse abzuspeichern: Sei val(v) der Wert des Teilbaums mit der Wurzel v, dann ergibt sich der Wert eines Knoten u mit zwei Vorg¨angern v, w und der zu u geh¨ origen Operation ◦ ∈ {+, ·} durch: val(u) = (av · val(v) + bv ) ◦ (aw · val(w) + bw ) . Die Werte (av , bv ) initialisieren wir mit (1, 0). Vor jeder Ausf¨ uhrung der rake-Operation auf einen Knoten v im Algorithmus 17 modifizieren wir zun¨ achst die Parameter (aw , bw ) von w = g(v): 1. Ist p(v) = u ein Multiplikationsknoten, so gilt au · (av · val(v) + bv ) · (aw · val(w) + bw ) + bu
=
(au · (av · val(v) + bv ) · aw ) · val(w) + (au · (av · val(v) + bv ) · bw + bu ) .
Wir setzen daher aw bw
= au · (av · val(v) + bv ) · aw = au · (av · val(v) + bv ) · bw + bu .
2. Ist p(v) = u ein Additionsknoten, , so gilt au · ((av · val(v) + bv ) + (aw · val(w) + bw )) + bu
= au · aw · val(w) + au · (av · val(v) + bv + bw ) + bu .
Wir setzen daher aw bw
= au · aw = au · (av · val(v) + bv + bw ) + bu .
Man beachte, dass die Werte von aw und bw ohne weitere Probleme berechnet werden k¨onnen. Durch diese Operationen gehen die Zwischenergebnisse der entfernten Bl¨atter nicht verloren, vielmehr gilt, dass sich der Wert des arithmetischen Ausdrucks durch die so modifizierte rake-Operation nicht ¨andert. Es gilt somit: Theorem 14 Ein arithmetischer Ausdruck kann in O(log n) Schritten und mit Arbeit O(n) ausgewertet werden.
5
Verschmelzen und Sortieren
In diesem abschließenden Kapitel u ¨ber PRAM Algorithmen werden wir uns mit einem der am besten untersuchten Probleme f¨ ur parallele Algorithmen besch¨aftigen, dem Sortieren. Ein Großteil der parallelen Sortierverfahren beruht auf einem Divide-and-Conquer Ansatz: 1. Teile die unsortierte Folge der Eingabewerte A(0), . . . , A(n − 1) in zwei (m¨oglichst) gleichgroße Teilfolgen A0 , A1 . 2. Sortiere A0 und A1 parallel. Sei B0 die sortierte Folge zu A0 und B1 die sortierte Folge zu A1 . 3. Verschmelze (d.h. Merge) die Folgen B0 und B1 zur sortierten Folge B. Mit Hilfe von Theorem 5 erhalten wir unmittelbar: Theorem 15 Sei A eine unsortierte Liste der L¨ ange n, dann k¨ onnen wir diese Liste in Zeit O(log2 (n)) bei Arbeit O(n · log(n)) parallel sortieren. Der in Theorem 5 untersuchte Merge-Algorithmus ist jedoch recht aufw¨andig. Daher werden wir nun einige weitere Merge-Algorithmen vorstellen, die entweder um einiges einfacher oder schneller sind. Wir beginnen mit dem bitonischen Merge-Algorithmus. 35
5.1
Bitonischer Merge-Algorithmus
Das bitonische Merge-Verfahren basiert auf dem Vergleiche-und-Vertausche-Prinzip, d.h. wir vergleichen zwei fest durch ihre Position bestimmte Elemente der zu verschmelzenden Folgen und vertauschen diese abh¨angig von dem Ausgang des Vergleichs. Ein solches Verfahren kann vollst¨andig aus Vergleichsmodulen compare(x, y) = (min{x, y}, max{x, y}) . aufgebaut werden, d.h. compare(x, y) ist die einzige erlaubte datenabh¨angige Operation. Ein weit verbreitetes Prinzip, welches einerseits zur Verifikation und andererseits zur Konstruktion von auf dem Vergleiche-und-Vertausche-Prinzip basierenden Sortierverfahren eingesetzt werden kann, ist das 0-1Prinzip. Theorem 16 (0-1-Prinzip) Ein Verfahren, welches auf dem Vergleiche-und-Vertausche-Prinzip basiert, sortiert genau dann jede Folge von n Werten korrekt, wenn es jede 0-1 Eingabesequenz korrekt sortiert. Beweis: Sei f : N → N eine monotone wachsende Funktion, das heißt, dass f¨ ur alle Eingaben A(i) und A(j) gilt: A(i) ≤ A(j) =⇒ f (A(i)) ≤ f (A(j)) . F¨ ur ein Vergleichsmodul gilt: compare(f (A(i)), f (A(j)))
= (min{f (A(i)), f (A(j))}, max{f (A(i)), f (A(j))}) = (f (min{A(i), A(j)}), f (max{A(i), A(j)})) .
Wir k¨onnen bei einem Vergleichsmodul eine monotone Transformation der Eingabe direkt auf die Ausgabe des Moduls anwenden. F¨ uhren wir diesen Schritt Modul f¨ ur Modul bei einem Vergleiche-undVertausche-Algorithmus aus, beobachten wir: Sei B(0), . . . , B(n − 1) die Ausgabe eines Vergleiche-und-Vertausche-Algorithmus G auf Eingabe A(0), . . . , A(n − 1), dann generiert G auf die Eingabe von f (A(0)), . . . , f (A(n − 1)) die Ausgabe f (B(0)), . . . , f (B(n − 1)). Wir definieren nun eine Folge von monotonen Funktionen f1 , . . . , fn : N → {0, 1} u ¨ber: 0 wenn xj < xi fi (A(j)) := 1 sonst. Der Beweis der Korrektheit des 0-1-Prinzips erfolgt nun u ¨ber eine Widerspruchsannahme. Sei A(0), . . . , A(n − 1) eine Eingabefolge, f¨ ur die die Ausgabe A(π(0)), . . . , A(π(n − 1)) von G nicht korrekt sortiert ist. Es existiert somit ein i < n mit A(π(i)) > A(π(i + 1)). Aus der oben gemachten Beobachtung folgt, dass G auf die Eingabe von fπ(i) (A(0)), . . . , fπ(i) (A(n − 1)) die Ausgabefolge fπ(i) (A(π(0))), . . . , fπ(i) (A(π(n−1))) generiert. Aus der Definition von fπ(i) folgt, dass fπ(i) (A(π(i))) = 1 und fπ(i) (A(π(i+1))) = 0. G generiert somit auf die Eingabe der 0-1-Folge fπ(i) (A(0)), . . . , fπ(i) (A(n−1)) keine korrekt sortierte Ausgabefolge. Dieses ist ein Widerspruch zur Annahme, dass G jede 0-1 Eingabesequenz korrekt sortiert. Wir wollen uns der Analyse bitonischer Folgen zuwenden: Definition 6 Eine Folge A(0), A(1), . . . , A(n − 1) nennen wir bitonisch, wenn f¨ ur ein ein i ≤ n gilt: • f¨ ur alle 2 ≤ j ≤ i A(j − 1) ≤ A(j) und f¨ ur alle i < j ≤ n A(j − 1) ≥ A(j) oder • f¨ ur alle 2 ≤ j ≤ i A(j − 1) ≥ A(j) und f¨ ur alle i < j ≤ m A(j − 1) ≤ A(j). Sind nun A0 = A0 (0), . . . , A0 (n − 1) und A1 = A1 (0), . . . , A1 (n − 1) zwei sortierte 0-1-Folgen, so ist die Folge A
:=
A0 , rev(A1 )
=
A0 (0), . . . , A0 (n − 1), A1 (n − 1), . . . , A1 (0)
36
∈
{ 0i 1j 02m−i−j | i, j ∈ N }
mit rev(A1 (0), . . . , A1 (n − 1))
:=
A1 (n − 1), . . . , A1 (0)
bitonisch. Setzt sich eine Folge A aus zwei Teilfolgen A0 und A1 zusammen, so gilt: rev(A) = rev(A0 A1 ) = rev(A1 )rev(A0 ) . F¨ ur eine bitonische 0-1-Folge A(0), . . . , A(2n − 1) k¨onnen wir die folgende Eigenschaft herleiten: Lemma 7 Sei A = A(0), A(1), . . . , A(2n − 1) eine bitonische 0-1-Folge, dann sind A1 := min{A(0), A(n)}, . . . , min{A(n − 1), A(2n − 1)} und A2 := max{A(0), A(n)}, . . . , max{A(n − 1), A(2n − 1)} ebenfalls bitonisch, und es gilt: Entweder ist A1 eine 0-Folge oder A2 eine 1-Folge. Beweis: Im Folgenden stellen wir die Folgen A, A1 , A2 als bin¨are Zeichenketten dar. Sei A = 0a 1b 0c mit a + b + c = 2n, dann unterscheiden wir die folgenden zwei F¨alle: 1. Ist a + c ≥ m, so hat bei jedem Vergleich zumindest einer der Werte A(i) und A(n + i) den Wert 0. Folglich ist A0 eine 0-Folge. Bei der Bestimmung der Folge A1 betrachten wir drei Unterf¨alle: (a) a ≥ m: = max{A(0), A(n)}, . . . , max{A(n − 1), A(2n − 1)} = max{0, 0}, . . . , max{0, 0} max{0, 1}, . . . , max{0, 1} max{0, 0}, . . . , max{0, 0} {z } | {z } | {z } |
A1
a−m
=
c
b
0a−m 1b 0c
(b) c ≥ m: A1
= max{A(0), A(n)}, . . . , max{A(n − 1), A(2n − 1)} = max{0, 0}, . . . , max{0, 0} max{1, 0}, . . . , max{1, 0} max{0, 0}, . . . , max{0, 0} {z } | {z } | {z } | a
=
c−m
b
a b c−m
0 1 0
(c) a, c < m und a + c ≥ m: A1
= max{A(0), A(n)}, . . . , max{A(n − 1), A(2n − 1)} = max{0, 1}, . . . , max{0, 1} max{0, 0}, . . . , max{0, 0} max{1, 0}, . . . , max{1, 0} | {z } | {z } | {z } a+b−m
=
m−b
b+c−m
1a+b−m 0m−b 1b+c−m .
2. Ist a + c < m, so ist bei jedem Vergleich zumindest einer der Werte A(i) und A(n + i) gleich 1. Folglich ist A1 eine 1-Folge. F¨ ur die Folge A0 gilt: A0
= min{A(0), A(n)}, . . . , min{A(n − 1), A(2n − 1)} = min{0, 1}, . . . , min{0, 1} min{1, 1}, . . . , min{1, 0} min{1, 0}, . . . , min{1, 0} | {z } | {z } | {z } =
a a m−a−c c
0 1
m−a−c
c
0 .
Die Analyse von A = 1a 0b 1c mit a + b + c = 2n erfolgt analog. Fassen wir Theorem 16 und Lemma 7 zusammen, so erhalten wir den folgenden Merge-Algorithmus: Lemma 8 Algorithmus 18 sortiert eine bitonische Folge in Zeit O(log n) und Arbeit O(n). 37
Algorithm 18 Bitonic Merge Eingabe: eine bitonische Sequenz A = A(0), . . . , A(2n − 1) Ausgabe: sortierte Sequenzen B 1: for i = 0 to n − 1 par do 2: Setze A0 (i) = min{A(i), A(n + i)} 3: Setze A1 (i) = max{A(i), A(n + i)} 4: end for 5: Berechne parallel B0 =Bitonic Merge(A0 ) und B1 =Bitonic Merge(A1 ) 6: Gebe die Sequenz B = B0 B1 zur¨ uck
Durch iteriertes Anwenden der Bitonic Merge-Operation erhalten wir folgendes rekursiv definiertes Sortierverfahren: Algorithm 19 Bitonic Sort Eingabe: eine unsortierte Sequenz A = A(0), . . . , A(2n − 1) Ausgabe: sortierte Sequenzen B 1: for i = 0 to n − 1 par do 2: Setze A0 (i) = A(i) 3: Setze A1 (i) = A(n + i) 4: end for 5: Berechne parallel B0 =Bitonic Sort(A0 ) und B1 =Bitonic Sort(A1 ) 6: for i = 0 to n − 1 par do 7: Setze A(i) = B0 (i) 8: Setze A(2n − 1 − i) = B1 (i) 9: end for 10: Berechne B =Bitonic Merge(A) 11: Gebe die Sequenz B = B0 B1 zur¨ uck Theorem 17 Algorithmus 19 sortiert eine unsortierte Folge in Zeit O(log2 n) und Arbeit O(n log n). Betrachten wir den Kontrollfluss in den letzten beiden Algorithmen, so k¨onnen wir folgende Regelm¨aßigkeit feststellen: Der Kontrollfluss in Algorithmus 18 entspricht einem Butterfly-Netzwerk. Sortieren wir B0 aufsteigend und B1 absteigend, so entspricht der Kontrollfluss in Algorithmus 19 einer Sequenz von log n hintereinander geschalteter Butterfly-Netzwerken. Ein Butterfly-Netzwerk ist wie folgt definiert: Definition 7 Ein Butterfly-Netzwerk der Tiefe k besteht aus k +1 Ebenen V0 , V1 , . . . Vk zu je 2k Knoten: Vi
:= { Pw,i | w ∈ {0, 1}k } .
Die Ebenen sind mit Hilfe zweier Kantentypen E1,i und E2,i miteinander verbunden E1,i E2,i
:= { (Pw,i , Pw,i+1 ), (Pw,i+1 , Pw,i ) | w ∈ {0, 1}k } := { (Pw,i , Pu,i+1 ), (Pw,i+1 , Pu,i ) | w ∈ {0, 1}k und u = w[1 . . . k − i − 1]w[k − i]w[k − i + 1 . . . k]}
f¨ ur i ∈ {0, . . . , k − 1}. Den zweiten Kantentyp E2,i nennen wir auch Kreuzungskanten. Der große Vorteil eines solchen Verfahrens ist, dass wir es direkt in Hardware gießen k¨onnen. Wir erhalten ein bitonisches Sortiernetzwerk.
38
Ebene: 0
1
0
1
2
1
0
1
2
3
2
1
0
7
7
3
3
3
3
3
3
3
3
3
2
1
3
3
7
7
7
6
6
6
6
6
4
1
2
8
8
6
8
8
8
7
7
7
7
2
3
3
6
6
8
6
6
7
8
8
8
8
1
4
4
4
4
1
1
1
1
1
2
5
5
5
5
5
1
1
4
4
4
2
2
1
4
4
6
6
6
5
5
2
5
5
5
4
5
2
2
7
7
7
2
2
5
2
2
4
5
4
1
1
8
8
8
000 001 010 011 100 101 110 111
A
B
C
A
B
C
A
B
Abbildung 7: Der Kontrollfluss des Bitonic Sort Algorithmus hat die Form von log n hintereinander geschalteter Butterfly-Netzwerke, die in mehreren Phasen arbeiten: Phase A schiebt Werte ans Ende eines (Teil)-Butterflys, Phase B gibt den Kontrollfluss Bitonic Merge Verfahrens wieder, und Phase C dreht jede zweite Teilfolge um.
5.2
Cole’s Merge-Sort Algorithmus
Wir wollen uns nun einem optimalen O(log n) zeitbeschr¨ankten Sortieralgorithmus zuwenden. Dieses Verfahren basiert auf einer Pipelining Strategie, in der wir die sortierte Liste Schritt f¨ ur Schritt approximieren. Zun¨ achst ein paar Notationen: Sei T ein Bin¨ arbaum, dessen Bl¨ atter (von links nach rechts gesehen) die unsortierte Liste A beinhalten, d.h. ist u das i’te Blatt von links, so speichern wir in u den Wert A(i). F¨ ur jeden Knoten v von T sei L[v] die sortierte Liste der Bl¨ atter des Unterbaums von T mit der Wurzel v. Wir wollen im Folgenden das Problem betrachten f¨ ur alle Knoten v die Liste L[v] zu bestimmen. 1, 2, 3, 5, 8, 9, 10, 11 2, 5, 10
1, 3, 8, 9, 11
5, 10
3, 9, 11 2
10
1, 8
9, 11
5
3 11
1
8
9
Abbildung 8: Ein Baum mit den sortierten Listen L[v] der Konten v. Alle bisher betrachteten Sortieralgorithmen basieren auf einer Divide-and-Conquer Strategie, wobei f¨ ur jeden Knoten startend von den Bl¨ attern die Listen L[v] bestimmt wurden. Hierbei wurde von jedem Knoten v die komplette Liste L[v] berechnet, bevor mit der Liste des Vorg¨angerknotens begonnen wurde. Die pipelined Divide-and-Conquer Strategie, welche wir im Folgenden untersuchen wollen, berechnet Approximationen dieser Liste, sogenannte Samples, und verbessert diese sukzessive bis wir schließlich die kompletten Listen L[v] erhalten. Hierbei arbeitet das Verfahren in Phasen, wobei in Phase s das Sample Ls [v] bestimmt wird. In Phase s + 1 verbessern wir Ls [v] zu Ls+1 [v] und senden gleichzeitig Ls [v] zum Vorg¨ angerknoten u von v, wo wir basierend auf Ls [v] das Sample Ls+1 [u] bestimmen k¨onnen.
39
F¨ ur einen Baum T und einen Knoten v definieren wir level(v) = L¨ ange des Pfads von v zur Wurzel hight(T ) = maximale L¨ange eines Blatt-Wurzel Pfads in T alt(v) = hight(T ) − level(v) . Sei L = L(1), . . . , L(n) eine Liste, dann definiere das c-sample als samplec (L) = L(c), L(2c), . . . . Wir nennen einen Knoten v aktiv in der Phase s, wenn alt(v) ≤ s ≤ 3alt(v). In den Phasen, in denen ein Knoten v aktiv ist, verbessert er seine Approximation von L[v] sukzessive, so dass in den Phasen s ≥ 3alt(v) die Liste Ls [v] voll ist, d.h. Ls [v] = L[v]. In jeder Phase s, in der ein Knoten v aktiv ist, greift dieser auf die Listen seiner Nachfolger u zu. Jedoch benutzen wir zur Bestimmung der Liste Ls+1 [v] nicht die ganze Liste Ls [u] sondern nur einen ausgew¨ahlten Teil. Wir definieren: ur s ≤ 3 · alt(u), sample4 (Ls [u]) f¨ sample2 (Ls [u]) f¨ ur s = 3 · alt(u) + 1, Sample(Ls [u]) = Ls [u] f¨ ur s = 3 · alt(u) + 2. Der Sortieralgorithmus f¨ ur eine Phase s + 1 ergibt sich nun wie folgt: Algorithm 20 Pipelined Merge Sort Eingabe: eine Phasennummer s und ein Bin¨arbaum T , dessen Knoten v die sortierten Listen Ls [v] speichern Ausgabe: die sortierten Listen Ls+1 [v] 1: for alle aktiven Knoten v par do 2: Seien u, w die beiden Nachfolger von v 3: Setze L0s+1 [u] =Sample(Ls [u]) 4: Setze L0s+1 [w] =Sample(Ls [w]) 5: Merge die Listen L0s+1 [u] und L0s+1 [w] zu Ls+1 [v] 6: end for Die Korrektheit dieses Verfahrens ist einfach nachzuvollziehen. Viel aufwendiger gestaltet sich die Analyse der ben¨ otigten Zeit und Arbeit. Daher wollen wir uns nun auf diese konzentrieren. Lemma 9 F¨ ur jeden Knoten v und s ≥ 1 gilt |Ls+1 [v]| ≤ 2|Ls [v]| + 4. Lemma 10 F¨ ur alle s ≥ 1 ist die Gesamtl¨ ange ns der Listen Ls [v] aktiver Knoten v durch O(n) beschr¨ ankt, wobei n die Anzahl der Bl¨ atter des Baums T ist. Beweis: Es gilt ns =
X
|Ls [v]| =
X
|Ls [v]| .
bs/3c≤alt(v)≤s
v ist aktiv
P Da f¨ ur alle k die Listen Ls [v] der Knoten v mit alt(v) = k disjunkt sind, gilt bs/3c=alt(v) |Ls [v]| ≤ n. P Aus Lemma 9 folgt bs/3c+i=alt(v) |Ls [v]| ≤ n/2i . Somit gilt ns ∈ O(n). Um den Merge-Schritt zu implementieren, benutzen wir einen Merge-Algorithmus, der auf einer ¨ sogenannten Uberdeckung basiert. Eine sortierte Sequenz X = (X(1), . . . , X(m)) nennen wir c¨ Uberdeckung einer sortierten Sequenz A, wenn f¨ ur jedes Paar von aufeinander folgenden Elementen X(i), X(i + 1) der Sequenz X∞ = (−∞, X(1), . . . , X(m), ∞) maximal c Elemente A(j) in A gibt, mit X(i) < A(j) ≤ X(i + 1). ¨ Lemma 11 Seien A0 , A1 zwei sortierte Listen der L¨ ange n und X eine c-Uberdeckung von A0 und A1 f¨ ur eine Konstante c. Sind rank(X : A0 ) und rank(X : A1 ) bekannt, dann k¨ onnen die Folgen rank(A0 : A1 ) und rank(A1 : A0 ) in Zeit O(1) und Arbeit O(|X|) berechnet werden.
40
Beweis: Sei X = (X(1), . . . , X(m)), rank(X : A0 ) = (r1 , . . . , rm ) und rank(X : A1 ) = (s1 , . . . , sm ). F¨ ur jedes i ∈ N mit 1 ≤ i ≤ m + 1 definieren wir A0,i = (A0 (ri−1 + 1), . . . , A0 (ri )) und A1,i = (A1 (ri−1 + 1), . . . , A1 (ri )), wobei wir r0 = t0 = 0 und rm+1 = tm+1 = n. Sei a ∈ A0,i , dann gilt A1 (si−1 ) ≤ X(i − 1) < A0 (ri−1 + 1) ≤ a ≤ A0 (ri ) ≤ X(i) < A1 (si + 1) und somit rank(a : A1 ) = si−1 + rank(a : A1,i ). Da A1,i maximal c Elemente beinhaltet, k¨onnen wir rank(a : A1,i ) in Zeit O(1) und Arbeit O(1) bestimmen. ¨ Der Zusammenhang zwischen einer c-Uberdeckung und Algorithmus 20 folgt aus folgender Beobachtung: ¨ Lemma 12 F¨ ur jeden Knoten v in T und s ≥ 1 gilt: L0s [v] ist eine 4-Uberdeckung von L0s+1 [v]. An Stelle von diesem Lemma wollen wir ein etwas allgemeineres Lemma beweisen. Wie definieren hierf¨ ur: Sein a, b ∈ L0s [v]∞ mit a ≤ b, dann sagen wir, dass das Intervall [a, b] die Folge L0s [v]∞ = (−∞, L0s [v], ∞) in k Elementen schneidet, wenn es genau k Elemente x ∈ L0s [v]∞ gibt, so dass a ≤ x ≤ b ist. Lemma 13 Schneidet [a, b] L0s [v]∞ in k Elementen, so schneidet [a, b] L0s+1 [v] in maximal 2k Elementen. Beweis: Der Beweis erfolgt u ur s = 1 die Mengen L0s [v] und ¨ber eine vollst¨andige Induktion u ¨ber s. Da f¨ 0 0 Ls+1 [v] leer sind, schneidet das Intervall [a, b] f¨ ur jedes Paar a, b ∈ Ls [v]∞ die Sequenzen L0s [v]∞ und 0 Ls+1 [v] in maximal 2 Elementen. Wir nehmen nun an, dass f¨ ur alle t < s die Aussage des Lemmas korrekt ist, d.h. wenn f¨ ur ein Paar a0 , b0 ∈ L0t [v]∞ das Intervall [a0 , b0 ] L0t [v]∞ in h Elementen schneidet, dann schneidet [a0 , b0 ] L0t+1 [v] in 2h Elementen. ur s ≥ Sei [a, b] ein Intervall mit a, b ∈ L0s [v]∞ , so dass [a, b] L0s [v]∞ in k Elementen schneidet. F¨ 3 · alt(v) + 1 gilt Ls−1 [v] = Ls [v] = L[v]. Wir unterscheiden die folgenden zwei F¨alle: • F¨ ur s ≥ 3 · alt(v) + 2 gilt L0s [v] = sample2 (L[v]) oder L0s [v] = L[v], wohin gegen L0s+1 [v] = L[v] ist. • F¨ ur s ≥ 3 · alt(v) + 1 gilt L0s [v] = sample4 (L[v]), wohin gegen L0s+1 [v] = sample2 (L[v]) ist. In allen drei F¨ allen liegt zwischen zwei aufeinander folgenden Elementen aus L0s [v] maximal ein Element 0 aus Ls+1 [v]. Die Aussage des Lemmas folgt unmittelbar. Wir betrachten nun den Fall, dass s ≤ 3 · alt(v) ist. Da f¨ ur ein solches s L0s [v] = sample4 (Ls−1 [v]) ist, 0 schneidet f¨ ur jedes Intervall [a, b] mit a, b ∈ Ls [v]∞ die Folge Ls−1 [v]∞ in 4k − 3 Elementen. Seien u, w die Nachfolger von v im Baum T , dann entsteht Ls−1 [v] aus der Verschmelzung von L0s−1 [u] und L0s−1 [w]. Folglich ist jedes Element aus dem Schnitt von [a, b] und Ls−1 [v] entweder ein Element aus L0s−1 [u] oder aus L0s−1 [w]. Seien [au , bu ] das kleinste Intervall mit au , bu ∈ L0s−1 [u]∞ und [aw , bw ] das kleinste Intervall mit aw , bw ∈ L0s−1 [w]∞ welche [a, b] beinhalten. Ferner seien pu die Anzahl der Elemente, in denen sich [au , bu ] und L0s−1 [u]∞ schneiden, und pw die Anzahl der Elemente, in denen sich [aw , bw ] und L0s−1 [w]∞ schneiden. Sind die Elemente aus L[v] paarweise unterschiedlich – wovon wir ausgehen –, dann ist pu + pw = 4k − 3 + 2 = 4k − 1. Die zus¨ atzlichen zwei Elemente sich zwei Randelemente aus {au , bu , aw , bw } der neuen Intervalle. Aus der Bedingung, dass die neuen Intervalle das alte Intervall beinhalten folgt: |{x ∈ {au , aw , bu , bw }|x 6∈ [a, b]}| = 2 . Aus der Induktionshypothese folgt, dass [au , bu ] L0s [u] in maximal 2pu Elementen schneidet, und dass [aw , bw ] L0s [w] in maximal 2pw Elementen schneidet. Da Ls [v] sich durch Verschmelzen der Listen L0s [u] und L0s [w] ergibt, schneiden sich [a, b] und Ls [v] in maximal 2 · (pu + pw ) = 8k − 2 Elementen. Da L0s+1 [v] = sample4 (Ls [v]), werden von diesen Elementen aus Ls [v] maximal 2k Elemente in L0s+1 [v] u ¨bernommen. Womit das Lemma bewiesen ist. Der Beweis f¨ ur Lemma 12 folgt aus der Beobachtung, dass f¨ ur jedes Paar a, b von aufeinander folgenden Elementen in L0s [v] gilt, dass [a, b] L0s [v]∞ in 2 Elementen schneidet. Somit schneidet [a, b] L0s+1 [v]∞ ¨ in maximal 4 Elementen und ist daher eine 4-Uberdeckung. Analog zu Lemma 11 k¨ onnen wir zeigen: 41
Lemma 14 Sei s ≥ 2 und v ein interner Knoten von T mit Nachfolgerknoten u und w. Gegeben sind rank(L0s [v] : L0s+1 [v]),
rank(L0s [u] : L0s [w]),
und
rank(L0s [w] : L0s [u]).
Dann k¨ onnen rank(L0s+1 [v] : L0s+2 [v]),
rank(L0s+1 [u] : L0s+1 [w]),
und
rank(L0s+1 [w] : L0s+1 [u])
und rank(Ls [v] : L0s+1 [u])
und
rank(Ls [v] : L0s+1 [w]).
in Zeit O(1) und Arbeit O(|L0s+1 [u]| + |L0s+1 [w]|) berechnet werden. F¨ ur gegeben Listen rank(L0s [u] : L0s+1 [u]), rank(L0s [w] : L0s+1 [w]), rank(L0s [u] : L0s [w]) und rank(L0s [w] : k¨onnen wir den Merge-Schritt aus Algorithmus 20 wie folgt realisieren:
L0s [u])
1. Berechne rank(Ls [v] : L0s+1 [u]) und rank(Ls [v] : L0s+1 [w]). ¨ 2. Verschmelze L0s+1 [u] und L0s+1 [w] zu Ls+1 [v] unter Benutzung von Ls [v] als 4-Uberdeckung. 3. Berechne rank(L0s+1 [u] : L0s+2 [u]), rank(L0s+1 [w] : L0s+2 [w]), rank(L0s+1 [u] : L0s+1 [w]) und rank(L0s+1 [w] : L0s+1 [u]). Um die Elemente der Bl¨ atter zu sortieren, m¨ ussen wir f¨ ur einen Baum der H¨ohe h 3h Phasen des Algorithmus 20 durchlaufen. Jede Phase ben¨otigt konstante Zeit und nach Lemma 10 O(n) Operationen. Lemma 15 Sei T ein Bin¨ arbaum der H¨ ohe h mit n Bl¨ attern, dann generiert Algorithmus 20 zu jedem Knoten v des Baums die sortierte Liste aller Elemente der Bl¨ atter im Unterbaum mit der Wurzel v in Zeit O(h) und Arbeit O(n · h). Ist T ein Bin¨ arbaum der Tiefe O(log n), so erhalten wir: Theorem 18 Eine Liste der L¨ ange n kann mit Hilfe von Algorithmus 20 parallel in Zeit O(log n) und Arbeit O(n log n) sortiert werden.
6
Boolesche Schaltkreise
Neben der PRAM und den Netzwerken stellen die Schaltkreise eines der wichtigsten Modelle zur Beschreibung paralleler Algorithmen dar. In dem nun folgenden Abschnitt wollen wir uns daher intensiv mit diesem Modell besch¨ aftigen.
6.1
Das Schaltkreismodell
Definition 8 Ein Boolescher Schaltkreis C mit n Eingaben x1 , . . . , xn und m Ausgaben y1 , . . . , ym ist ein endlicher gerichteter azyklischer Graph mit m Senken. Jeder Knoten v im Graph ist mit einer Funktion σ markiert, hierbei gilt: 1. Ist der Eingrad von v gleich 0, so ist σ(v) ∈ {x1 , . . . , xn , 0, 1}. 2. Ist der Ausgrad von v gleich 0, so ist σ(v) ∈ {y1 , . . . , ym }. Der Einfachheit halber nehmen wir an, dass alle Senken einen Eingrad von 1 haben. +
3. Ist der Eingrad d+ (v) ≥ 1 und der Ausgrad d− (v) ≥ 1, so ist σ(v) eine Funktion von {0, 1}d nach {0, 1}.
(v)
Die Knoten nennen wir auch Gatter. Der Einfachheit halber identifizieren wir die Eingabe- und Ausgabegatter v mit den entsprechenden Werten σ(v). ¨ Uber die Funktion σ(v) k¨ onnen wir bei einer gegebenen Eingabe x jedem Gatter C einen Wert valv (x) zuordnen. Es gilt:
42
1. Ist σ(v) ∈ {x1 , . . . , xn , 0, 1}, so weisen wir valv (x) den entsprechenden Wert aus x bzw. den Wert der Konstante aus {0, 1} zu. 2. Ist σ(v) ∈ {y1 , . . . , ym }, so weisen wir valv (x) den Wert des Vorg¨ angergatters zu. 3. In den verbleibenden F¨ allen berechnen wir valv (x), indem wir die Funktion σ(v) auf Eingabe der Werte der Vorg¨ angerknoten auswerten. Die durch den Schaltkreis C berechnete Funktion fC ergibt sich durch fC (x) = valC (x) = (valy1 (x), valy2 (x), . . . , valym (x)) . Die in einem Schaltkreis benutzten Funktionen σ schr¨anken wir im Allgemeinen durch die Wahl einer Basis ein. Beschr¨ anken wir uns auf die bin¨aren Operationen {∧, ∨} und auf die un¨are Operation ¬, so nennen wir diese Basis auch Standardbasis. Um die Komplexit¨ at eines Schaltkreises zu messen, benutzen wir die Maße • Tiefe depth(C) als die L¨ ange des l¨ angsten Quellen-Senken Pfads, und • Gr¨oße size(C) als die Anzahl der Gatter in C. Die Tiefe entspricht der parallelen Zeit einer Berechnung und die Gr¨oße der parallelen Arbeit. Im Gegensatz zu dem Modell der PRAM k¨onnen Schaltkreise nur Berechnungen auf einer festen Eingabel¨ange durchf¨ uhren. Um dieses Modell auf beliebige Eingabel¨angen anwenden zu k¨onnen, betrachten wir sogenannte Schaltkreisfamilien: Definition 9 Eine Boolesche Schaltkreisfamilie C = (Ci )i∈N ist eine unendliche Sequenz von Booleschen Schaltkreisen Ci , wobei Ci eine Funktion von i Eingabewerten auf m(i) Ausgabewerten ist, d.h. fCi : {0, 1}i → {0, 1}m(i) . Die durch die Schaltkreisfamilie berechnete Funktion fC ergibt sich u ¨ber fC (x) = fC|x| (x) . Die Komplexit¨ at einer Schaltkreisfamilie ergibt sich u ¨ber depthC (n) = depth(Cn )
und
sizeC (n) = size(Cn ) .
Ohne weitere Einschr¨ ankungen nennen wir eine solche Schaltkreisfamilie nicht-uniform. Hiermit dr¨ ucken wir aus, dass diese Familie nicht konstruierbar sein muss. Beschr¨anken wir uns jedoch auf konstruierbare Schaltkreisfamilien, so kommen wir zu uniformen Schaltkreisfamilien. Eine Schaltkreisfamilie C = (Ci )i∈N nennen wir uniform, wenn eine Turing-Maschine auf Eingabe von 1n den Schaltkreis Cn generiert. Beschr¨ anken wir uns auf effizient konstruierbare Schaltkreisfamilien, so stehten die logPlatz-konstruierbaren Schaltkreisfamilien im Mittelpunkt unseres Interesses. Definition 10 F¨ ur k ∈ N bezeichnet N C k die Menge aller Booleschen Sprachen, d.h. Funktionen f : ∗ {0, 1} → {0, 1}, die durch eine log-Platz-konstruierbare Schaltkreisfamilie C u ¨ber der Standardbasis akzeptiert werden kann. Hierbei gilt, dass depthC (n) ∈ O(logk (n)) und sizeC (n) polynomiell sind. Die Klasse N C, auch Nicks-Klasse genannt, ist [ NC = N Ck . k∈N
Ein einfaches Beispiel f¨ ur eine solche Funktion ist die Addition. Seien a =
n−1 X
ai · 2i ,
i=0
b =
n−1 X
bi · 2i ,
c = a+b =
i=0
n X
ci · 2i
i=0
f¨ ur a0 , . . . , an−1 , b0 , . . . , bn−1 , c0 , . . . , cn ∈ {0, 1}. Einen Additionsschaltkreis erhalten wir u ¨ber: sn si c0 ci+1
= cn = ai XOR bi XOR ci = 0 = (ai ∧ bi ) ∨ (ai ∧ ci ) ∨ (bi ∧ ci ) . 43
Der so konstruierte Schaltkreis hat jedoch eine lineare Tiefe, die vor allem auf die Berechnung der Werte ci zur¨ uckzuf¨ uhren ist. Erinnern wir uns jedoch an die Berechnung der Pr¨afixsummen, so k¨onnen wir folgende Beobachtung herleiten: Beobachtung 14 Seien pi = ai XOR bi und gi = ai ∧ bi f¨ ur alle i = {0, . . . , n − 1}. Ferner sei (ri , ti ) = (p0 , g0 ) ◦ (p1 , g1 ) ◦ . . . ◦ (pi , gi ) f¨ ur (P0 , G0 ) ◦ (P1 , G1 ) = (P0 ∧ P1 , G1 ∨ (P1 ∧ G0 )) . Dann gilt ci+1 = ti . Da die Operation ◦ assoziativ ist, k¨ onnen wir zur Berechnung der einzelnen Werte ci einen Baum logarithmischer Tiefe benutzen. Es folgt somit unmittelbar: Theorem 19 Die Addition zweier nicht-negativer Zahlen ist in N C 1 .
6.2
Addition in Tiefe o(log n)
Wie wir oben gesehen haben, ist die Addition zweier nicht-negativer Zahlen in N C 1 . Wir wollen nun untersuchen, ob wir die Addition auch mit einem Schaltkreis der Tiefe o(log n) realisieren k¨onnen. Zun¨achst k¨onnen wir ein negatives Ergebnis herleiten. Theorem 20 Die Addition zweier Zahlen kann nicht durch eine Schaltkreisfamilie u ¨ber der Standardbasis der Tiefe o(log n) berechnet werden, wenn wir uns auf Bin¨ arzahlen beschr¨ anken. Beweis: Der Beweis erfolgt u ¨ber eine Widerspruchsannahme. Sei C eine Schaltkreisfamilie u ¨ber der Standardbasis der Tiefe o(log n), die die Addition korrekt berechnet. Dann gibt es ein n, so dass die Tiefe von C2n kleiner log n ist. Wir betrachten die h¨ochstwertige Ausgabeposition sn . Da die Tiefe von C2n kleiner log n ist und jedes Gatter maximal zwei Vorg¨angergatter hat, kann C2n zur Bestimmung von sn maximal 2depth(C2n ) < n − 1 Eingabepositionen ber¨ ucksichtigen. Es gibt somit eine Position i, so dass C2n zur Bestimmung von sn die Eingabe ai nicht ber¨ ucksichtigt. Wir setzen nun f¨ ur alle Positionen j, k mit k 6= j die Werte bj auf 1 und die Werte ak auf 0. Die Wahl von ai lassen wir zun¨achst noch offen. Da die Berechnung von sn in C2n unabh¨angig von ai ist, kann sn mit Hilfe der oben spezifizierten Eingabepositionen berechnet werden. Sei b das Ergebnis dieser Berechnung. Gilt b = 0, so w¨ahlen wir ai = 1 und erhalten f¨ ur das Ergebnis der Addition sn = 1. Ist b = 1, so w¨ahlen wir ai = 0 und erhalten f¨ ur das Ergebnis der Addition sn = 0. In beiden F¨allen berechnet Cn die Addition fehlerhaft – ein Widerspruch. Wir wollen nun zeigen, dass die Addition in konstanter Tiefe m¨oglich ist, wenn wir eine redundante Zahlendarstellung w¨ ahlen. Definition 11 F¨ ur ein k ∈ N sei Zm = {0, . . . , m − 1} mit m = 4k + 1. Die Radix-4 Darstellung einer Zahl x ∈ Zm ist ein k + 1-Tupel (xk , . . . , x0 ) mit xk , . . . , x0 ∈ {−3, . . . , 3}, f¨ ur welches gilt |(xk , . . . , x0 )|4
=
k X
xi · 4i
≡ x mod m .
i=0
F¨ ur diese Darstellung gilt 15 = 3 · 4 + 3 = |(3, 3)|4 = |(0, −2)|4 = −2 ≡ 15 mod 17 Zur Darstellung der Zahlen xi w¨ ahlen wir eine beliebige Darstellung, f¨ ur die wir Vergleich, Addition und Subtraktion effizient, d.h. mit Hilfe von Schaltkreisen konstanter Tiefe und Gr¨oße, realisieren k¨onnen. Dann gilt: Theorem 21 Die Addition zweier Zahlen kann durch eine log-Platz-konstruierbare Schaltkreisfamilie u oße O(n) berechnet werden, wenn wir uns auf Zahlen in ¨ber der Standardbasis der Tiefe O(1) und Gr¨ der Radix-4 Darstellung beschr¨ anken. 44
Beweis: Seien (xk−1 , . . . , x0 ) und (yk−1 , . . . , y0 ) die Eingaben x und y in Radix-4 Darstellung und (zk−1 , . . . , z0 ) eine Radix-4 Darstellung von z = x + y. Die Berechnung von z erfolgt in zwei Schritten: 1. Zun¨achst addieren wir die einzelnen Positionen von x und y, und erhalten xi + yi = 4 · ci + vi . Hierbei werden die Werte von ci und vi gem¨aß der folgenden Tabelle bestimmt: xi + yi vi ci
−6 −2 −1
−5 −1 −1
−4 0 −1
−3 1 −1
−2 −2 0
−1 −1 0
0 0 0
1 1 0
2 2 0
3 −1 1
4 0 1
5 1 1
6 2 1
Es gilt somit |vi | ≤ 2 und |ci | ≤ 1. Ferner gilt: x+y
= xk 4k + xk−1 4k−1 + · · · + x0 + yk 4k + yk−1 4k−1 + · · · + y0 = (xk + yk )4k + (xk−1 + yk−1 )4k−1 + · · · + (x0 + y0 ) = (4ck + vk )4k + (4ck−1 + vk−1 )4k−1 + · · · + (4c0 + v0 )40 = 4k+1 ck + (vk + ck−1 ) 4k + (vk−1 + ck−2 ) 4k−1 + · · · + v0 | {z } | {z } |vk +ck−1 |≤3
|vk−1 +ck−2 |≤3
Wir erhalten somit si = vi + ci−1 f¨ ur i = 1, . . . , k − 1 und s0 = v0 . Es tritt jedoch ein Problem bei der Behandlung des Terms ck · 4k+1 auf, da es keinen k + 2-ten Eintrag in der Radix-4 Darstellung von z gibt. Es gilt jedoch: ck 4k+1 = 4ck 4k = 4ck (m − 1) ≡ −4ck
mod m .
Mit Hilfe eines Schaltkreises der Gr¨ oße O(k) und der Tiefe O(1) k¨onnen wir die Addition x + y auf eine vereinfachte Addition von s + c∗ reduzieren, wobei (sk , . . . , s0 ) die Radix-4 Darstellung von s und (0, . . . , 0, −ck , 0) die Radix-4 Darstellung von c∗ ist. 2. Im zweiten Schritt addieren wir s und c∗ . Hierbei unterscheiden wir die folgenden F¨alle: (a) |s1 − ck | ≤ 3 In diesem Fall w¨ ahlen wir z1 = s1 − ck und zi = si f¨ ur alle i 6= 1. F¨ ur z erhalten wir die Radix-4 Darstellung von (zk , . . . , z0 ). (b) |s1 − ck | > 3 Nach Konstruktion gilt |s1 | ≤ 3 und |ck | ≤ 1. Dieses impliziert, dass |s1 − ck | = 4 ist. Addieren wir s und c∗ auf die gleiche Art und Weise, wie wir schon x und y addiert haben, so erhalten wir zwei neue Zahlen sˆ und cˆ∗ , so dass x + y = s + c∗ = sˆ + cˆ∗ ist. Es gilt jedoch, dass |s0 | ≤ 2, c∗0 = 0 und |s1 − ck | = 4 ist. Folglich gilt sˆ0 = s0 und sˆ1 = 0. c∗1 | = 1 Nach Konstruktion des ersten Schritts der Addition gilt |ˆ c∗1 | = 1. Da somit |ˆ s1 + cˆ∗1 | = |ˆ ∗ ist, k¨ onnen wir nun in einem dritten Schritt die Summe von sˆ und cˆ direkt berechnen. Um die Addition in der Radix-4 Darstellung benutzen zu k¨onnen, m¨ ussen wir eine M¨oglichkeit finden Bin¨arzahlen in diese Darstellungsform zu u ¨bertragen und umgekehrt. Die Umwandlung einer Bin¨arzahl x = bn−1 . . . b0 in eine Radix-4 Darstellung (xk , . . . , x0 ) erhalten wir durch lnm xi = b2i+1 b2i f¨ ur alle i ≤ k und k = . 2
45
Die Umwandlung einer Radix-4 Darstellung (xk , . . . , x0 ) in eine Bin¨arzahl x = bn−1 . . . b0 ist etwas + aufw¨andiger. Wir teilen hierf¨ ur (xk , . . . , x0 ) in dessen positiven Anteil (x+ k , . . . , x0 ) und in dessen nega− − tiven Anteil (xk , . . . , x0 ), wobei f¨ ur alle i gilt xi f¨ ur xi > 0 −xi f¨ ur xi < 0 − x+ = und x = i i 0 sonst 0 sonst. + − − Offensichtlich gilt (xk , . . . , x0 ) = (x+ k , . . . , x0 )−(xk , . . . , x0 ). Die Umwandlung einer Radix-4 Darstellung (xk , . . . , x0 ), die ausschließlich aus positiven Eintr¨agen besteht, in eine Bin¨arzahl kann analog zu der Umwandlung der Bin¨ arzahl in eine Radix-4 Darstellung erfolgen. Die einzigen Probleme, die wir demnach noch zu l¨osen haben, sind die Berechnung der Subtraktion und die Moduloberechnung am Ende der Subtraktion. Das zweite Problem k¨ onnen wir mit Hilfe der Addition bzw. der Subtraktion von 0, m, 2m oder 3m l¨ osen. Die Subtraktion k¨ onnen wir analog zur Addition implementieren. Hierbei m¨ ussen wir jedoch darauf achten, ob wir negative Zahlen zulassen wollen oder nicht.
Theorem 22 Die Subtraktion zweier Zahlen sowie die Umwandlung einer Bin¨ arzahl in eine Radix-4 Darstellung und umgekehrt sind in N C 1 . ¨ Es ist eine einfache Ubungsaufgabe die Multiplikation zweier n-Bit Bin¨arzahlen mit Hilfe von n − 1 Additionen in einem Bin¨ arbaum der Tiefe O(log n) zu implementieren. Wandeln wir die einzelnen Summanden in ihre Radix-4 Darstellung um, und transformieren wir abschließend das Ergebnis der Gesamtsumme wieder in eine Bin¨ arzahl, so erkennen wir: Theorem 23 Die Multiplikation zweier Bin¨ arzahlen ist in N C 1 .
6.3
Die Division und das Newton-Verfahren
Im Folgenden wollen wir uns der Division zuwenden. Das hier vorgestellte Verfahren ist jedoch nicht optimal. Es basiert jedoch auf einem Verfahren, mit dessen Hilfe auch verschiedene andere Funktionen berechnet werden k¨ onnen: dem Newton-Verfahren. F¨ ur die Division a/d k¨ onnen wir zwei Varianten betrachten. In der ersten Variante wollen wir den maximalen Wert q ∈ Z finden, so dass a = q · d + r ist, und in der zweiten Variante den Quotienten m¨oglichst genau bestimmen. Im Folgenden wollen wir uns der zweiten Variante zuwenden. Da der Quotient a/d im Allgemeinen nicht als endliche Bin¨arzahl dargestellt werden kann, wollen wir versuchen eine gute Approximation f¨ ur den Quotienten zu finden. Definition 12 Sei x ∈ R, dann ist x ˜ eine Approximation von x mit Pr¨ azision 2−c – Pr¨ azision mit c −c Bits –, wenn |x − x ˜| ≤ 2 . Wir definieren die Division wie folgt: Definition 13 Division: Gegeben: Zwei n-Bit Bin¨ arzahlen a, d ∈ N. Gesucht: Berechne den Wert x, so dass a x − ≤ 2−n . d Um eine solche Approximation zu finden, werden wir das Newton-Verfahren anwenden. 6.3.1
Das Newton-Verfahren
Das folgende Theorem stellt die Basisversion des Newton-Verfahrens dar: Theorem 24 Sei f eine im Intervall [a, b] 2-fach stetig differenzierbare Funktion mit f 0 (x) 6= 0 f¨ ur alle x ∈ [a, b] und f (x) · f 00 (x) ≤ 1, max x∈[a,b] (f 0 (x))2
46
dann existiert exakt eine Nullstelle x von f in [a, b] und die Folge xk+1
= xk −
f (xk ) f 0 (xk )
konvergiert gegen diesen Wert. Abh¨angig vom Startwert x0 konvergiert das Newton-Verfahren quadratisch gegen die Nullstelle. Das folgende Verfahren zeigt ein entsprechendes Ergebnis unabh¨angig vom Startwert: Theorem 25 Sei f eine im Intervall [a, b] 2-fach stetig differenzierbare Funktion mit f 0 (x) 6= 0 f¨ ur alle x ∈ [a, b] und f (x) · f 00 (x) ≤ 1. max (f 0 (x))2 x∈[a,b] Ferner sei f eine konvexe Funktion, d.h. f¨ ur alle x1 , x2 ∈ [a, b] gilt f ((x1 + x2 )/2) ≤
f (x1 ) + f (x2 ) 2
und f (a) < 0 < f (b), dann gilt 1. es existiert exakt eine Nullstelle x von f in [a, b] 2. die Sequenz xk+1
= xk −
f (xk ) f 0 (xk )
ist monoton fallend und konvergiert zur Nullstelle von f f¨ ur jeden Startwert x0 ∈ [a, b] und 3. f¨ ur f 0 (z) ≤ C > 0 und f 00 (x) ≤ K f¨ ur alle z < x ≤ b, dann gilt ∀n ∈ N : 6.3.2
|xn+1 − xn | ≤ |z − xn | ≤
K · |xn − xn−1 |2 . 2C
Die Division
Um das Problem der Division zu l¨ osen, k¨onnen wir zwei Teilprobleme betrachten: 1) das Berechnen des Kehrwerts d−1 mit einer Pr¨ azision von 2−2n und 2) die Multiplikation der Approximation d˜−1 von d−1 mit a. Lemma 16 Seien a, d ∈ N zwei n-Bit Bin¨ arzahlen. Sei d˜−1 eine Approximation von d−1 mit einer −2n −1 ˜ Pr¨ azision von 2 , dann ist a · d eine Approximation von a/d mit Pr¨ azision von 2−n . Beweis: Da a < 2n ist, gilt a − a · d˜−1 = |a| · |d−1 − d˜−1 | ≤ 2n · 2−2n = 2−n . d Zun¨achst reduzieren wir unser Problem den Kehrwert d−1 zu finden auf Zahlen d mit 12 ≤ d < 1. Wir Pn−1 nehmen hierf¨ ur an, dass d = i=0 (1−d)i ist und dk die h¨ochstwertig Stelle von d ist, deren Wert ungleich 0 ist. Dann k¨ onnen wir die Berechnung von d˜−1 darauf reduzieren, den Kehrwert von 2−k · d ∈ [1/2, 1) mit einer Pr¨ azision von 2−2n zu berechnen. Das um k Bits nach rechts geshiftete Resultat approximiert den Kehrwert mit einer Pr¨ azision von 2n + k Bits. P2n+1 Lemma 17 Sei 1/2 ≤ d < 1 und b2n+1 = i=0 (1 − d)i , dann gilt |d−1 − b2n+1 | ≤ 2−2n .
47
Beweis: Da 1/2 ≤ d < 1 ist, gilt 0 < 1 − d ≤ 1/2. Somit konvergiert die Reihe
∞ P
(1 − d)i und es gilt
i=0 ∞ X
(1 − d)i =
i=0
Der Grenzwert
∞ P
1 1 = . 1 − (1 − d) d
(1 − d)i ist der Kehrwert von d. Es gilt:
i=0
∞ ∞ 2n+1 X X X |d−1 − b2n+1 | = (1 − d)i − (1 − d)i (1 − d)i = i=0
i=2n+2
i=0
und somit |d−1 − b2n+1 |
≤
∞ X
|(1 − d)i | ≤
i=2n+2
=
1 − 1 + ( 21 )2n+2 1 − 21
i ∞ X 1 2 i=2n+2
= 2−(2n+1) < 2−2n .
Um die Folge der Werte von bi nicht Schritt f¨ ur Schritt berechnen zu m¨ ussen, werden wir auf das Newton-Verfahren zur¨ uckgreifen. Wir erinnern uns: Sei f eine 2-fach stetig differenzierbare Funktion und x∗ ein Wert mit f (x∗ ) = 0, dann k¨ onnen wir x∗ mit der gew¨ unschten Pr¨azision bestimmen, indem wir die Werte der Folge f (xk−1 ) xk = xk−1 − 0 f (xk−1 ) bestimmen – vorausgesetzt, der Startwert x0 wurde korrekt gew¨ahlt. Es gilt lim xk = x∗ .
k→∞
Um dieses Verfahren auf das Problem, den Kehrwert zu finden, anzuwenden, setzen wir f (x) = d − 1/x. Es gilt f 0 (x) = x12 und f 00 (x) = − x23 . Die Sequenz (xk )k∈N , die wir aus dem Newton-Verfahren erhalten, ist durch f (xk−1 ) 1 2 xk = xk−1 − 0 = xk−1 − xk−1 d − = (2 − xk−1 d)xk−1 f (xk−1 ) xk−1 gegeben. Sind alle Voraussetzungen des Newton-Verfahrens erf¨ ullt, dann konvergiert diese Folge quadratisch gegen d−1 . Somit gen¨ ugen O(log n) Iterationen, um die gew¨ unschte Pr¨azision zu erreichen. Es verbleibt somit zu testen, ob diese Voraussetzungen erf¨ ullt sind. Da 1/2 ≤ d < 1 ist, ist x ∈ (1, 2]. Es gilt 1 2 f (x) · f 00 (x) = max −(d − x ) · x3 max 1 (f 0 (x))2 x∈(1,2] x∈(1,2] x4 =
max |−(2d · x − 2)| . x∈(1,2]
Ist d = 1 − ε f¨ ur einen kleinen Wert ε > 0, so gilt max |−(2d · x − 2)| = x∈(1,2]
max |−2x + 2εx + 2| x∈(1,2]
≥ 2 − 2ε . Betrachten wir zum Beispiel d = 34 , so gilt 2 − 2ε = 23 > 1. Die Voraussetzungen des Newton-Verfahrens sind somit nicht erf¨ ullt. Um die Folge xk dennoch zur Approximation von d−1 zu nutzen, leiten wir das folgende Theorem her: 48
Theorem 26 Seien d mit 1/2 ≤ d < 1, x0 = 1 und xk = (2 − xk−1 d)xk−1 f¨ ur alle k ≥ 1. Dann gilt k 2X −1
xk =
(1 − d)i .
i=0
Beweis: Der Beweis erfolgt durch eine Induktion u ur k = 1 gilt ¨ber k. F¨ x1 = (2 − x0 d)x0 = 1 + (1 − d) =
1 2X −1
(1 − d)i .
i=0
Der Induktionsschritt von k zu k + 1 erfolgt durch: xk+1
(2 − xk d)xk = 2xk − xk dxk
=
2xk −
=
k 2X −1
i
(1 − d) · d ·
i=0
k 2X −1
(1 − d)i .
i=0
Aus der Induktionshypothese folgt: xk+1
=
k 2X −1
2xk +
i
(1 − d) (−1 + (1 − d))
i=0 2X −1
2xk −
k
(1 − d)i
2X −1
i=0
+
k 2X −1
(1 − d)i
i=0 k 2X −1
(1 − d)i+1
i=0
(1 − d)i
i=0 k
=
2xk − xk −
2X −1
i
(1 − d)
i=1
+
k 2X −1
i
(1 − d)
i=1
= xk + 2
k 2X −1
(1 − d)i
i=0
k 2X −1
2k
i
(1 − d) + (1 − d)
k 2X −1
2k +i
(1 − d)
k 2X −1
(1 − d)i
i=0
i=0
=
i=0
=
(1 − d)i
i=0
k
=
k 2X −1
k 2X −1
i=0
i
(1 − d) +
2k+1 X−1
(1 − d)i
i=2k
k+1
X−1
(1 − d)i .
i=0
Man beachte, dass die Sequenz (xk )k∈N eine Teilsequenz von (bk )k∈N aus Lemma 17 ist. Somit folgt k aus Lemma 17, dass xk eine Approximation von d−1 mit Pr¨azision 2−2 +2 ist. Diese Beobachtungen k¨onnen wir in folgendem Theorem zusammenfassen: Theorem 27 Es existiert eine log-Platz konstruierbare Familie C = (Cn )n∈N Boolescher Schaltkreise mit depthC (n) = O((log n)2 ) und sizeC (n) = O(M (n) log n), so dass Cn auf Eingabe zweier n-Bit Bin¨ arzahlen a, d den Quotient a/d mit einer Pr¨ azision von 2−n berechnet. Hierbei ist M (n) die Gr¨ oße eines Multiplikationsschaltkreises auf Eingaben der L¨ ange n. Beweis: Im Theorem 26 haben wir gezeigt, dass d−1 mit einer Pr¨azision von 2−2n in dlog 2ne + 1 Iterationen berechnet werden kann. In jeder Iteration m¨ ussen wir zwei Multiplikationen und eine Addition ausf¨ uhren. F¨ ur eine Multiplikation ben¨otigen wir einen Schaltkreis der Tiefe O(log n) und der
49
Gr¨oße M (n). Die Addition ben¨ otigt die Tiefe O(log n) und Gr¨oße O(n). Somit k¨onnen wir d−1 mit der gew¨ unschten Pr¨ azision in Tiefe O((log n)2 ) und Gr¨oße O(M (n) log n) berechnen. Ferner haben wir gesehen, dass der Quotient a/d in der gew¨ unschten Pr¨azision durch eine einfache Multiplikation von a mit der Approximation von d−1 bestimmt werden kann. Diese Operation ver¨andert jedoch nicht die asymptotischen Schranken, die wir oben gefunden haben. Abschließend verbleibt noch zu zeigen, dass diese Familie auch log-Platz konstruierbar ist. Da sich jeder Schaltkreis einfach durch die Verwendung eines Z¨ahlers f¨ ur die jeweiligen Indizes der Werte xk und einer Routine zur Generierung der Multiplikations- und Additionsschaltkreise berechnen kann, ist die log-Platz Konstruierbarkeit erf¨ ullt.
Literatur [CoLR90]
T. H. Cormen, C. E. Leiserson, R. L. Rivest, Introduction to Algorithms, MIT Press, Cambridge-London, 1990.
[GrHR95]
R. Greenlaw, H. J. Hoover, W. L. Ruzzo, Limits to Parallel Computation: P-Completeness Theory, Oxford University Press, New York-Oxford, 1995.
[JaJa92]
J. J´ aJ´ a, An Introduction to Parallel Algorithms, Addison-Wesley, 1992.
[Ladn75]
R. E. Ladner, The Circuit Value Problem is Log Space Complete for P, SIGACT News 7(1), 1975, 18-20.
[Leig92]
F. Thomson Leighton, Introduction to Parallel Algorithms and Architectures: Arrays, Trees, Hypercubes, Morgan Kaufmann Publishers, San Mateo, 1992.
[Reis99]
K. R. Reischuk, Komplexit¨ atstheorie, Band I: Grundlagen, B. G. Teubner Stuttgart - Leipzig, 1999.
[Zeug03]
T. Zeugmann, Parallel and Distributed Systems, Universit¨at zu L¨ ubeck, Course Notes 2003.
50