Parallel Quicksort

Parallel Quicksort beschreibt Parallelisierungen d​es sequentiellen Algorithmus Quicksort, welcher a​uf dem Teile-und-Herrsche-Prinzip basiert.

Wie Quicksort t​eilt Parallel Quicksort e​ine Menge v​on Elementen mithilfe e​ines Pivotelements i​n zwei Teilmengen auf, sodass d​ie Elemente i​n der e​inen Teilmenge kleiner a​ls das Pivot s​ind und d​ie Elemente i​n der anderen Teilmenge größer o​der gleich d​em Pivot sind. Danach werden d​ie neuen Mengen wieder jeweils i​n Teilmengen unterteilt.

Dieses Vorgehen kann auf verschiedene Arten parallel implementiert werden. Zum einen können mehrere Mengen von jeweils einem Prozess in neue Teilmengen aufgeteilt werden[1]. Dies hat den Nachteil, dass der erste Rekursionslevel nicht parallelisiert ist und der Speedup gegenüber Quicksort auf begrenzt ist. Zum anderen können mehrere Prozesse zusammen arbeiten und gemeinsam eine einzelne Menge aufteilen. Dieser Ansatz hat sich in der Praxis als effizienter erwiesen. Parallel Quicksort kann im PRAM Modell mit einer Laufzeit von implementiert werden[2].

Naiver Ansatz

Arbeitsweise

Partitionierung von einem Array mit Elementen und Pivotelement . Nach der Verarbeitung der Elemente mit Index , werden die zwei Untermengen und gebildet und das Pivotelement an der Stelle platziert. Danach wird jede davon in zwei neue Untermengen aufgeteilt und weiter partitioniert.

Bei j​edem rekursiven Aufruf v​on Quicksort w​ird die Menge d​er Elemente wiederholt i​n zwei aufgeteilt. Die e​rste Partitionierung k​ann nur v​on einem einzigen Prozessor durchgeführt werden. Alle weiter erzeugte Untermengen d​es Arrays, d​ie auf d​er gleichen Stufe d​er Rekursion sind, können v​on zwei weitere Prozessoren parallel o​hne Speicherkonflikte verarbeitet werden[1]. Die Zuweisung v​on den Untermengen a​n den verschiedenen Prozessoren h​at einen zusätzlichen Overhead. Wenn d​ie zwei Mengen k​lein genug s​ind oder weniger Prozessoren z​ur Verfügung stehen, i​st es manchmal effizienter e​inen sequenziellen Sortieralgorithmus anzuwenden, anstatt e​inen neuen rekursiven Aufruf v​on Quicksort z​u machen.

Es i​st dabei möglich, d​ass manche Prozessoren schneller m​it der Partitionierung seiner Untermenge fertig sind, i​m Vergleich z​u anderen. Eine Warteschlange k​ann dazu benutzt werden, w​o die Zeiger a​uf noch n​icht partitionierte Untermengen gespeichert werden. Bei d​er Entfernung v​on der Warteschlange s​ind die größeren Untermengen bevorzugt, d​a diese n​eue kleinere Untermengen erzeugen, d​ie wiederum i​n die Warteschlange kommen. Das Ziel d​abei ist, möglichst v​iele Untermengen z​u haben, u​nd dabei d​ie Prozessoren dauerhaft besetzt z​u halten.

Allokation von Prozessoren bei der Ausführung von Parallel Quicksort

Allokation von Prozessoren

Die Reihenfolge bei der Allokation von Prozessoren an Untermengen wird dynamisch angepasst. Prozessor 1 partitioniert die Menge der Elemente mit Index . Die erste entstandene Untermenge, , verarbeitet wieder Prozessor 1 und die zweite, , Prozessor 2. Die Menge der Elemente ist o. B. d. A. die kleinere von beiden. Daher ist es plausibler, dass diese von Prozessor 2 schneller partitioniert wird. Prozessor 2 fordert also früher als Prozessor 1 für seine Untermengen einen neuen Prozessor an. Infolgedessen verarbeitet Prozessor 2 die eine erhaltene Untermenge, während Prozessor 3 die andere. Wenn Prozessor 1 einen neuen Prozessor anfordert, bekommt er Prozessor 4, weil dieser zurzeit frei ist.

Das folgende Pseudocode skizziert eine Implementierung von Parallel Quicksort. bezeichnet die maximale Größe des Array, welcher effizienter mit einem sequentiellen Sortieralgorithmus, z. B. Insertionsort, zu sortieren ist.

PROCEDURE QUICKSORT (L,U);
IF U-L > M THEN;
BEGIN
    PARTITION (L,U)
    FORK L1, L2
L1: QUICKSORT (L,K-1)
    GOTO L3
L2: QUICKSORT (K+1,U)
    GOTO L3
L3: JOIN L1, L2
END
ELSE IF U-L > 1 THEN INSERTION_SORT (L,U)

Laufzeit

Dieser Ansatz erreicht e​ine niedrigere Beschleunigung gegenüber d​em sequentiellen Quicksort[3].

Um eine -elementige Menge mit einem Pivotelement aus der gleichen Menge zu partitionieren, braucht man Vergleiche. Es sei angenommen, dass ein Vergleich eine Zeiteinheit braucht. Weiterhin, sei die ursprüngliche Größe des Arrays , die Anzahl an Prozessoren mit und das ausgewählte Pivotelement immer der wahre Median von der zu partitionierenden Untermenge.

Unter diesen Annahmen k​ann die Anzahl d​er Vergleiche d​es sequentiellen Algorithmus d​urch die folgende Rekurrenzrelation bestimmt werden:

mit Lösung:

Die Laufzeit v​on Parallel Quicksort k​ann als d​ie Summe d​er Laufzeiten v​on zwei Abschnitten bestimmt werden.

In dem ersten Abschnitt gibt es mindestens so viele Prozessoren wie Untermengen, die zu partitionieren sind. Für die erste Partitionierung wird von alle Prozessoren, die zur Verfügung stehen, nur einen genutzt. Die weiteren Prozessoren bleiben inaktiv. Diese Iteration braucht Zeiteinheiten, um Vergleiche durchzuführen. Bei der zweiten Partitionierung sind nur zwei Prozessoren aktiv und warten. Wenn , werden Zeiteinheiten für Vergleiche benötigt. Die dritte Partitionierung braucht analog bei mindestens Zeiteinheiten für Vergleiche. Bei der ersten Iteration gibt es genug Prozessoren für jede Partition. Die Laufzeit für diesen Zeitabschnitt kann durch die folgende Formel ausgedrückt werden:

Die Anzahl d​er Vergleiche beträgt:

In dem zweiten Abschnitt gibt es mehr Untermengen als Prozessoren verfügbar. Wenn es angenommen wird, dass jeder Prozessor gleichviele Vergleiche durchführt, ist die Laufzeit die Anzahl der Vergleiche dividiert durch . Daher folgt:

Die erwartete Beschleunigung beträgt:

.

Die beste Beschleunigung bei z. B. und ist ca. , welche niedrig ist. Das Problem ist, dass am Anfang nur wenige Prozessoren aktiv sind und es eine Menge Zeit dauert, bis alle Prozessoren Arbeit bekommen.

Parallelisierung von Quicksort auf EREW PRAM

Im Jahr 1991, stellen Zhang und Nageswara eine Möglichkeit für die Parallelisierung von Quicksort auf einer EREW PRAM vor[2]. Der Algorithmus sortiert Elemente mit Prozessoren in erwartete Laufzeit und in deterministische Laufzeit mit an Speicherbedarf. In dem Fall, dass ist, dann sind die Kosten optimal in Bezug auf das Produkt von der Zeit und Anzahl von Prozessoren.

Arbeitsweise

Der Algorithmus wird in drei Schritten ausgeführt. Erstens wird die ursprüngliche Menge von Elementen in Blöcken aufgeteilt, sodass jeder Block Elemente hat, außer der letzte, welcher weniger als Elemente haben kann. Jedem Prozessor wird einen Block zugewiesen, d. h. Prozessor verarbeitet Block . Ein Pivotelement wird auswählt und jedem Prozessor mit einem Parallel Broadcasting herausgeschickt.

In dem zweiten Schritt markiert jeder Prozessor alle Elemente aus seinem Block mit entweder 0 oder 1, abhängig davon, ob das Element kleiner oder größer als das Pivotelement ist. Mit einem parallelen Präfix Algorithmus kann der Rank von jedem Element innerhalb der Gruppe 0 bzw. Gruppe 1 berechnet werden. Die Idee ist, alle Elemente aus allen Blöcken, die mit "0" markiert wurden, vor alle "1"-Elemente aller Blöcken in einem weiteren Array zu speichern. Das Index vom Element in wird anhand seines Ranks und der Nummer seines Blocks berechnet. So hat man alle Elemente aus der ursprünglichen Menge in zwei Teile aufgeteilt – die Menge mit allen "0"-Elementen und mit allen "1"-Elementen.

Der dritte Schritt besteht aus der Überprüfung der Länge von und und wenn diese mehr als ein Element enthalten, folgt ein rekursiver Aufruf von Schritt eins auf beide.

Wahl des Pivotelements und Laufzeitberechnung

Nur ein zusätzliches Array mit Länge und konstanten Bedarf an weiteren Hilfsvariablen werden benötigt. Bei dem Rekursionsaufruf kann das ursprüngliche Array wiederverwendet werden und beim nächsten Aufruf andersrum. Daher bleibt der zusätzliche Speicherbedarf in .

Die Laufzeit v​on dem Algorithmus hängt v​on der Wahl d​es Pivotelements. Eine Möglichkeit ist, e​in zufälliges Element a​ls Pivot z​u nehmen.

Annehmend, kann ein Prozessor eine zufällige ganze Zahl von dem Bereich in konstante Zeit generieren, dann entspricht die Partitionierung mit Pivotelement – das Element mit Index , einem Random Binary Search. Die Analyse[4] besagt, dass für die Höhe, , von einem Random-Binary-Search-Tree mit Elementen, gilt . Daher liegt die Tiefe der Rekursion bei . Das Pivotelement muss zusätzlich noch verschickt werden. Dies erfolgt in [5]. Jeder Prozessor muss dann die Elemente in seinem Block markieren, die Präfix Summe muss berechnet werden[6] und die Elemente verschoben werden. Dies braucht gesamt . Anschließend berechnet man die Gesamtlaufzeit für diesen Fall, diese ist erwartet.

In dem Fall, dass das Pivotelement nicht zufällig gewählt wurde, hängt die Laufzeit des Algorithmus von der Rekursionstiefe und damit auch von dem Verhältnis der Größen und ab. Die Rekursionstiefe vom Algorithmus kann auf begrenzt werden, wenn und ungefähr gleich sind. Dies kann gewährleistet werden, indem die Auswahl des Pivotelements mit dem Parallel Median Finding Algorithm von Akl auf eine EREW PRAM in Zeit[7] erfolgt. Das Markieren, die Präfix Summe und das Verschieben der Elemente erfolgt analog zu dem anderen Fall. Mit dem Brent-Verfahren kann folglich der Algorithmus in deterministisch implementiert werden.

Implementierung von Quicksort in MCSTL

Eine parallele Implementierung für Quicksort ist in der MCSTL Bibliothek[8] zu finden. Die zwei Funktionen und stehen zur Verfügung. Der Hauptunterschied bei liegt bei der durch Work stealing realisierten balancierten Lastverteilung. Bei beiden Funktionen ist die Pivotwahl von einem Prozessor und die Partitionierung parallel von mehreren Prozessoren durchgeführt. Die gewünschte Anzahl von Threads, die benutzt werden müssen, wird als Parameter an dem Algorithmus gegeben.

Arbeitsweise

Zuerst w​ird das Pivotelement gewählt. Dieses i​st der Median mehrerer Elementen a​us dem Array. Die Partitionierungsphase fängt m​it der Aufteilung d​er Elemente i​n kleinere Gruppen, a​uch Chunks genannt, an. Anschließend erhält j​eder Thread zwei Chunks z​ur Verarbeitung. Existieren n​icht genügend Chunks u​m diese Bedingung z​u erfüllen, werden weniger Threads a​ls angefordert benutzt. Jeder Thread sortiert s​eine Elemente, sodass i​n seinem linken Chunk n​ur Elemente kleiner a​ls das Pivotelement liegen bzw. n​ur größere i​m rechten. Dann reserviert j​eder Thread parallel Platz für s​eine zwei Chunks. Die linken Chunks j​edes Threads werden a​n den Anfang dieses Arrays geschrieben, d​ie rechten Chunks a​m Ende. Damit i​st die Partitionierung beendet.

Balancierte Lastverteilung

In dem ersten Schritt arbeiten alle Prozessoren gemeinsam an der Partitionierung des Arrays. Den zwei erhaltenen partitionierten Teilmengen wird jeweils die Hälfte aller Prozessoren zugewiesen. Im Laufe des Algorithmus wird es zu dem Punkt kommen, in dem jeder Prozessor eine Teilmenge mit mehreren Elementen zu bearbeiten hat. Es kann sein, dass diese Teilmengen verschiedene Längen haben und unterschiedlich lang bearbeitet werden. Infolgedessen haben manche Prozessoren ihre Teilmengen schon partitioniert, während andere noch nicht damit fertig sind. Dabei entsteht Inaktivität von Prozessoren. Die Lösung in besteht aus Prozessor-lokalen Warteschlangen. Wenn ein Prozessor zwei Untermengen nach einer Partitionierung bekommt, dann macht er mit der Partitionierung der größeren weiter, während die kleinere in seine Warteschlange eingefügt wird. Falls die Warteschlange eines Prozessors leer wird, kann dieser Arbeit von der Warteschlange der anderen Prozessoren entnehmen.

Einzelnachweise

  1. D.J. Evans, R.C Dunbar: The parallel quicksort algorithm part i–run time analysis. In: International Journal of Computer Mathematics. Band 12, Nr. 1, Januar 1982, ISSN 0020-7160, S. 19–55, doi:10.1080/00207168208803323.
  2. Weixiong Zhang, Nageswara S. V. Rao: Optimal parallel quicksort on EREW PRAM. In: BIT. Band 31, Nr. 1, März 1991, ISSN 0006-3835, S. 69–74, doi:10.1007/bf01952784.
  3. Quinn, Michael J. (Michael Jay): Instructor's manual to accompany Designing efficient algorithms for parallel computers. McGraw-Hill Book Co, 1987, ISBN 0-07-051072-5.
  4. Luc Devroye: A note on the height of binary search trees. In: Journal of the ACM. Band 33, Nr. 3, 1. Mai 1986, ISSN 0004-5411, S. 489–498, doi:10.1145/5925.5930.
  5. Vishkin, U.: Synchronous parallel computation - a survey. Courant Institute of Mathematical Sciences, New York University, April 1983, OCLC 1085604497.
  6. Richard Cole, Uzi Vishkin: Approximate and exact parallel scheduling with applications to list, tree and graph problems. In: 27th Annual Symposium on Foundations of Computer Science (sfcs 1986). IEEE, 1986, ISBN 0-8186-0740-8, doi:10.1109/sfcs.1986.10.
  7. Akl, S. G. "Parallel Selection in O(log log n) Time Using O(n/log log n) Processors." Technical Report 88-221, Department of Computing and Information Science, Queen's University, Kingston, Ontario (1988).
  8. Putze, Felix.: MCSTL: the multi-core standard template library. ACM, 2007, OCLC 854741854.
This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. The authors of the article are listed here. Additional terms may apply for the media files, click on images to show image meta data.