Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Edited bybyFoxit
FoxitReader
PDF Editor
Copyright (c)bybyFoxit
Copyright(C) FoxitSoftware
Software Company, 2004 - 2007
Company,2005-2007
For Evaluation Only.
For Evaluation Only.
Fachhochschule Wedel
Fachbereich Technische Informatik
Diplomarbeit
Eingereicht von
Jens Remus
Iltisweg 8, 21614 Buxtehude, jens.remus@gmx.de
Abgegeben am
21. Februar 2008
Erarbeitet im
11. Semester
Referent: Betreuer:
Prof. Dr. Sebastian Iwanowski Dr. Burkhard D. Steinmacher-Burow
Fachhochschule Wedel IBM Deutschland Entwicklung GmbH
Feldstraße 143 Schönaicher Straße 220
22880 Wedel 71032 Böblingen
Tel: (04103) 8048-63 Tel: (07031) 16-2863
E-Mail: STEINMAC@de.ibm.com
Diese Diplomarbeit wurde mit Hilfe von LATEX2e und KOMA-Script unter Verwendung
der Dokumentklasse scrbook mit der Erweiterung scrpage2 und den folgenden Paketen
gesetzt: amsmath, array, babel, booktabs, bytefield, csquotes, fixme, glossaries,
graphicx, hypercap, hyperref, icomma, ifdraft, ifthen, listings, longtable,
microtype, natbib, nomencl, onlyamsmath, rail, setspace, subfig, svninfo, tikz,
units, url, xcolor, xspace
Als Schriftarten wurden Latin Modern (Paket lmodern) und LuxiMono (Paket
luximono) verwendet.
Die Abbildungen wurden, sofern sie nicht fremden Quellen entnommen wurden, mit
TikZ in LATEX erstellt. Alle Graphen wurden mit Hilfe von gnuplot erzeugt.
Zusammenfassung
Das Ziel dieser Arbeit ist die Konzeption und Entwicklung einer
parallelen Laufzeitumgebung für CES in C zur parallelen Aus-
führung einer neuartigen Implementierung von Unterprogrammen
unter Verwendung von POSIX Threads zur Parallelisierung auf
einem SMP Computer mittels des Cop/Thief Cork-Stealing Lö-
sungsansatzes.
Ein vorhandener CES Compiler übersetzt CES Quellcode in
C Quellcode, welcher von einer Laufzeitumgebung für CES aus-
geführt wird. Der CES Compiler wurde im Rahmen dieser Arbeit
um die Unterstützung alternativer Laufzeitumgebungen für CES
erweitert. Der CES Compiler bietet dem Programmierer die Mög-
lichkeit, Unterprogrammaufrufe in CES als parallel ausführbar zu
deklarieren. Diese Information wird von der parallelen Laufzeit-
umgebung zur parallelen Ausführung genutzt.
Die entwickelte parallele Laufzeitumgebung für CES liefert auf-
grund des Work-Stealing parallele Performance für simple divide
and conquer Algorithmen wie zum Beispiel Sortieralgorithmen
(z. B. Mergesort, Quicksort), vollständige Suchalgorithmen (z. B.
Minimum/Maximum Suche, N-Damen Problem) und numerische
Integrationsalgorithmen.
iii
Inhaltsverzeichnis
Abbildungsverzeichnis x
Tabellenverzeichnis xii
Listings xiii
Abkürzungsverzeichnis xvii
Symbolverzeichnis xx
Vorwort xxi
1 Einleitung 1
1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Aufgabenstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
v
Inhaltsverzeichnis
vi
Inhaltsverzeichnis
vii
Inhaltsverzeichnis
5 Fazit 145
6 Ausblick 147
viii
Inhaltsverzeichnis
B Listings 156
B.1 Producer/Consumer Demonstrationsprogramm mit Pthreads in C . . 156
B.1.1 Programmquelltext . . . . . . . . . . . . . . . . . . . . . . . . . 156
B.1.2 Bildschirmausgabe . . . . . . . . . . . . . . . . . . . . . . . . . 158
B.2 Bildschirmausgabe des Pretty Printer der round-robin Laufzeitumgebung
für CES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
B.3 Atomare Operationen in C mit Inline-Assembler für IA-32 . . . . . . . 164
B.3.1 32-Bit Compare and Swap mit booleschem Rückgabewert . . . 164
B.3.2 32-Bit Compare and Swap mit datentypabhängigem Rückgabewert165
B.3.3 64-Bit Compare and Swap mit booleschem Rückgabewert . . . 166
B.3.4 64-Bit Compare and Swap mit datentypabhängigem Rückgabewert167
B.4 Definition des Thief Unterprogramms der parallelen Laufzeitumgebung
für CES mit Pthreads Synchronisation . . . . . . . . . . . . . . . . . . 168
B.5 Definition des Thief Unterprogramms der parallelen Laufzeitumgebung
für CES mit CAS64 Synchronisation . . . . . . . . . . . . . . . . . . . 170
B.6 Testprogramme zur Verifikation der parallelen Laufzeitumgebung für CES 172
B.6.1 Testprogramm 1 . . . . . . . . . . . . . . . . . . . . . . . . . . 172
B.6.2 Testprogramm 2 . . . . . . . . . . . . . . . . . . . . . . . . . . 174
Glossar 183
Literaturverzeichnis 185
Stichwortverzeichnis 191
ix
Abbildungsverzeichnis
x
Abbildungsverzeichnis
xi
Tabellenverzeichnis
C.1 Übersicht über die Verzeichnisse und Dateien der Arbeit . . . . . . . . 176
xii
Listings
xiii
Listings
xiv
Listings
4.1 Laufzeitmessung in CES mit Hilfe der CES Stopwatch Library . . . . . 107
4.2 Fibonacci Zahlen in CES (CES Unterprogramm fibonacci) – fibonacci.
ces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
4.3 Fibonacci Zahlen in CES (CES Unterprogramm add_uint64) – fibonacci.
ces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
4.4 Aufsummierung der natürlichen Zahlen von 1–n in CES (CES Unterpro-
gramm sumgauss) – sumn.ces . . . . . . . . . . . . . . . . . . . . . . . . 117
4.5 Aufsummierung der natürlichen Zahlen von 1–n in CES (CES Unterpro-
gramm sumnm) – sumn.ces . . . . . . . . . . . . . . . . . . . . . . . . . . 117
4.6 Aufsummierung der natürlichen Zahlen von 1–n in CES (CES Unterpro-
gramm add_uint64) – sumn.ces . . . . . . . . . . . . . . . . . . . . . 118
4.7 N -Damen Problem in CES (CES Unterprogramm nqueens) – nqueens.ces 120
4.8 N -Damen Problem in CES (CES Unterprogramm nqueens_recursive)
– nqueens.ces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
4.9 N -Damen Problem in CES (CES Unterprogramm sum_uint32) – nqueens.
ces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
4.10 N -Damen Problem in CES (C Unterprogramm test_board) – nqueens.
ces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
4.11 Mergesort in CES (CES Unterprogramm sort_mergesort) – sort.ces 124
4.12 Mergesort in CES (CES Unterprogramm mergesort_internal) –
sort.ces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
4.13 Mergesort in CES (CES Unterprogramm merge) – sort.ces . . . . 126
4.14 Quicksort in CES (CES Unterprogramm sort_quicksort) – sort.ces 128
4.15 Quicksort in CES (CES Unterprogramm quicksort_internal) – sort.
ces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
xv
Listings
xvi
Abkürzungsverzeichnis
API (Application Programming Interface) Eine Programmierschnittstelle, die von
einem Softwaresystem anderen Programmen zur Anbindung zur Verfügung gestellt
wird.
CESC (CES Compiler) Ein von Sven Wagner (IBM) entwickelter CES nach C Com-
piler.
ES (Execution System) Ein System zur Verwaltung und Ausführung von Arbeitspa-
keten. Das Konzept wird beispielsweise in Anwendungen für verteiltes Rechnen
eingesetzt.
xvii
Abkürzungsverzeichnis
FJTask (Java Fork/Join Framework) Framework von Doug Lea zur transparenten
nebenläufigen Programmierung in Java mit einer Cilk ähnelnden work-stealing
Ausführung. http://gee.cs.oswego.edu/dl/.
GCC (GNU Compiler Collection) Eine Sammlung von Compilern für verschiedene
Programmiersprachen (z. B. C, C++, Fortran, Ada, Pascal, Objective C, Java,
. . . ), die vom GNU Projekt entwickelt wurde und von der Free Software Foun-
dation unter den Bedingungen der GNU General Public License (GNU GPL)
und GNU Lesser General Public License (GNU LGPL) vertrieben wird. http:
//gcc.gnu.org/.
IBM (International Business Machines Corporation) Die IBM zählt zu den weltweit
größten Anbietern im Bereich Informationstechnologie (Hardware, Software und
Services). Der Standort Böblingen, die IBM Deutschland Entwicklung GmbH, ist
das größte Entwicklungszentrum außerhalb der USA. http://www.ibm.com/.
ILP (Instruction Level Parallelism) Ein Maß für die Anzahl der innerhalb eines Pro-
gramms parallel ausführbaren Operationen. Ziel vieler Compiler- und Prozessor-
optimierungen ist die Steigerung der ILP.
LIFO (Last In – First Out) Prinzip eines Stack (deutsch: Keller, Stapel).
NOP (No OPeration) Bezeichnung für einen Maschinen- oder Protokollbefehl der
effektiv nichts macht.
POWER (Performance Optimized With Enhanced RISC) Bezeichnung für eine Pro-
zessorarchitektur von IBM für Server, Workstations und Supercomputer.
Pthreads (POSIX Threads) Ein Portable Operating System Interface Standard für
Threads vom IEEE Portable Applications Standards Committee. http://www.
pasc.org/.
xviii
Abkürzungsverzeichnis
TBB (Intel® Threading Building Blocks) Eine von Intel® entwickelte und unter der
GPLv2 with runtime exception veröffentlichte Bibliothek für C++ zur paralle-
len Programmierung von SMP Computern. http://threadingbuildingblocks.
org/.
xix
Symbolverzeichnis
COPi Cop bzw. Cop Frame i
F Si Frame Stack i
Pi Prozessor i
xx
Vorwort
Ich möchte mich ganz herzlich bei Herrn Prof. Dr. Sebastian Iwanowski von der
Fachhochschule Wedel (FH Wedel) und bei Herrn Dr. Burkhard D. Steinmacher-Burow
von der International Business Machines Corporation (IBM) für die Betreuung dieser
Arbeit bedanken.
Bei Herrn Mark Lehmann von der IBM möchte ich mich für die kurzfristige Bereit-
stellung und Einrichtung des Testservers und der Installation einer aktuellen GCC-
und Java-Version bedanken.
Ganz besonderer Dank gilt meinen Eltern Barbara und Winfried Remus, die mir
mein Studium ermöglicht haben und mich dabei immer unterstützt haben.
xxi
Vorwort
xxii
1 Einleitung
The biggest sea change in software development since the OO
revolution is knocking at the door, and its name is Concurrency.
Die Mikroprozessorentwicklung hat sich in den letzten Jahren stark verändert. Noch
vor wenigen Jahren wurden Performancesteigerungen hauptsächlich durch höhere
Taktfrequenzen, immer komplexere Ausführungsoptimierungen auf Prozessorebene
(z. B. instruction pipelining, out-of-order execution, branch prediction, speculative
execution) als auch auf Compilerebene sowie durch größere und schnellere Caches
erreicht. [Sut05a; Shi06; Gil07]
Moores1 Vorhersage, das „Mooresche Gesetz“ (englisch: „Moore’s Law“), die besagt,
dass sich die Transistorzahl auf einem Chip etwa alle zwei Jahre verdoppelt, hat sich in
den letzten 40 Jahren bestätigt. [Intc] Wie man Abbildung 1.1 entnehmen kann, hat es
sich mit der Taktfrequenz bisher ebenso verhalten. Seit 2003 ist allerdings ein vorläufiges
Ende der Taktfrequenzsteigerung festzustellen. Auch die Steigerung der Instruction
Level Parallelism (ILP) durch Ausführungsoptimierungen auf Prozessorebene stagniert.
Stattdessen ist man auf neue Konzepte zur Performancesteigerung ausgewichen: Hyper-
Threading sowie Mehrkernprozessoren (englisch: multi-core CPU). [Sut05a; Shi06;
Gil07; DTM07] Abbildung 1.2 zeigt Intels Prognose der Entwicklung der Performanz
von Mehrkernprozessoren im Gegensatz zu derjenigen von Einkernprozessoren.
Im Unterschied zur früheren Performancesteigerung durch immer höhere Taktfrequen-
zen und komplexere Ausführungsoptimierungen profitiert Software nicht automatisch
von Hyper-Threading und Mehrkernprozessoren. Soll Software weiterhin von der expo-
nentiellen Performancesteigerung der Prozessoren profitieren, dann muss sie speziell in
Hinsicht auf Nebenläufigkeit programmiert werden.
Nach Herb Sutter2 [Sut05a] ist Nebenläufigkeit die nächste größere Revolution in
der Softwareentwicklung nach der Objektorientierten Programmierung (OOP) mit
dem gleichen Maß an Revolution und Komplexität sowie einer ähnlichen Lern- und
1
Gordon E. Moore: Intel-Mitgründer (1968) und ehemaliger CEO von Intel. [Inta]
2
Sutter, Herb (http://www.gotw.ca/): Vorsitzender des ISO C++ Standard Komitee, Softwareent-
wickler bei Microsoft und Autor mehrerer C++ Fachbücher und hunderter technischer Publikationen.
1
1 Einleitung
Erfahrungskurve.
Traditionelle Ansätze zur nebenläufigen Programmierung wie Threads erfordern vom
Programmierer, sich Gedanken über die Ausführung und die Threadverwaltung zu
machen, anstatt diese Zeit in die Entwicklung skalierbarer paralleler Algorithmen zu
investieren. [DTM07] Zudem birgt die auf Locks basierende Programmierung Risiken
wie etwa Dead- oder Livelocks und Race Conditions. [Sut05a]
Bisher gibt es noch kein Produkt, das alle Probleme der parallelen Programmierung
wie Korrektheit, Performanz (Skalierbarkeit) und die Benutzbarkeit sowie Produkti-
2
1.1 Motivation
vität allumfassend löst. Die Industrie hat gerade erst ihre Anstrengungen gesteigert,
Kandidaten für dem Mainstream zugängliche Produkte auf den Markt zu bringen, wie
etwa die Intel® Threading Building Blocks (TBB) [Intb], den Intel® C++ Software
Transactional Memory (SMT) Compiler [TG07] sowie die Microsoft .NET Parallel
Extensions (PFX) mit der Task Parallel Library (TPL). [Sut07]
1.1 Motivation
Diese Arbeit präsentiert einen neuen Ansatz basierend auf einem Execution System
(ES), welches die Ausführung einer neuen Implementierung von Unterprogrammen von
Dr. Burkhard D. Steinmacher-Burow (IBM) [SB00b] ermöglicht und im Falle ihrer
parallelelen Ausführung auch die Threadverwaltung und Synchronisation übernimmt.
Der neue Ansatz orientiert sich am Konzept erfolgreicher existierender Systeme zur
transparenten parallelen Datenverarbeitung. Beispiele solcher Systeme zur transpa-
renten parallelen Verarbeitung von Arbeitspaketen sind Berkeley Open Infrastructure
for Network Computing (BOINC)3 , Folding@home4 , MapReduce5 und manche Teile
von Cilk, Java Fork/Join Framework (FJTask) und TBB. Sie haben sich allesamt als
sehr erfolgreich zur parallelen Datenverarbeitung erwiesen, finden jedoch nur in einer
geringen Anzahl von Anwendungen Verwendung.
Der neue Ansatz erkennt die gemeinsame ES Architektur (siehe Abbildung 1.3) dieser
Systeme und generalisiert diese. Teil dieser Generalisierung ist eine neue Implementie-
rung von Unterprogrammen von Steinmacher-Burow.
Tabelle 1.1 zeigt die veränderte Aufgabenverteilung der Entwickler der einzelnen
Systemschichten bei der Entwicklung paralleler Anwendungen mit und ohne ES.
Die Arbeit basiert auf der Erweiterung der bekannten Programmiersprache C um ein
Execution System (ES) zur Ausführung der neuen Implementierung von Unterprogram-
men C with Execution System (CES) von Steinmacher-Burow und dem zugehörigen
CES Compiler (CESC) welcher von Sven Wagner (Fachhochschule Gießen-Friedberg)
im Rahmen seiner Diplomarbeit [Wag07] entwickelt wurde.
Abbildung 1.4 zeigt das langfristige Ziel, den Erfolg von ES Systemen zu generalisieren
und damit transparente Nebenläufigkeit einer breiten Masse von Anwendungen nutzbar
3
BOINC: Ein Projekt der Unviversität von Californien für verteiltes Rechnen. Bekannte Projekte, die
BOINC nutzen, sind unter Anderem Seti@home, Climateprediction.net, Rosetta@home und World
Community Grid. [BOI]
4
Folding@home: Das Projekt der Universität Stanford hat Ende 2007 einen Eintrag ins Guinness
Buch der Rekorde für das leistungsstärkste Netzwerk für verteiltes Rechnen erhalten. [Sta; Son07]
5
MapReduce: Ein Framework von Google zur parallelen Datenverarbeitung von großen Mengen von
Daten (viele Terabytes) auf einem Cluster von Massenware-Computern. [DG04]
3
1 Einleitung
Anwendung
bearbeitete
Aufgaben
Aufgaben
Hardware
sieren“
Produktivität der
herkömmlicher
Ansatz:
„Probleme ein-
dämmen“
herkömmliche
Anwendung im-
plementiert expli-
gering
zit Nebenläufig-
keit
wenige Anwendungen viele
4
1.2 Aufgabenstellung
1.2 Aufgabenstellung
Das Ziel dieser Arbeit ist die Konzeption und Entwicklung einer parallelen Lauf-
zeitumgebung für C with Execution System (CES) in C zur parallelen Ausführung
der neuartigen Implementierung von Unterprogrammen unter Verwendung von PO-
SIX Threads (Pthreads) zur Parallelisierung auf einem Symmetric Multiprocessing
(SMP) Computer mittels des Cop/Thief Cork-Stealing Lösungsansatzes.
Abbildung 1.5b zeigt das neue Konzept der Ausführung eines CES Programms mit
Hilfe der zu entwickelnden parallelen Laufzeitumgebung.
Die parallele Laufzeitumgebung für CES sieht unter Verwendung von Cop/Thief
Work-Stealing vor, dass ein Prozessor Arbeit von den anderen Prozessoren stehlen
kann, sobald er keine eigene Arbeit mehr hat oder diese blockiert ist. Der CESC von
6
Der interessierte Leser möge den FJTask Quelltext [Lea00], S. 2, und den TBB Quelltext [Int07e],
S. 50f und S. 56, mit dem CES Quelltext im Kapitel 4 vergleichen.
5
1 Einleitung
Anwendung Anwendung
NEU
Sequential CES Runtime Parallel CES Runtime
OS, . . . Pthreads, OS, . . .
6
2 C with Execution System (CES)
C with Execution System (CES) ist im eigentlichen Sinne keine neue Programmierspra-
che, sondern eine Erweiterung der bekannten Programmiersprache C um sogenannte
Execution Features zur Ausführung einer neuartigen Implementierung von Unterpro-
grammen (auch Funktionen, Prozeduren oder Routinen genannt; englisch: subroutines,
functions, procedures) von Steinmacher-Burow [SB00b].
Zusammen mit einer Laufzeitumgebung für CES (englisch: CES Runtime Environ-
ment; kurz: CES Runtime), welche die Ausführung der neuen Implementierung von
Unterprogrammen übernimmt, kann ein CES Programm mit Hilfe des CES Compiler
(CESC) und eines üblichen C Compiler (z. B. GCC, IBM XL C, . . . ) übersetzt werden.
Bei der herkömmlichen Implementierung von Unterprogrammen wird bei einem Unter-
programmaufruf das aufrufende Unterprogramm unterbrochen und zu dem aufgerufenen
Unterprogramm gewechselt. Nach dem Beenden des aufgerufenen Unterprogramms
wird das aufrufende Unterprogramm fortgesetzt. Da das aufrufende Unterprogramm
bei einem Unterprogrammaufruf unterbrochen wird, kann es auf die Ergebnisse seines
aufgerufenen Unterprogramms zugreifen und diese weiter verarbeiten, da es diese bei
der Beendigung seines aufgerufenen Unterprogramms übergeben bekommt.
Listing 2.1 zeigt einige Unterprogrammdefinitionen in Pseudocode. Das Unterpro-
gramm |a| ruft die Unterprogramme |b| und |c| auf und das Unterprogramm |b|
wiederum das Unterprogramm |d|.
Listing 2.1: Pseudocode zur Demonstration der unterschiedlichen Ausführung der her-
kömmlichen Implementierung von Unterprogrammen mit Activation Frames
und der neuen Implementierung von Unterprogrammen mit Task Frames
7
2 C with Execution System (CES)
d
b b b c
a a a a a a a
Zeit
Abbildung 2.1: Abarbeitung des Unterprogramms a des Pseudocode aus Listing 2.1
mittels der herkömmlichen Implementierung von Unterprogrammen.
Der oberste Kasten symbolisiert das zum jeweiligen Zeitpunkt laufende
Unterprogramm. Darunter befinden sich unterbrochene Unterprogram-
me.
b d
a c c c
Zeit
Abbildung 2.2: Abarbeitung des Unterprogramms a des Pseudocode aus Listing 2.1
mittels der neuen Implementierung von Unterprogrammen. Der oberste
Kasten symbolisiert das zum jeweiligen Zeitpunkt laufende Unterpro-
gramm. Darunter befinden sich gemerkte Unterprogrammaufrufe.
Abbildung 2.1 zeigt die Ausführung des Unterprogramms a des Pseudocode aus
Listing 2.1 mittels der herkömmlichen Implementierung von Unterprogrammen. Die
Ausführung des Unterprogramms a beinhaltet die Ausführung seiner aufgerufenen
Unterprogramme.
Bei der neuen Implementierung von Unterprogrammen von Steinmacher-Burow wird
bei einem Unterprogrammaufruf das aufrufende Unterprogramm hingegen nicht un-
terbrochen. Stattdessen werden die Unterprogrammaufrufe gesammelt und erst nach
dem Beenden des aufrufenden Unterprogramms in ihrer ursprünglichen Aufrufreihen-
folge durch ein Execution System (ES) ausgeführt. Im Gegensatz zur herkömmlichen
Implementierung von Unterprogrammen kann das aufrufende Unterprogramm hier
nicht auf die Ergebnisse seiner aufgerufenen Unterprogramme zugreifen und diese
daher nicht selbst weiter verarbeiten, da die Unterprogrammaufrufe nur gemerkt und
nicht sofort ausgeführt werden. Das aufrufende Unterprogramm muss daher andere
Unterprogramme mit der Aufgabe beauftragen, die Ergebnisse weiter zu verarbeiten.
Abbildung 2.2 zeigt die Ausführung des Unterprogramms a des Pseudocode aus
Listing 2.1 mittels der neuen Implementierung von Unterprogrammen von Steinmacher-
Burow. Die Ausführung des Unterprogramms a beinhaltet nicht die Ausführung seiner
8
2.1 Die neue Implementierung von Unterprogrammen
1 a(...) { ... @x ← b(...) ... @y ← h(@x) ... @z ← 42 ... c(@x, @y, @z) }
2 b(...) { ... return(...) }
3 c(x, y, z) { ... x ... y ... z ... }
4 h(x) { return(x*x) }
Listing 2.3: Bei der neuen Implementierung von Unterprogrammen kann ein Unterpro-
gramm nicht selber auf die Ergebnisse seiner aufgerufenen Unterprogramme
zugreifen. Daher müssen zusätzliche Hilfs-Unterprogramme zur Weiterver-
arbeitung der Ergebnisse verwendet werden. Parameter werden hierbei
über Referenzen auf Speicherplätze übergeben.
9
2 C with Execution System (CES)
Merken übergeben, also noch bevor diese ausgeführt werden. Gleiche Referenzen bilden
somit eine gerichtete Verbindung zwischen den gemerkten Unterprogrammaufrufen
des aufrufenden Unterprogramms. Durch die anschließende Ausführung der gesam-
melten gemerkten Unterprogrammaufrufe in ihrer ursprünglichen Reihenfolge wird
sichergestellt, dass von aufgerufenen Unterprogrammen produzierte Ergebnisse vor
der Konsumierung durch andere aufgerufene Unterprogramme vorhanden sind. Die
Referenzen und Speicherplätze werden durch das ES generiert und verwaltet.
Die Ergebnisse eines Unterprogramms sind sowohl unter Verwendung der herkömm-
lichen Implementierung von Unterprogrammen als auch unter Verwendung der neuen
Implementierung von Unterprogrammen gleich. Bei der Ausführung der neuen Imple-
mentierung von Unterprogrammen wird die herkömmliche Ausführungsreihenfolge der
Quelltextzeilen eines Unterprogramms von oben nach unten eingehalten. Bei der sequen-
tiellen Ausführung resultiert daraus die depth-first Ausführung von Unterprogrammen.
[SB00a, S. 6][SB00b, S. 74]
Die Vorteile der neuen Implementierung von Unterprogrammen für die parallele
Ausführung werden in Kapitel 3 gezeigt.
Die herkömmliche Implementierung von Unterprogrammen wird mit Hilfe eines Call
Stack von Activation Frames abgearbeitet. Ein Activation Frame enthält hierbei den
internen Zustand eines Unterprogramms (z. B. lokale Variablen, . . . ). Zu einem jeden
Zeitpunkt befindet sich das aktuell laufende Unterprogramm zu oberst auf dem Call
Stack. Bei einem Unterprogrammaufruf wird der aktuelle Zustand des aufrufenden
Unterprogramms in seinem Activation Frame gespeichert, ein neuer Activation Frame
für das aufgerufene Unterprogramm zu oberst auf dem Call Stack angelegt und zu dem
aufgerufenen Unterprogramm gewechselt. Beim Beenden des aufgerufenen Unterpro-
gramms wird sein Activation Frame wieder vom Call Stack entfernt, der alte Zustand
des aufrufenden Unterprogramms mit Hilfe des obersten Activation Frame des Call
Stack wiederhergestellt und zu diesem zurückgekehrt. Durch seine Unterbrechung beim
Unterprogrammaufruf kann ein Unterprogramm auf die Ergebnisse seiner aufgerufenen
Unterprogramme zugreifen und diese weiter verarbeiten.
Der Ablauf des Unterprogramms a aus Listing 2.1 mittels der herkömmlichen Im-
plementierung von Unterprogrammen mit Activation Frames ist in Abbildung 2.1
wiedergegeben, die den sich über der Zeit ändernden Zustand des Call Stack zeigt.
Zu Beginn befindet sich der Activation Frame des Unterprogramms a zuoberst auf
dem Call Stack und das Unterprogramm a befindet sich in der Ausführung. Als erstes
10
2.1 Die neue Implementierung von Unterprogrammen
11
2 C with Execution System (CES)
Weiterhin wird durch das Ersetzen des Task Frame eines Unterprogramms durch die
Task Frames seiner aufzurufenden Unterprogramme einerseits implizit eine Endrekursi-
onsoptimierung durchgeführt und andererseits sichergestellt, dass der Frame Stack bei
rekursiven Unterprogrammen nicht in dem Maße wächst, wie es beim Call Stack der
herkömmlichen Implementierung von Unterprogrammen der Fall wäre.
Abbildung 2.2 zeigt den Ablauf des Unterprogramms a aus Listing 2.1 mittels
der neuen Implementierung von Unterprogrammen von Steinmacher-Burow mit Task
Frames und den sich über der Zeit ändernden Zustand des Frame Stack. Zu Beginn
befindet sich der Task Frame des Unterprogramms a zuoberst auf dem Frame Stack und
es läuft das Unterprogramm a. Während seiner Laufzeit ruft es die Unterprogramme b
und c auf, wobei diese Unterprogrammaufrufe in Form von Task Frames nur gemerkt
werden. Beim Beenden des Unterprogramms a wird sein Task Frame auf dem Frame
Stack durch die gemerkten Unterprogrammaufrufe b und c ersetzt, wobei diese in ihrer
ursprünglichen Reihenfolge des Aufrufens bleiben. Somit befinden sich nun die Task
Frames der Unterprogramme b und c auf dem Frame Stack und das Unterprogramm b
läuft. Dieses ruft seinerseits das Unterprogramm d auf, welches wieder gemerkt wird
und beim Beenden des Unterprogramms b dessen Task Frame ersetzt. Daher befinden
sich nun die Task Frames der Unterprogramme d und c auf dem Frame Stack und
das Unterprogramm d läuft. Dieses ruft selbst keine weiteren Unterprogramme auf, so
dass beim Beenden sein Task Frame vom Frame Stack entfernt wird. Jetzt enthält der
Frame Stack lediglich den Task Frame des Unterprogramms c, welches auch läuft.
12
2.2 CES Syntax
Tabelle 2.1: Übersicht über die CES Unterprogramm Parametertypen und -variablen
CES Parametertyp Beschreibung
Eingabeparameter Eingabeparameter (englisch: input parame-
ters; kurz: input, in) dürfen ausschließlich
gelesen werden. Ihre Parametervariablen
dürfen nicht beschrieben werden.
Ein-/Ausgabeparameter Ein- und Ausgabeparameter (englisch: in-
put/output parameters; kurz: in-/output,
in/out) dürfen gelesen und ihre Parameter-
variablen beschrieben werden, um Werte
zurückzuliefern.
Ausgabeparameter Ausgabeparameter (englisch: output param-
eters; kurz: output, out) dürfen nicht gelesen
werden. Ihre Parametervariablen dürfen aus-
schließlich beschrieben werden, um Werte
zurückzuliefern. Beim CES Unterprogramm-
aufruf wird für diese Variablen automatisch
Speicher reserviert und auch automatich
wieder freigegeben, sobald die Gültigkeit
der Variablen endet.
13
2 C with Execution System (CES)
Bezeichner
- Buchstabe -
Buchstabe
Ziffer
_
Unterprogramm-, Typ- & CES Parametervariablenbezeichner
- Bezeichner -
Parameterdefinition
- Typbezeichner - - CES Parametervariablenbezeichner -
Parameterdefinitionsliste
-
- Parameterdefinition
,
Eingabe-, Ein-/Ausgabe- & Ausgabeparameterdefinitionsliste
- Parameterdefinitionsliste -
CES Unterprogrammdefinition
- $ - Unterprogrammbezeichner - ( - Eingabeparameterdefinitionsliste - ;
- Ein-/Ausgabeparameterdefinitionsliste - ;
- Ausgabeparameterdefinitionsliste - )
- { - C Quelltext - } - $ -
14
2.2 CES Syntax
Eingabeparameterliste
- Typbezeichner - - C Variablen- & CES Parametervariablenbezeichner -
- CES Parametervariablenbezeichner
,
Ein-/Ausgabeparameterliste
- Typbezeichner - - C Variablen- & CES Parametervariablenbezeichner -
- CES Parametervariablenbezeichner
,
Ausgabeparameterliste
- Typbezeichner - CES Parametervariablenbezeichner -
- CES Parametervariablenbezeichner
,
CES Unterprogrammaufruf
- $
- Unterprogrammbezeichner - (
- parallel -
- Eingabeparameterliste - ; - Ein-/Ausgabeparameterliste - ;
- Ausgabeparameterliste - ) - ; - $ -
CES Parametervariablenzugriff
- $ - CES Parametervariablenbezeichner - $ -
15
2 C with Execution System (CES)
$<unterprogrammbezeichner>([<eingabeparameterdefinitionsliste>];
[<ein-/ausgabeparameterdefinitionsliste>];
[<ausgabeparameterdefinitionsliste>]){
...
}$
3
Dies stellt eine aktuelle Einschränkung des CESC dar, welche lediglich zur Vereinfachung seiner
Implementierung getroffen wurde.
16
2.2 CES Syntax
1 /**
2 * @param[in] n the input n.
3 */
4 $call_by_value(int n;;){
5 int i, k;
6
7 for (i = 1; i <= $n$; ++i) {
8 $print_int(int i;;);$ /* prints the value of i (i.e. 1, 2, 3, ..., n) */
9 }
10
11 k = 2 * $n$;
12 $print_int(int k;;);$ /* prints the value of 2*n */
13
14 k += 1; /* k = 2 * $n$ + 1 */
15 $print_int(int k;;);$ /* prints the value of 2*n+1 */
16 }$
Implementierung des CESC erlaubt keine Übergabe von Konstanten oder Ausdrücken.4
C Variablen können ausschließlich als Eingabeparameter verwendet werden und müssen
mit ihrem vorangestellten Variablentypen angegeben werden.5 Bei der Verwendung als
Eingabeparameter bei Ein-/Ausgabeparametern initialisiert die C Variable lediglich
die neu erzeugte CES Parametervariable (call-by-value) und dient somit als reiner
Eingabeparameter. Bei der Übergabe von CES Parametervariablen des aufrufenden
CES Unterprogramms wird kein Variablentyp mit angegeben und es entfällt die sonst
notwendige Umschließung mit Dollarzeichen. CES Parametervariablen werden immer
call-by-reference übergeben (Referenz auf Speicherplatz; siehe Abschnitt 2.1). Neue
CES Parametervariablen für Ausgabeparameter werden wie bei der CES Unterpro-
grammdefinition mit vorangestelltem Variablentyp deklariert. Hierdurch kann der CESC
zwischen der Erzeugung einer neuen CES Parametervariablen und einer bestehenden
CES Parametervariablen unterscheiden. Das optional voranstellbare Schlüsselwort
parallel dient der Markierung des Unterprogrammaufrufs als parallel ausführbar
durch die Laufzeitumgebung und wird im Abschnitt 3.4 genauer erläutert.
In Listing 2.6 wird die call-by-value Parameterübergabe von C Variablen beim
CES Unterprogrammaufruf an einem Beispiel demonstriert. Das nicht mit angegebene
CES Unterprogramm print_int gibt den Wert des übergebenen Integer auf dem
Bildschirm aus. C Variablen initialisieren als Argument beim CES Unterprogramm-
aufruf den neu erzeugten Speicherplatz der zugehörigen CES Parametervariablen des
aufgerufenen CES Unterprogramms. Eine Veränderung des Werts der C Variablen
4
Diese Einschränkung wurde lediglich zur Vereinfachung der Implementierung des CESC getroffen.
5
Die erneute Angabe des Variablentypen bei der Verwendung von C Variablen als Parameter ist eine
Einschränkung, die zur Vereinfachung der Implementierung des CESC getroffen wurde.
17
2 C with Execution System (CES)
1 /**
2 * @param[in] n the input n.
3 * @param[out] k the output k.
4 */
5 $call_by_reference(int n;; int k){
6 $k$ = 2 * $n$;
7 $print_int(k;;);$ /* prints the value of 2*n+1 from the assignment below !!! ATTENTION !!! '
*/
8
9 $k$ += 1; /* $k$ = 2 * $n$ + 1 */
10 $print_int(k;;);$ /* prints the value of 2*n+1 */
11 }$
im Nachhinein hat somit keine Auswirkung auf die später vom ES durchgeführte
Unterprogrammausführung.
In Listing 2.7 wird entsprechend die call-by-reference Parameterübergabe von CES Pa-
rametervariablen beim CES Unterprogrammaufruf an einem Beispiel demonstriert.
CES Parametervariablen werden beim CES Unterprogrammaufruf immer in Form einer
Referenz auf den Speicherplatz der Parametervariablen übergeben. Eine Veränderung
der CES Parametervariablen im Nachhinein wirkt sich somit auf die später vom ES
durchgeführte Unterprogrammausführung aus.
In der aktuellen Implementierung von CES existiert ein spezielles CES Unterpro-
gramm program(int argc, argv_t argv;;). Dieses stellt analog zu dem C Unter-
programm int main(int argc, char ** argv) den Einstiegspunkt eines CES Pro-
gramms dar. Es bekommt die Kommandozeilenparameter wie in C über die Eingabe-
parameter int argc (Anzahl der Parameter – „argument count“) und argv_t argv
(Array der Parameter – „argument vector“) übergeben. Der benutzerdefinierte Typ
argv_t ist hierbei als typedef char ** argv_t; definiert, so dass die beiden Para-
meter vollständig kompatibel zu der Definition in C sind.
18
2.2 CES Syntax
1 /**
2 * CES Subroutine MERGESORT:
3 * Implements John von Neumann’s Mergesort algorithm.
4 *
5 * This subroutine sorts the items of size \a size of the array \a array of
6 * length \a n using the comparison function \a compare.
7 * The comparison function must be of type \a compare_t which takes two
8 * pointers to two elements to compare and returns an integer based on
9 * the result of the comparison:
10 * - If *element1 < *element2, compare should return an integer < 0.
11 * - If *element1 == *element2, compare should return 0.
12 * - If *element1 > *element2, compare should return an integer > 0.
13 *
14 * @param[in] n the number of elements.
15 * @param[in] size the size of an element (hint: sizeof()).
16 * @param[in] compare the pointer to the element comparison function of type
17 * compare_t.
18 * @param[in,out] array the array to sort.
19 */
20 $mergesort(size_t n, size_t size, compare_t compare; ptr_t array;){
21 if ($n$ <= 1) {
22 /* array has zero or one element(s) and is sorted by default */
23 } else {
24 /* divide and conquer */
25 size_t nleft = $n$ / 2;
26 size_t nright = $n$ - nleft;
27 /* allocate memory for left and right half of the array and make a copy of them */
28 ptr_t left = malloc(nleft * $size$);
29 ptr_t right = malloc(nright * $size$);
30 memcpy(left, $array$, nleft * $size$);
31 memcpy(right, $array$ + (nleft * $size$), nright * $size$);
32
33 $mergesort(size_t nleft, size, compare; ptr_t left;);$
34 $mergesort(size_t nright, size, compare; ptr_t right;);$
35 $merge(ptr_t left, size_t nleft, ptr_t right, size_t nright, size, compare; array;);$
36 }
37 }$
Listing 2.8 zeigt das typische Einstiegsbeispiel als CES Programm. Es gibt „Hello,
world!“ auf dem Bildschirm aus.
Listing 2.9 zeigt das etwas komplexere Beispiel einer Implementierung des Sor-
tieralgorithmus Mergesort6 in CES als CES Unterprogramm mergesort7 . Das vom
CES Unterprogramm mergesort verwendete CES Unterprogramm merge ist in Lis-
ting 2.10 verkürzt wiedergegeben, da seine Funktionalität, aber nicht seine genaue
Implementierung für die folgenden Erläuterungen von Bedeutung ist.
6
Der Sortieralgorithmus Mergesort wurde 1945 von John (Johann) von Neumann erstmals vorgstellt.
7
Die hier gezeigte Implementierung von Mergesort ist nicht optimal, da sie zum Aufteilen des
Array jeweils neuen Speicher reserviert und die Daten kopiert, was zu einem zusätzlichen Faktor
von mindestens O(log(n)) führt. Sie ist allerdings gegenüber der verbesserten Implementierung im
Unterabschnitt 4.5.1 (Listing 4.11, Listing 4.12 und Listing 4.13) leichter nachzuvollziehen, welche
ein einziges temporäres Hilfsarray zur Sortierung verwendet, dessen Ein- und Ausgaberolle jeweils
bei der Rekursion vertauscht wird.
19
2 C with Execution System (CES)
1 /**
2 * CES Subroutine MERGE:
3 * Merges two sorted arrays \a left and \a right into one sorted array \a array.
4 *
5 * @param[in] left the first array.
6 * @param[in] nleft the number of elements of the first array.
7 * @param[in] right the second array.
8 * @param[in] nright the number of elements of the second array.
9 * @param[in] size the size of an element (hint: sizeof()).
10 * @param[in] compare the pointer to the element comparison function of type
11 * compare_t.
12 * @param[in,out] the destination array.
13 */
14 $merge(ptr_t left, size_t nleft, ptr_t right, size_t nright, size_t size, compare_t compare; '
ptr_t array;){
15 ...
16 // merge left and right into array using compare
17 ...
18
19 /* free memory of left and right halves of the array */
20 free($left$);
21 free($right$);
22 }$
1/8
Zeit
Abbildung 2.4: Sequentielle Abarbeitung eines Programms, welches das Divide and
Conquer Konzept zur Aufteilung der Arbeit verwendet
20
2.2 CES Syntax
4 3 2 1
4 3 2 1
4 3 2 1
3 4 1 2
1 2 3 4
Abbildung 2.5: Sortierung eines Array mit vier Elementen mittels Mergesort aus
Listing 2.9
Mergesort benutzt das Konzept Divide and Conquer zur Aufteilung seiner Arbeit.
Es halbiert die zu sortierenden Daten, sortiert beide Hälften rekursiv und führt die
beiden sortierten Hälften anschließend zu einem sortierten Gesamtergebnis zusammen.
Der Algorithmus von Mergesort ist in [LP98], Kapitel 2.2, Seite 9, [Lan07] oder
[Alt06] detaillierter beschrieben.
Abbildung 2.4 zeigt die sequentielle Abarbeitung eines Programms, welches das
Konzept Divide and Conquer zur Aufteilung der Arbeit verwendet. Dies Konzept ist ein
verbreitetes Konzept der parallelen Programmierung und soll daher hier nicht weiter
erläutert werden. Abbildung 2.5 zeigt den Sortiervorgang von mergesort anhand
eines einfachen Beispiels mit vier natürlichen Zahlen, an dem auch die Anwendung des
Divide and Conquer Konzepts gut erkennbar ist.
Die Implementierung aus Listing 2.9 von Mergesort in CES wird im Folgenden als
primäres Anschauungsbeispiel für die sequentielle Ausführung von CES, die mögliche
Parallelisierung von CES und auch anschließend für die parallele Ausführung von CES
verwendet.
In Zeile 20 von Listing 2.9 wird das CES Unterprogramm mergesort mit den
Eingabeparametern n, size und compare und dem Ein-/Ausgabeparameter array
definiert. Die Parametervariablen und ihre jeweiligen Parameter- und Variablentypen
sind in Tabelle 2.2 noch einmal aufgelistet.
In den Zeilen 33 und 34 von Listing 2.9 ruft sich das CES Unterprogramm mergesort
rekursiv für die linke und rechte Hälfte des zu sortierenden Array auf. Bei der Über-
gabe der Parameter kann der Unterschied zwischen der Übergabe von C Variablen
mit Angabe des Variablentyps und der Übergabe von CES Parametervariablen gut
nachvollzogen werden. size und compare sind CES Parametervariablen des CES Un-
21
2 C with Execution System (CES)
Tabelle 2.2: Übersicht über die Parameter des CES Unterprogramms mergesort aus
Listing 2.9
CES Parametertyp Variablentyp Beschreibung
Parameter-
variable
n eingabe size_t Anzahl der Elemente des
Array
size eingabe size_t Größe eines Elements
compare eingabe compare_t Vergleichsfunktion zur Be-
stimmung der Ordnung
zweier Elemente
array ein-/ausgabe ptr_t Zeiger auf das zu sortieren-
de Array
terprogramms mergesort und werden daher ohne Variablentyp und an dieser Stelle
auch ohne Umschließung durch Dollarzeichen angegeben. nleft, nright, left und
right sind C Variablen und müssen daher mit ihrem Variablentyp angegeben werden.
Da CES Unterprogrammaufrufe nur gemerkt und nicht sofort ausgeführt werden,
kann das CES Unterprogramm mergesort an dieser Stelle nicht wie bei der herkömmli-
chen Implementierung von Unterprogrammen auf die Ergebnisse der beiden rekursiven
mergesort Aufrufe zugreifen, um die beiden sortierten Teilarrays zu einem einzel-
nen sortierten Array zu verknüpfen. Daher muss es diese Aufgabe an ein weiteres
CES Unterprogramm delegieren und ruft in der Zeile 35 von Listing 2.9 das CES Un-
terprogramm merge mit den Ein-/Ausgabeparametervariablen der vorangegangenen
mergesort Aufrufe auf.
Der CES Compiler (CESC) übersetzt CES Quelltext in C Quelltext, welcher anschlie-
ßend zusammen mit einer in C geschriebenen Laufzeitumgebung für CES, der soge-
nannten CES Runtime, mit einem handelsüblichen C Compiler zu einem ausführbaren
Programm übersetzt werden kann (siehe Abbildung 2.6).
Der CESC wurde von Sven Wagner im Rahmen seiner Diplomarbeit „Konzeption
und Entwicklung eines neuen Compiler ‚CESC‘ zur Implementierung von Prozeduren
als atomare Tasks“ [Wag07] bei der IBM Deutschland Entwicklung in Kooperation mit
der Fachhochschule Gießen-Friedberg entwickelt.
Der ursprüngliche CESC von Wagner generiert für CES direkt C Quelltext, welcher
22
2.3 CES Compiler (CESC)
Laufzeitumgebung
für CES
Abbildung 2.6: Schema der Übersetzung eines CES Programms mit dem CES Compiler
ausschließlich mit der sequentiellen Laufzeitumgebung für CES von Wagner übersetzbar
ist und keine Abstraktion zur Unterstützung unterschiedlicher Laufzeitumgebungen
enthält. Er führt selber nur minimale Syntax- und Typüberprüfungen durch und
delegiert diese Aufgabe an den C Compiler.
Ursprünglich war vorgesehen, die vom CESC für die CES Beispielprogramme dieser
Arbeit generierten C Quelltexte nach der Übersetzung falls notwendig manuell anzu-
passen oder diese gleich komplett manuell von CES nach C zu übersetzen. Aufgrund
der engen Verzahnung des vom CESC generierten C Quelltexts mit dem C Quelltext
der sequentiellen Laufzeitumgebung für CES wurde zunächst der vom CESC für die
eigenen CES Programme generierte C Quelltext als Ausgangsbasis für die Entwicklung
der Laufzeitumgebungen verwendet und während der Entwicklung manuell angepasst.
Schnell stellte sich jedoch heraus, dass diese Arbeiten in Form von Anpassungen oder
auch komplett manuellen Übersetzungen äußerst fehlerträchtig waren. Zudem musste
der C Quelltext aller Beispielprogramme bei jeder Entwicklungsstufe manuell angepasst
werden und bei Änderungen am ursprünglichen CES Quelltext gegebenenfalls sogar
komplett neu nach C übersetzt werden. Dies stellte einen nicht zu vernachlässigenden
Arbeitsaufwand dar, der sich mit den immer komplexeren CES Beispielprogrammen und
den notwendigen Änderungen aufgrund der Weiterentwicklung der Laufzeitumgebungen
für CES mit der Fehlerträchtigkeit der manuellen Arbeiten wächst.
Es wurde erkannt, dass die Codegenerierung des CESC relativ einfach an die eige-
nen Bedürfnisse angepasst werden konnte, um die vormals notwendigen manuellen
Änderungen an allen C Quelltexten der CES Beispielprogramme zentral an einer Stelle
vornehmen zu können und damit gleichzeitig das Problem der Fehlerträchtigkeit dieser
23
2 C with Execution System (CES)
Im Folgenden soll anhand des in Abschnitt 2.2 aufgeführten Beispiels von Mergesort
aus Listing 2.9 der durch den angepassten CESC generierte C Quelltext für CES Un-
terprogramme erläutert werden, insbesondere im Hinblick auf das verwendete Interface
zu den Laufzeitumgebungen für CES. Alle von der Laufzeitumgebung zu definierenden
Makros sind noch einmal im Anhang A vollständig aufgelistet und erklärt.
24
2.3 CES Compiler (CESC)
1 #include "ces_mergesort.h"
2 #include "ces_merge.h"
3 void mergesort(RUNTIME_TASK_FUNCTION_ARGUMENTS) {
4 RUNTIME_TASK_INITIALIZE(mergesort);
5
6 #ifdef CES_ANCESTOR
7 RUNTIME_TASK_ANCESTOR(mergesort)
8 #endif /* CES_ANCESTOR */
9
10 ...
11
12 /* COPY CURRENT STACK TO FRAME STACK */
13 RUNTIME_TASK_FINALIZE(mergesort);
14 }
Listing 2.11: Vom CESC generierter C Quelltext ces_mergesort.c für das CES Un-
terprogramm aus Listing 2.9
Der CESC generiert für jede *.ces CES Quelltextdatei eine ces_*.c C Quelltext-
datei, die den unveränderten C Quelltext und das für ein jedes CES Unterprogramm
generierte C Unterprogramm enthält. Zusätzlich generiert der CESC für ein jedes
CES Unterprogramm eine ces_*.h C Quelltextdatei, die eine an die jeweiligen Para-
metertypen angepasste Task Frame Struktur enthält.
Die für ein jedes CES Unterprogramm generierten C Unterprogramme weisen alle
dieselbe Schnittstelle zur Laufzeitumgebung auf und greifen zur Laufzeit über ihr
Task Frame auf ihre ursprünglichen Parameter vom CES Unterprogrammaufruf zu.
Sie enthalten zunächst einen Initialisierungsabschnitt, in dem die Laufzeitumgebung
notwendige Initialisierungsarbeiten vornehmen kann. Anschließend folgt der ansonsten
unveränderte C Quelltext aus dem CES Unterprogramm mit generiertem C Quelltext
zum Zugriff auf die CES Parametervariablen und zur Behandlung der CES Unterpro-
grammaufrufe. Zuletzt enthalten sie noch einen Finalisierungsabschnitt, in dem die
Laufzeitumgebung notwendige Finalisierungsarbeiten vornehmen kann.
Die für ein jedes CES Unterprogramm generierten angepassten Task Frame Struktu-
ren werden zur Typüberprüfung durch den C Compiler beim Zugriff auf die CES Para-
metervariablen innerhalb eines CES Unterprogramms und bei der Parameterübergabe
beim CES Unterprogrammaufruf benötigt.
Listing 2.11 zeigt den verkürzten vom CESC für das CES Unterprogramm mergesort
aus Listing 2.9 generierten C Quelltext. Zuerst werden die generierten ces_*.h
C Quelltextdateien des CES Unterprogramms (Zeile 1) und der von dem CES Un-
terprogramm aufgerufenen CES Unterprogramme (Zeile 1 und 2) eingebunden, um
die angepassten Task Frame Strukturen bekannt zu machen. Anschließend wird
das C Unterprogramm für das CES Unterprogramm mergesort definiert (Zeile 3–
25
2 C with Execution System (CES)
1 #ifndef CES_mergesort_H
2 #define CES_mergesort_H
3
4 typedef struct {
5 RUNTIME_TASK_HEADER_STRUCT_PREPARAM(mergesort, 3, 1, 0)
6 size_t *in1;
7 size_t *in2;
8 compare_t *in3;
9 ptr_t *inout1;
10 RUNTIME_TASK_HEADER_STRUCT_POSTPARAM(mergesort, 3, 1, 0)
11 } task_mergesort;
12
13 /* prototypes */
14 void mergesort(RUNTIME_TASK_FUNCTION_ARGUMENTS);
15 #ifdef CES_PRETTYPRINTER
16 void ces_print_mergesort(void);
17 #endif /* CES_PRETTYPRINTER */
18
19 #endif /* CES_mergesort_H */
Listing 2.12: Vom CESC generierter C Quelltext ces_mergesort.h für das CES Un-
terprogramm aus Listing 2.9
14). Die Parameterliste kann hierbei von der Laufzeitumgebung über das Makro
RUNTIME_TASK_FUNCTION_ARGUMENTS bestimmt werden. Als erstes erhält die Laufzeit-
umgebung die Gelegenheit, notwendige Initialisierungsarbeiten durchzuführen, indem sie
das Makro RUNTIME_TASK_INITIALIZE(...) definiert, dem der Bezeichner des CES Un-
terprogramms übergeben wird, mit dem die Laufzeitumgebung auf die angepasste Task
Frame Struktur zugreifen kann. Mit zu diesem Initialisierungsabschnitt zählt auch die
Möglichkeit, den ancestor Schalter über das Makro RUNTIME_TASK_ANCESTOR(...) zu
beeinflussen, falls das Programm anschließend mit Unterstützung des Pretty Printer
übersetzt wird. Die nun folgende Auslassung (Zeile 10) entspricht dem eigentlichen Inhalt
des ursprünglichen CES Unterprogramms. Zuletzt erhält die Laufzeitumgebung die Gele-
genheit, notwendige Finalisierungsarbeiten über das Makro RUNTIME_TASK_FINALIZE(...)
durchzuführen.
Listing 2.12 zeigt die vom CESC für das CES Unterprogramm mergesort aus Lis-
ting 2.9 generierte angepasste Task Frame Struktur task_mergesort sowie die C Unter-
programmprototypen für das vom CESC generierte C Unterprogramm mergesort. Auf
das ebenfalls vom CES generierte Pretty Printer Unterprogramm ces_print_mergesort
wird im Rahmen dieser Arbeit nicht weiter eingegangen. Die Task Frame Struktur kann
von der Laufzeitumgebung für CES über die beiden Makros RUNTIME_TASK_HEADER_STRUCT_PREPARAM(.
und RUNTIME_TASK_HEADER_STRUCT_POSTPARAM(...) beeinflusst werden, die beide den
CES Unterprogrammbezeichner sowie die Anzahl der Eingabe-, Ein-/Ausgabe und
Ausgabeparameter übergeben bekommen. Beide Informationen können von der Lauf-
26
2.3 CES Compiler (CESC)
Listing 2.13: Vom CESC generierter C Quelltext für den ersten CES Unterprogrammauf-
ruf aus Listing 2.9. Die Zeilen 1–12 entsprechen der Zeile 33 in Listing 2.9.
zeitumgebung für CES zur Typüberprüfung durch den C Compiler genutzt werden.
Zwischen den beiden Makros werden die typisierten Zeiger auf die jeweiligen CES Unter-
programmparametervariablen eines gespeicherten CES Unterprogrammaufrufs definiert.
Die Laufzeitumgebung nutzt beide Makros, um neben dem Funktionszeiger auf das
vom CES generierte C Unterprogramm auch noch weitere eigene Felder definieren zu
können und deren Position im Task Frame bestimmen zu können.
Der Aufbau der Task Frame Struktur der sequentiellen Laufzeitumgebung für CES
wird im Unterabschnitt 2.4.1 erläutert und in Abbildung 2.7 und Listing 2.16 gezeigt.
Entsprechend werden der Aufbau der Task Frame Struktur der round-robin Laufzeit-
umgebung für CES im Abschnitt 3.6 erläutert und in Abbildung 3.7 und Listing 3.7
gezeigt und der Aufbau der Task Frame Struktur der parallelen Laufzeitumgebung für
CES im Unterunterabschnitt 3.12.1.2 erläutert und in Abbildung 3.8 und Listing 3.35
gezeigt.
Im Folgenden soll anhand des in Abschnitt 2.2 aufgeführten Beispiels von Mergesort
aus Listing 2.9 der durch den angepassten CESC generierte C Quelltext für CES Un-
terprogrammaufrufe erläutert werden, insbesondere im Hinblick auf das verwendete
Interface zu den Laufzeitumgebungen für CES. Alle von der Laufzeitumgebung zu
definierenden Makros sind noch einmal im Anhang A vollständig aufgelistet und erklärt.
Bei einem CES Unterprogrammaufruf muss ein neuer Task Frame zum Merken des
Aufrufs erzeugt werden. Zusätzlich zum Task Frame müssen ebenfalls alle benötigten
CES Parametervariablen für den Aufruf erzeugt werden und die Referenzen auf diese
Parametervariablen im Task Frame vermerkt werden.
27
2 C with Execution System (CES)
Listing 2.13 zeigt den vom CESC für den im folgenden noch einmal wiedergegebenen
ersten CES Unterprogrammaufruf des CES Unterprogramms mergesort aus Listing 2.9,
Zeile 33 generierten C Quelltext:
$mergesort(size_t nleft, size, compare; ptr_t left;);$
2.3.4 Vom CES Compiler generierter C Quelltext für den Zugriff auf
CES Parametervariablen
Im Folgenden soll anhand des in Abschnitt 2.2 aufgeführten Beispiels von Mergesort
aus Listing 2.9 der durch den angepassten CESC generierte C Quelltext für den Zugriff
auf CES Parametervariablen erläutert werden, insbesondere im Hinblick auf das ver-
28
2.3 CES Compiler (CESC)
1 if (RUNTIME_TASK_PARAMIN(1) <= 1) {
2 /* array has zero or one element(s) and is sorted by default */
3 } else {
4 /* divide and conquer */
5 size_t nleft = RUNTIME_TASK_PARAMIN(1) / 2;
6 size_t nright = RUNTIME_TASK_PARAMIN(1) - nleft;
7 /* allocate memory for left and right half of the array and make a copy of them */
8 ptr_t left = malloc(nleft * RUNTIME_TASK_PARAMIN(2));
9 ptr_t right = malloc(nright * RUNTIME_TASK_PARAMIN(2));
10 memcpy(left, RUNTIME_TASK_PARAMINOUT(1), nleft * RUNTIME_TASK_PARAMIN(2));
11 memcpy(right, RUNTIME_TASK_PARAMINOUT(1) + (nleft * RUNTIME_TASK_PARAMIN(2)), nright * '
RUNTIME_TASK_PARAMIN(2));
12
13 ...
14 }
Listing 2.14: Vom CESC generierter C Quelltext für den Zugriff auf die CES Parame-
tervariablen aus Listing 2.9. Die Zeilen 1–11 entsprechen den Zeilen 21–31
in Listing 2.9.
wendete Interface zu den Laufzeitumgebungen für CES. Alle von der Laufzeitumgebung
zu definierenden Makros sind noch einmal im Anhang A vollständig aufgelistet und
erklärt.
Der Zugriff auf CES Parametervariablen muss über die im Task Frame des laufenden
CES Unterprogramms gespeicherten Referenzen erfolgen, welche auf die aktuellen
übergebenen Werte verweisen.
Listing 2.14 zeigt den vom CESC für den Zugriff auf die CES Parametervariablen des
CES Unterprogramms mergesort aus Listing 2.9 generierten C Quelltext. Der Zugriff er-
folgt mittels zweier Makros RUNTIME_TASK_PARAMIN(...) und RUNTIME_TASK_PARAMINOUT(...),
welche die Nummer des CES Parameters übergeben bekommen. Die Nummern werden
den Parametern vom CESC in der Reihenfolge vergeben, wie sie bei der CES Unter-
programmdefinition angegeben wurden (siehe Listing 2.12). Die Laufzeitumgebung für
CES muss die Makros so definieren, dass der Zugriff auf die aktuellen Werte über den
29
2 C with Execution System (CES)
90 /**
91 * Task Execution Loop
92 *
93 * Executes the topmost task on the frame stack in a loop until stack is empty.
94 *
95 * @author Sven Wagner \<SVENWAGN@de.ibm.com\>
96 * @author Jens Remus \<JREMUS@de.ibm.com\>
97 */
98 void exec_top_of_fs() {
99 while(fsp >= frame_stack) {
100 #ifdef CES_PRETTYPRINTER
101 if(fsp->ancestor == 0 && fsp->fnptr_task != func_storage) {
102 printf("\t*****************EXECUTE %d****************\n", count_executes);
103 show_fs();
104 #else /* not CES_PRETTYPRINTER */
105 if (fsp->fnptr_task != func_storage) {
106 #endif /* CES_PRETTYPRINTER */
107 fsp->fnptr_task(&fsp, csp); /* execute task */
108 count_executes++;
109 } else {
110 fsp -= 1; /* set frame stack pointer to next task */
111 }
112 }
113 }
Listing 2.15: Schleife zur sequentiellen Abarbeitung des jeweils obersten Task Frame
aus run_time.c der sequentiellen Laufzeitumgebung für CES
Die sequentielle Laufzeitumgebung für CES (Sequential CES Runtime) arbeitet die
Task Frames eines einzelnen Frame Stack, wie es der Name bereits andeutet, in
sequentieller Reihenfolge ab. Die ursprüngliche sequentielle Laufzeitumgebung wurde
wie auch der CESC ebenfalls von Sven Wagner im Rahmen seiner Diplomarbeit [Wag07]
entwickelt. Sie wurde im Rahmen dieser Arbeit weiterentwickelt und diente ferner als
Ausgangspunkt für die Entwicklung eines Zwischenschritts sowie des Kernstücks dieser
Arbeit, der round-robin Laufzeitumgebung für CES (Round-Robin CES Runtime) und
der parallelen Laufzeitumgebung für CES (Parallel CES Runtime).
Die sequentielle Laufzeitumgebung enthält einen sogenannten Pretty Printer Modus,
in dem sie den CES Programmablauf auf der Konsole aufbereitet ausgibt. Dies kann
einerseits zur Fehlersuche und -behebung (Debugging) genutzt werden und andererseits
30
2.4 Sequentielle Laufzeitumgebung für CES (Sequential CES Runtime)
Die sequentielle Laufzeitumgebung besitzt den erwähnten Frame Stack, der den aktuel-
len Zustand des laufenden CES Programms enthält, sowie einen sogenannten Current
Stack, der vom aktuell laufenden CES Unterprogramm zum Merken seiner CES Un-
terprogrammaufrufe genutzt wird. Das laufende CES Unterprogramm kopiert seine
gemerkten CES Unterprogrammaufrufe beim Beenden auf den Frame Stack, so dass der
zuerst gemerkte CES Unterprogrammaufruf zuoberst auf dem Frame Stack liegt und
der zuletzt gemerkte CES Unterprogrammaufruf seinen eigenen Task Frame ersetzt
(siehe Abbildung 2.9).
0 31
Abbildung 2.7 zeigt den Aufbau der Task Frames der sequentiellen Laufzeitum-
gebung für CES. Die Felder des Task Frame lassen sich hierbei in zwei Kategorien
einteilen: „Task“ und „Pretty Printer“. Die Felder der „Task“ werden zur Ausführung
31
2 C with Execution System (CES)
des CES Unterprogramms benötigt, die Felder des „Pretty Printer“ ausschließlich zur
Visualisierung des CES Programmablaufs durch den Pretty Printer.
Zu den Feldern der „Task“ gehören ein Funktionszeiger auf das C Unterprogramm,
das vom CESC für ein jedes CES Unterprogramm generiert wird, sowie eine Liste von
Zeigern auf die jeweiligen CES Parametervariablen des Aufrufs des CES Unterpro-
gramms.
Zu den Feldern des „Pretty Printer“ gehören ein Funktionszeiger auf das sogenannte
Pretty Printer Unterprogramm, außerdem eine Zeichenkette, in der der Name des
aufzurufenden CES Unterprogramms abgelegt wird, sowie zwei interne Schalter ancestor
und parallel. Das Pretty Printer Unterprogramm wird ebenfalls vom CESC für ein
jedes CES Unterprogramm generiert und von der Laufzeitumgebung zur Erzeugung der
Pretty Printer Ausgabe aufgerufen. Der ancestor Schalter dient dazu, eine erweiterte
Ausgabe des Pretty Printer zu erhalten, welche die Nachverfolgung der Erzeugungskette
der CES Unterprogrammaufrufe (Task Frames) auf dem Frame Stack ermöglicht.
[Wag07, S. 30f] Der parallel Schalter hat bei der sequentiellen Laufzeitumgebung keinen
Einfluss auf die Ausführung, sondern wird lediglich in der Ausgabe vom Pretty Printer
berücksichtigt und deshalb den Feldern des Pretty Printer zugeordnet.
Listing 2.16 zeigt die Definition des Task Frame aus Abbildung 2.7 in C aus run_
time.h.
Das C Unterprogramm wird vom CESC für jedes CES Unterprogramm generiert
und enthält die Funktionalität des jeweiligen CES Unterprogramms. Es bekommt
bei der Ausführung durch die Laufzeitumgebung einen Zeiger auf seinen Task Frame
auf dem Frame Stack fsp und einen Zeiger auf den zu verwendenden Current Stack
csp übergeben. Über seinen Task Frame kann es so auf seine aktuellen Eingabe-,
Ein-/Ausgabe und Ausgabe-Parametervariablen zugreifen.
32
2.4 Sequentielle Laufzeitumgebung für CES (Sequential CES Runtime)
1 /**
2 * CES Task Procedure of Storage Frames.
3 *
4 * This dummy task only decrements the task frame stack pointer to the next task
5 * frame to jump over the storage frame.
6 * @author Sven Wagner \<SVENWAGN@de.ibm.com\>
7 * @author Jens Remus \<JREMUS@de.ibm.com\>
8 *
9 * @param[in,out] fsp the task frame stack pointer.
10 * @param[in] csp the current task frame stack pointer.
11 */
12 void func_storage(TASK_FRAME ** fsp, TASK_FRAME * csp) {
13 (*fsp) -= 1;
14 }
Neben den Task Frames werden auch sogenannte Storage Frames auf dem Frame Stack
abgelegt, welche zur Speicherung der CES Parametervariablen verwendet werden. Die
Storage Frames sind insofern vom Aufbau her kompatibel zum Aufbau der Task Frames,
als dass sie an derselben Position einen Funktionszeiger auf ein C Unterprogramm
enthalten. Dieser verweist auf ein statisches Storage Unterprogramm, welches keine
wirkliche Funktionalität enthält und bei der Ausführung lediglich seinen Storage Frame
vom Frame Stack entfernt.
Listing 2.17 zeigt den Quelltext des Storage Unterprogramms, wie es in allen drei
Laufzeitumgebungen für CES definiert ist.
0 31
o
Funktionszeiger auf Unterprogramm Task
o
Zeiger auf Namenszeichenkette Pretty Printer
Variable
hhhh
hhhhhhhhh
h
hhh h hh
hhhh
hhhh hh h
hhhh h Storage
hhhhhhhhh
hhhh h
hhhhhhh
hhhh
h
33
2 C with Execution System (CES)
1 typedef struct {
2 void (*fnptr_storage)(TASK_FRAME ** /* fsp */, TASK_FRAME * /* csp */);
3 #ifdef CES_PRETTYPRINTER
4 char *name;
5 #endif /* CES_PRETTYPRINTER */
6 int value __attribute__ ((aligned (8)));
7 } STORAGE_FRAME;
Abbildung 2.8 zeigt den Aufbau der Storage Frames der sequentiellen Laufzeitumge-
bung für CES. Die Felder des Storage Frames lassen sich hierbei in drei Kategorien
einteilen: „Task“, „Pretty Printer“ und „Storage“. Die Felder der „Task“ werden zur
Kompatibilität mit den Task Frames benötigt, die Felder des „Pretty Printer“ aus-
schließlich zur Visualisierung des CES Programmablaufs durch den Pretty Printer und
die Felder des „Storage“ zur Speicherung einer CES Parametervariablen.
Zu den Feldern der „Task“ gehört der Funktionszeiger auf das Unterprogramm (hier
auf das Storage Unterprogramm), das aus Kompatibilitätsgründen mit den Task Frames
benötigt wird.
Zu den Feldern des „Pretty Printer“ gehört eine Zeichenkette, in der der Name der
CES Parametervariablen abgelegt wird, sowie die als Platzhalter definierte Variable.
Zu den Feldern des „Storage“ gehört die Variable vom Typ des zu speicherenden
CES Parameters. In der Definition der Struktur (siehe Listing 2.18) innerhalb der
Laufzeitumgebung ist der Typ der Variablen mit int als Platzhalter für den Pretty
Printer angegeben. Bei der Erzeugung von Storage Frames und beim Zugriff auf die
Variable durch die Unterprogramme wird jedoch eine andere, vom CESC für den
jeweiligen CES Parameter generierte Struktur mit dem wirklichen Typ des CES-
Parameters zur Typüberprüfung verwendet.
Listing 2.18 zeigt die Definition des Storage Frame aus Abbildung 2.8 in C aus
run_time.h. Die Ausrichtung (englisch: alignment der Variablen value an einer 8 Byte
Grenze dient lediglich dazu, dass die Variable unabhängig vom letztendlichen Typ
immer an der gleichen Stelle innerhalb der Struktur gespeichert wird, damit der Pretty
Printer auf diese unabhängig vom Typ zugreifen kann.
Im Folgenden soll anhand des in Abschnitt 2.2 aufgeführten Beispiels von Mergesort
aus Listing 2.9 die sequentielle Ausführung eines CES-Programms erläutert werden.
34
2.4 Sequentielle Laufzeitumgebung für CES (Sequential CES Runtime)
mergesort(n/4, size,
compare; left1 ;)
mergesort(n/4, size,
compare; right1 ;)
mergesort(n/2, size, merge(n/4, left1 , n/4, right1 ,
compare; left0 ;) size, compare; left0 ;)
mergesort(n/2, size, mergesort(n/2, size,
compare; right0 ;) compare; right0 ;)
mergesort(n, size, merge(n/2, left0 , n/2, right0 , merge(n/2, left, n/2, right,
compare; array;) size, compare; array;) size, compare; array;)
mergesort(n/4, size,
compare; right1 ;)
merge(n/4, left1 , n/4, right1 , merge(n/4, left1 , n/4, right1 ,
size, compare; left0 ;) size, compare; left0 ;)
mergesort(n/2, size, mergesort(n/2, size, mergesort(n/2, size,
compare; right0 ;) compare; right0 ;) compare; right0 ;)
merge(n/2, left0 , n/2, right0 , merge(n/2, left0 , n/2, right0 , merge(n/2, left0 , n/2, right0 ,
size, compare; array;) size, compare; array;) size, compare; array;)
mergesort(n/4, size,
compare; left2 ;)
mergesort(n/4, size, mergesort(n/4, size,
compare; right2 ;) compare; right2 ;)
merge(n/4, left2 , n/4, right2 , merge(n/4, left2 , n/4, right2 , merge(n/4, left2 , n/4, right2 ,
size, compare; right0 ;) size, compare; right0 ;) size, compare; right0 ;)
merge(n/2, left0 , n/2, right0 , merge(n/2, left0 , n/2, right0 , merge(n/2, left0 , n/2, right0 ,
size, compare; array;) size, compare; array;) size, compare; array;)
(j) t9
Abbildung 2.9: Sequentielle Ausführung eines CES Programms auf einem Prozessor am
Beispiel von Mergesort aus Listing 2.9. Die zugehörige Bildschirm-
ausgabe des Pretty Printer der sequentiellen Laufzeitumgebung für
CES ist in Listing 2.19 wiedergegeben.
35
2 C with Execution System (CES)
array
left0 right0
Abbildung 2.10: Rekursive Aufteilung des zu sortierenden Array mit vier Elementen
bei der Ausführung von Mergesort aus Listing 2.9
Abbildung 2.9 zeigt einen Ausschnitt der sequentiellen Ausführung des CES Un-
terprogramms mergesort aus Listing 2.9. Die bereits vorhandenen und die von den
ablaufenden CES Unterprogrammen erzeugten Storage Frames für die CES Parame-
tervariablen werden in diesem Beispiel nicht berücksichtigt. Das Array enthält initial
nur vier zu sortierende Elemente, so dass ein Teilarray mit n/4 der Elemente nur ein
Element enthält und damit per Definition bereits sortiert ist. Die Zeitschritte zeigen
jeweils den Zustand des Frame Stack zu Beginn der Abarbeitung des jeweiligen obersten
Task Frame. Abbildung 2.10 verdeutlicht die während der Laufzeit erfolgende rekursive
Aufteilung des zu sortierenden Array.
Zum Zeitpunkt 2.9a wird das initiale CES Unterprogramm mergesort ausgeführt.
Gemäß der Definition von Mergesort aus Listing 2.9 teilt es das zu sortierende Array
array mit n Elementen in zwei Hälften left und right mit jeweils n/2 Elementen,
welche jeweils rekursiv mittels mergesort sortiert werden sollen und anschließend
mittels merge wieder zu dem nun sortierten Array array zusammengefügt werden
sollen. Zum Zeitpunkt 2.9b hat diese Aufteilung bereits stattgefunden, der Task Frame
des initialen mergesort CES Unterprogrammaufrufs wurde bereits durch dessen neu
erzeugte Task Frames für seine CES Unterprogrammaufrufe ersetzt und es erfolgt
erneut eine Aufteilung des linken Teilarray in zwei Hälften mit n/4 Elementen. Im
Zeitschritt 2.9c wird erstmals ein Teilarray mit nur einem Element (n/4 = 1) verarbeitet,
kann nicht weiter aufgeteilt werden und wird daher als bereits sortiertes Teilarray
zurückgeliefert. Im Zeitschritt 2.9d wird abermals ein solches einelementiges Teilarray
verarbeitet und als bereits sortiert zurückgeliefert. Im Zeitschritt 2.9e werden nun diese
beiden sortierten Teilarrays zu dem sortierten linken Teilarray mit zwei Elementen
zusammengefasst. Entsprechend wird in den Zeitschritten 2.9f–2.9i das rechte Teilarray
sortiert. Im letzten Zeitschritt 2.9j werden dann die beiden sortierten Teilarrays left
und right zu dem sortierten Array array zusammengefasst.
36
2.4 Sequentielle Laufzeitumgebung für CES (Sequential CES Runtime)
Abbildung 2.5 zeigt den Sortiervorgang von Mergesort aus Listing 2.9, wie er
während der Ausführung stattfindet, wie in Abbildung 2.9 und Listing 2.19 gezeigt.
Listing 2.19 zeigt analog zu Abbildung 2.9 die reduzierte Bildschirmausgabe des
Pretty Printer der sequentiellen Laufzeitumgebung für CES für die Ausführung von
Mergesort aus Listing 2.9. Zur Vereinfachung wurden folgende Details verändert:
die beiden Parameter size und compare wurden durch ... ersetzt und die Varia-
blentypen der CES Parametervariablen sowie die Speicheradressen der Arrays wurden
entfernt. Einziger Unterschied zu Abbildung 2.9 ist das initiale CES Unterprogramm
program (siehe Abschnitt 2.2), das in der aktuellen Implementierung von CES den
Programmeinstigspunkt darstellt und hier das CES Unterprogramm mergesort aufruft.
Anschließend ist die Ausführung identisch mit der Abbildung 2.9. Die Nummer links
neben den CES Unterprogrammbezeichnern stellt die Position des Task Frame auf
dem Frame Stack dar. Die Sprünge in der Positionsnummer der Task Frames ergibt
sich durch die Storage Frames, welche auch auf dem Frame Stack abgelegt werden.
CES Parametervariablen wird die Positionsnummer ihres Storage Frame angehängt
und ihr aktueller Wert wird hinter dem Gleichheitszeichen mit ausgegeben.
37
2 C with Execution System (CES)
1 *****************EXECUTE 1****************
2 1 program(int argc, char** argv; ;)
3 *****************EXECUTE 2****************
4 5 mergesort(n_1=4, ...; array_4;)
5 *****************EXECUTE 3****************
6 11 mergesort(nleft_5=2, ...; left_6;)
7 10 mergesort(nright_7=2, ...; right_8;)
8 9 merge(left_6, nleft_5=2, right_8, nright_7=2, ...; array_4;)
9 *****************EXECUTE 4****************
10 17 mergesort(nleft_11=1, ...; left_12;)
11 16 mergesort(nright_13=1, ...; right_14;)
12 15 merge(left_12, nleft_11=1, right_14, nright_13=1, ...; left_6;)
13 10 mergesort(nright_7=2, ...; right_8;)
14 9 merge(left_6, nleft_5=2, right_8, nright_7=2, ...; array_4;)
15 *****************EXECUTE 5****************
16 16 mergesort(nright_13=1, ...; right_14;)
17 15 merge(left_12, nleft_11=1, right_14, nright_13=1, ...; left_6;)
18 10 mergesort(nright_7=2, ...; right_8;)
19 9 merge(left_6, nleft_5=2, right_8, nright_7=2, ...; array_4;)
20 *****************EXECUTE 6****************
21 15 merge(left_12, nleft_11=1, right_14, nright_13=1, ...; left_6;)
22 10 mergesort(nright_7=2, ...; right_8;)
23 9 merge(left_6, nleft_5=2, right_8, nright_7=2, ...; array_4;)
24 *****************EXECUTE 7****************
25 10 mergesort(nright_7=2, ...; right_8;)
26 9 merge(left_6, nleft_5=2, right_8, nright_7=2, ...; array_4;)
27 *****************EXECUTE 8****************
28 16 mergesort(nleft_10=1, ...; left_11;)
29 15 mergesort(nright_12=1, ...; right_13;)
30 14 merge(left_11, nleft_10=1, right_13, nright_12=1, ...; right_8;)
31 9 merge(left_6, nleft_5=2, right_8, nright_7=2, ...; array_4;)
32 *****************EXECUTE 9****************
33 15 mergesort(nright_12=1, ...; right_13;)
34 14 merge(left_11, nleft_10=1, right_13, nright_12=1, ...; right_8;)
35 9 merge(left_6, nleft_5=2, right_8, nright_7=2, ...; array_4;)
36 *****************EXECUTE 10****************
37 14 merge(left_11, nleft_10=1, right_13, nright_12=1, ...; right_8;)
38 9 merge(left_6, nleft_5=2, right_8, nright_7=2, ...; array_4;)
39 *****************EXECUTE 11****************
40 9 merge(left_6, nleft_5=2, right_8, nright_7=2, ...; array_4;)
Listing 2.19: Reduzierte Bildschirmausgabe des Pretty Printer während der sequentiel-
len Ausführung von Mergesort aus Listing 2.9. Die zugehörige aufberei-
tete Darstellung ist in Abbildung 2.9 gezeigt.
38
3 Parallele Laufzeitumgebung für CES
(Parallel CES Runtime)
39
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
40
3.4 Markierung parallel ausführbarer CES Unterprogrammaufrufe
so wird schnell deutlich, dass die beiden rekursiven CES Unterprogrammaufrufe von
mergesort parallel ausgeführt werden dürfen, da sie beide voneinander unabhängig
sind.
Abbildung 3.1 zeigt die Datenflussabhängigkeiten der CES Unterprogrammaufrufe
des CES Unterprogramms mergesort aus Listing 2.9. Der erste Aufruf von mergesort
arbeitet auf gegenüber dem zweiten Aufruf von mergesort unabhängigen Daten (left
und right). Keiner der beiden Unterprogrammaufrufe erzeugt Daten, die vom jeweils
anderen Unterprogrammaufruf konsumiert werden. Damit sind beide mergesort Unter-
programmaufrufe voneinander unabhängig und dürfen somit parallel ausgeführt werden.
Der folgende merge Unterprogrammaufruf konsumiert die produzierten Daten (left
und right) der beiden mergesort Unterprogrammaufrufe (im Datenflussgraphen durch
beschriftete Pfeile dargestellt), ist somit abhängig von den beiden Aufrufen und darf
daher erst im Anschluss an diese ausgeführt werden. Der merge Unterprogrammaufruf
darf also nicht parallel zu den beiden mergesort Unterprogrammaufrufen ausgeführt
werden.
mergesort(n/2, size, mergesort(n/2, size,
compare; left;) compare; right;)
left right
Wie man sieht, steht die Information, welche Unterprogrammaufrufe eines Unterpro-
gramms parallel zueinander ausgeführt werden dürfen, bereits zur Übersetzungszeit
des Unterprogramms fest und ist ausschließlich von den Datenflussabhängigkeiten der
Unterprogrammaufrufe innerhalb des Unterprogramms abhängig. Somit kann diese
Information zur Übersetzungszeit als statischer Hinweis an die Unterprogrammaufrufe
gebunden werden.
41
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
1 /**
2 * @param[in] n the input n.
3 * @param[out] result the output result.
4 */
5 $allowed(int n; int result){
6 int even, odd;
7
8 even = 2 * $n$;
9 odd = 2 * $n$ + 1;
10
11 $parallel foo(int even;; int result1);$ /* OK: all data dependencies are satisfied */
12 $parallel bar(int odd;; int result2);$ /* OK: all data dependencies are satisfied */
13
14 $add_int(result1, result2;; result);$
15 }$
1 /**
2 * @param[in] n the input n.
3 * @param[out] result the output result.
4 */
5 $forbidden(int n; int result){
6 $calc_oddeven(n;; int even, int odd);$
7
8 $parallel foo(even;; int result1);$ /* ERROR: data dependency for "even" is unsatisfied */
9 $parallel bar(odd;; int result2);$ /* ERROR: data dependency for "odd" is unsatisfied */
10
11 $add_int(result1, result2;; result);$
12 }$
42
3.5 Parallele Ausführung von CES Programmen mittels Cop/Thief Work-Stealing
Abbildung 3.2 zeigt einen Ausschnitt aus Abbildung 2.9 der sequentiellen Ausführung
von Mergesort aus Listing 3.3 mit den als parallel abarbeitbar markierten Task
Frames. Die hierzu passende Bildschirmausgabe des Pretty Printer der sequentiellen
Laufzeitumgebung für CES ist in Listing 3.4 gezeigt. Die rekursive Aufteilung des Array
kann anhand der Abbildung 2.10 nachvollzogen werden. left1 und right1 stellen die
aufgeteilte ursprüngliche Hälfte left0 dar und können daher parallel zur anderen Hälfte
right0 sortiert werden.
3.5.1 Work-Stealing
Beim Konzept des Work-Stealing zur Verteilung von Arbeit auf mehrere Prozessoren
versucht ein Prozessor oder Thread, welcher keine Arbeit mehr hat, Arbeit von benach-
barten Prozessoren oder Threads zu stehlen. Neben Work-Stealing existieren auch noch
eine Reihe von anderen Konzepten zur Verteilung von Arbeit auf mehrere Prozessoren.
Beim Konzept des Work-Sharing versucht ein jeder Prozessor oder Thread einen
Teil seiner Arbeit auf seine benachbarten Prozessoren oder Threads zu verteilen.
Nach Blumofe und Leiserson [BL99, S. 2] ist Work-Stealing jedoch dem Work-Sharing
überlegen, da die Anzahl der Stehl-Vorgänge beim Work-Stealing relativ gering ist und
damit der Kommunikationsoverhead und damit auch der Synchronisationsoverhead
zwischen den Prozessoren oder Threads gegenüber dem Work-Sharing wesentlich
niedriger ist.
Eine weitere Möglichkeit der Arbeitsverteilung wäre das Konzept des Master/Slave,
bei dem ein Prozessor oder Thread die Funktion des Master übernimmt und die Arbeit
auf die restlichen Prozessoren oder Threads verteilt, welche die Funktion der Slaves
übernehmen. Im Gegensatz zum Konzept des Work-Stealing oder des Work-Sharing
sind hierbei jedoch nicht alle Prozessoren oder Threads gleichberechtigt, sondern
übernehmen eine ihnen zugeordnete spezielle Funktion. Der Prozessor oder Thread,
43
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
1 /**
2 * CES Subroutine MERGESORT:
3 * Implements John von Neumann’s Mergesort algorithm.
4 *
5 * ...
6 *
7 * @param[in] n the number of elements.
8 * @param[in] size the size of an element (hint: sizeof()).
9 * @param[in] compare the pointer to the element comparison function of type
10 * compare_t.
11 * @param[in,out] array the array to sort.
12 */
13 $mergesort(size_t n, size_t size, compare_t compare; ptr_t array;){
14 if ($n$ <= 1) {
15 /* array has zero or one element(s) and is sorted by default */
16 } else {
17 /* divide and conquer */
18 ...
19
20 $parallel mergesort(size_t nleft, size, compare; ptr_t left;);$
21 $parallel mergesort(size_t nright, size, compare; ptr_t right;);$
22 $merge(ptr_t left, size_t nleft, ptr_t right, size_t nright, size, compare; array;);$
23 }
24 }$
mergesort(n/4, size,
compare; left1 ;)
mergesort(n/4, size,
compare; right1 ;)
mergesort(n/2, size, merge(n/4, left1 , n/4, right1 ,
compare; left0 ;) size, compare; left0 ;)
mergesort(n/2, size, mergesort(n/2, size,
compare; right0 ;) compare; right0 ;)
mergesort(n, size, merge(n/2, left0 , n/2, right0 , merge(n/2, left0 , n/2, right0 ,
compare; array;) size, compare; array;) size, compare; array;)
Task Frame, der sich in Bearbeitung durch den Prozessor seines Frame Stack
befindet
Task Frame, der als sequentiell zu bearbeiten markiert ist und dessen Daten-
abhängigkeiten unbestimmt sind
Task Frame, der als parallel bearbeitbar markiert ist und dessen Datenab-
hängigkeiten somit bereits erfüllt sind
Abbildung 3.2: Ausschnitt der sequentiellen Ausführung von Mergesort aus Abbil-
dung 2.9 mit hervorgehobenen als parallel ausführbar markierten Task
Frames
44
3.5 Parallele Ausführung von CES Programmen mittels Cop/Thief Work-Stealing
1 *****************EXECUTE 1****************
2 1 program(int argc, char** argv; ;)
3 *****************EXECUTE 2****************
4 5 mergesort(n_1=4, ...; array_4;)
5 *****************EXECUTE 3****************
6 11 mergesort(nleft_5=2, ...; left_6;) PARALLEL
7 10 mergesort(nright_7=2, ...; right_8;) PARALLEL
8 9 merge(left_6, nleft_5=2, right_8, nright_7=2, ...; array_4;)
9 *****************EXECUTE 4****************
10 17 mergesort(nleft_11=1, ...; left_12;) PARALLEL
11 16 mergesort(nright_13=1, ...; right_14;) PARALLEL
12 15 merge(left_12, nleft_11=1, right_14, nright_13=1, ...; left_6;)
13 10 mergesort(nright_7=2, ...; right_8;) PARALLEL
14 9 merge(left_6, nleft_5=2, right_8, nright_7=2, ...; array_4;)
15 *****************EXECUTE 5****************
16 16 mergesort(nright_13=1, ...; right_14;) PARALLEL
17 15 merge(left_12, nleft_11=1, right_14, nright_13=1, ...; left_6;)
18 10 mergesort(nright_7=2, ...; right_8;) PARALLEL
19 9 merge(left_6, nleft_5=2, right_8, nright_7=2, ...; array_4;)
20 *****************EXECUTE 6****************
21 15 merge(left_12, nleft_11=1, right_14, nright_13=1, ...; left_6;)
22 10 mergesort(nright_7=2, ...; right_8;) PARALLEL
23 9 merge(left_6, nleft_5=2, right_8, nright_7=2, ...; array_4;)
24 *****************EXECUTE 7****************
25 10 mergesort(nright_7=2, ...; right_8;) PARALLEL
26 9 merge(left_6, nleft_5=2, right_8, nright_7=2, ...; array_4;)
27 *****************EXECUTE 8****************
28 16 mergesort(nleft_10=1, ...; left_11;) PARALLEL
29 15 mergesort(nright_12=1, ...; right_13;) PARALLEL
30 14 merge(left_11, nleft_10=1, right_13, nright_12=1, ...; right_8;)
31 9 merge(left_6, nleft_5=2, right_8, nright_7=2, ...; array_4;)
32 *****************EXECUTE 9****************
33 15 mergesort(nright_12=1, ...; right_13;) PARALLEL
34 14 merge(left_11, nleft_10=1, right_13, nright_12=1, ...; right_8;)
35 9 merge(left_6, nleft_5=2, right_8, nright_7=2, ...; array_4;)
36 *****************EXECUTE 10****************
37 14 merge(left_11, nleft_10=1, right_13, nright_12=1, ...; right_8;)
38 9 merge(left_6, nleft_5=2, right_8, nright_7=2, ...; array_4;)
39 *****************EXECUTE 11****************
40 9 merge(left_6, nleft_5=2, right_8, nright_7=2, ...; array_4;)
Listing 3.4: Reduzierte Bildschirmausgabe des Pretty Printer während der sequentiellen
Ausführung von Mergesort aus Listing 3.3
45
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
der die Funktion des Master übernimmt, kann selbst nicht direkt zur Abarbeitung der
Arbeit beitragen. Er muss weiterhin für eine möglichst gleichmäßige Auslastung der
Slaves Sorge tragen. Dies ist insbesondere bei Arbeitspaketen mit unterschiedlichem
Aufwand schwierig. Auch muss der Prozessor oder Thread performant genug sein, um
nicht einen Flaschenhals darzustellen, wenn er nicht mit der Arbeitsverteilung an die
Slaves hinterherkommt, weil diese die Arbeit schneller abarbeiten als der Master sie
ihnen zuteilen kann.
Beim klassischen Work-Stealing besitzt jeder Prozessor oder Thread eine eigene War-
teschlange von Arbeitspaketen, welche er der Reihenfolge nach abarbeitet. Üblicherweise
arbeitet er seine Warteschlange von oben nach unten ab. Neue Arbeitspakete, welche
einerseits durch sein in der Abarbeitung befindliches Arbeitspaket erzeugt werden und
andererseits von ihm von benachbarten Prozessoren gestohlen werden, fügt er von oben
in seine Warteschlange ein. Daraus folgt die in Abschnitt 2.1 erwähnte sequentielle
depth-first Ausführung. Benachbarte Prozessoren stehlen seine Arbeit immer von unten
aus seiner Warteschlange, woraus eine parallele breadth-first Ausführung folgt. Das
Stehlen von unten aus der Warteschlange benachbarter Prozessoren oder Threads soll
die Wahrscheinlichkeit erhöhen, dass möglichst umfangreiche Arbeitspakete gestohlen
werden, wie es beispielsweise bei divide and conquer Algorithmen der Fall ist. Der
Prozessor oder Thread stiehlt immer dann Arbeit von benachbarten Prozessoren oder
Threads, wenn seine Warteschlange leer ist.
1/8
1 1/2 1/4 1/2 1/8 1/4 1/4 1/8 1/4 1/8 1/4 1/8 1/4 1/8 1/8
A B C A B C A B C A B C A B C A B C A B C A B C A B C
t0 t1 t2 t3 t4 t5 t6 t7 t8
B ← A C ← A A ← B B ← A
Zeit
Abbildung 3.3: Parallele Abarbeitung des Programms aus Abbildung 2.4, welches das
Divide and Conquer Konzept zur Aufteilung der Arbeit verwendet, mit
drei Prozessoren (A, B und C) unter Verwendung des Work-Stealing
Konzepts. X ← Y bedeutet, dass Prozessor X von Prozessor Y bestohlen
wird.
Abbildung 3.3 zeigt die parallele Abarbeitung des Programms aus Abbildung 2.4
mit drei Prozessoren (A, B und C) unter Verwendung des Konzepts des Work-Stealing.
Prozessor B stiehlt zum Zeitpunkt t1 von Prozessor A ein Arbeitspaket. Im darauf
46
3.5 Parallele Ausführung von CES Programmen mittels Cop/Thief Work-Stealing
Cop (Polizist) Der Cop dient dazu, seinem zugehörigen Thief die vollständige Abar-
beitung des auszuführenden Unterprogramms des zugehörigen gestohlenen Task
Frame und dessen aufgerufenen Unterprogrammen zu signalisieren. Für jeden
47
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
COPi Frame muss immer auch ein zugehöriger T HIEFi Frame existieren.
Thief (Dieb) Der Thief dient dazu, solange die sequentielle Abarbeitung über seinen
T HIEF Frame des lokalen Frame Stack hinaus zu blockieren, wie das auszufüh-
rende Unterprogramm seines zugehörigen gestohlenen Task Frame und dessen
aufgerufene Unterprogramme noch nicht vollständig abgearbeitet wurden. Derweil
versucht der Thief, als parallel markierte Task Frames von den Frame Stacks
der anderen Prozessoren zu stehlen. Sein zugehöriger Cop signalisiert ihm die
vollständige Abarbeitung seines zugehörigen gestohlenen Task Frame. Für jeden
T HIEFi Frame muss immer auch ein zugehöriger COPi Frame existieren.
Stößt ein Prozessor im Rahmen der sequentiellen Abarbeitung seines Frame Stack
auf einen T HIEFi Frame, so wird ein spezielles Thief Unterprogramm (Thief) aus-
geführt. Dieses versucht so lange als parallel abarbeitbar markierte Task Frames von
unten von den Frame Stacks der anderen Prozessoren zu stehlen, bis es das Signal
seines zugehörigen COPi erhält, dass sein zugehöriger, durch seinen T HIEFi Frame
durch einen anderen Thief ersetzter gestohlener Task Frame vollständig abgearbeitet
wurde. Dadurch blockiert der Thief mit seinem T HIEFi Frame die weitere sequentielle
Abarbeitung des Frame Stack seines Prozessors über seinen Frame hinaus, bis sein
zugehöriger gestohlener Task Frame abgearbeitet wurde. Damit stellt er die korrekte
Einhaltung der Datenflussabhängigkeiten der darunterliegenden, als sequentiell auszu-
führen markierten Task Frames sicher, welche von den ursprünglich darüberliegenden
Task Frames abhängen.
Zum Stehlen wählt der Thief einen anderen Prozessor als Ziel aus, von dem er
einen als parallel abarbeitbar markierten Task Frame stehlen möchte und beginnt den
fremden Frame Stack von unten nach oben nach als parallel abarbeitbar markierten
Task Frames zu scannen. Findet der Thief auf dem fremden Frame Stack keinen als
parallel abarbeitbar markierten Task Frame, so wählt er einen neuen Prozessor als
neues Ziel zum Stehlen aus. Findet der Thief einen geeigneten als parallel abarbeitbar
markierten Task Frame, so erzeugt er einen neuen COPk Frame zuoberst auf seinem
Frame Stack und kopiert den zu stehlenden Task Frame darüber, wobei er dessen
parallele Markierung entfernt und den ursprünglichen Task Frame auf dem fremden
Frame Stack durch einen neuen T HIEFk Frame ersetzt. Anschließend beendet sich der
Thief, wobei jedoch sein eigener T HIEF Frame auf dem Frame Stack seines Prozessors
verbleibt.
Wurde der gestohlene Task Frame vollständig inklusive aller durch seinen Unter-
programmaufruf neu erzeugten Task Frames abgearbeitet, so stößt der Zielprozessor
auf den darunterliegenden COPk Frame und führt ein spezielles Cop Unterprogramm
48
3.5 Parallele Ausführung von CES Programmen mittels Cop/Thief Work-Stealing
program(argc, argv;;)
COPn−2
...
P0 P1 ... Pn−1
(Cop) aus. Der Cop signalisiert seinem zugehörigen Thief (T HIEFk Frame), dass
der gestohlene Task Frame vollständig abgearbeitet wurde und beendet sich, wobei
er seinen COP Frame vom Frame Stack entfernt. Dadurch entfernt der Thief seinen
T HIEF Frame vom Frame Stack und erlaubt dem Prozessor, die darunterliegenden
als sequentiell auszuführen markierten Task Frames abzuarbeiten, da deren Datenfluss-
abhängigkeiten durch die Abarbeitung aller vormals darüberliegender Task Frames
gelöst sind.
Zur Ausführung des initialen Unterprogramms der neuen Implementierung von
Unterprogrammen werden die Frame Stacks der N Prozessoren Pi wie folgt initialisiert:
auf dem Frame Stack des Prozessors P0 wird für jeden weiteren Prozessor Pk ein
COP Frame erzeugt und auf den Frame Stacks der Prozessoren Pk wird jeweils ein
zugehöriges T HIEF Frame erzeugt.
Abbildung 3.5 zeigt den Vorgang des Stehlens eines als parallel abarbeitbar markierten
Task Frame anhand eines einfachen Beispiels. Abbildung 3.4 zeigt die Initialisierung
der Frame Stacks der Prozessoren anhand der Ausführung eines CES Programms.
Abbildung 3.6 zeigt die parallele Ausführung eines CES Programms unter Verwen-
dung von Cop/Thief Work-Stealing am Beispiel von Mergesort aus Listing 2.9. Die
rekursive Aufteilung des Array kann wieder anhand der Abbildung 2.10 nachvollzogen
werden.
3.5.3 Auswahl eines geeigneten Ziels zum Stehlen von Arbeit beim
(Cop/Thief) Work-Stealing
Sobald ein Prozessor bei der Abarbeitung seines Frame Stack auf einen Thief stößt,
der die weitere sequentielle Abarbeitung über seinen T HIEF Frame hinaus blockiert,
49
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
...
...
...
foobar(...)
... T HIEFk
P0 P1
(a) t0 : Der auf Prozessor P1 laufende T HIEFk scannt den Frame Stack von Prozessor P0 von
unten nach oben nach als parallel abarbeitbar markierten Task Frames.
...
...
...
Task
foobar(...)
foobar(...) COPi
T HIEFi
... T HIEFk
P0 P1
(b) t1 : Der T HIEFk erzeugt einen neuen COPi Frame zuoberst auf dem Frame Stack seines
Prozessors P1 , kopiert den zu stehlenden Task Frame darüber und ersetzt den gestohlenen
Task Frame durch einen neuen T HIEFi Frame.
...
...
... foobar(...)
T HIEFi COPi
... T HIEFk
P0 P1
(c) t2 : Der Stehlvorgang ist abgeschlossen und der gestohlene Task Frame wird von Prozessor P1
abgearbeitet. COPi wird T HIEFi bei seiner späteren Abarbeitung signalisieren, dass der
gestohlene Task Frame vollständig abgearbeitet wurde, damit der T HIEFi die sequentielle
Abarbeitung über seinen Frame hinaus freigibt.
mergesort(n, size,
compare; array;)
COP1
P0 P1 P2
(a) Der Frame Stack von Prozessor P0 wurde mit jeweils einem COPi Frame für jeden T HIEFi Frame
der beiden anderen Prozessoren P1 und P2 sowie dem initialen Task Frame des CES Unterpro-
grammaufrufs mergesort(n, size, compare; array;) initialisiert. Die Frame Stacks der beiden
Prozessoren P1 und P2 wurden jeweils mit einem T HIEFi Frame initialisiert. P0 bearbeitet den
initialen Task Frame und führt das CES Unterprogramm mergesort aus, das sich beim Beenden
durch zwei parallel abarbeitbare Task Frames für die beiden parallel ausführbaren rekursiven
CES Unterprogrammaufrufe von mergesort sowie einen anschließend sequentiell abzuarbeitenden
Task Frame für den CES Unterprogrammaufruf von merge ersetzt (siehe 3.6b). T HIEF0 (P1 ) und
T HIEF1 (P2 ) versuchen, Arbeit in Form von parallel abarbeitbaren Task Frames von den jeweils
anderen Prozessoren zu stehlen (derzeit sind jedoch noch keine als parallel abarbeitbar markierten
Task Frames vorhanden).
mergesort(n/2, size,
compare; left0 ;)
mergesort(n/2, size,
compare; right0 ;)
COP1
P0 P1 P2
(b) Auf dem Frame Stack von P0 wurde der initiale mergesort Task Frame durch die drei neuen Task
Frames der gemerkten CES Unterprogrammaufrufe des ausgeführten CES Unterprogramms ersetzt:
zwei parallel abarbeitbare mergesort Task Frames und ein anschließend sequentiell abzuarbeitender
merge Task Frame. P0 bearbeitet den obersten seiner beiden als parallel abarbeitbar markierten
Task Frames, so dass nur noch der untere seine Markierung behält. T HIEF1 (P1 ) stiehlt den als
parallel abarbeitbar markierten mergesort Task Frame von P0 . Hierzu ersetzt er den gestohlenen
Task Frame auf dem Frame Stack von P0 durch einen neuen T HIEFk Frame, erzeugt einen neuen
COPk Frame zuoberst auf seinem eigenen Frame Stack, der später bei der Bearbeitung dem
T HIEFk signalisieren wird sich zu beenden, und kopiert den gestohlenen mergesort Task Frame
zuoberst über den neuen COPk Frame auf seinen Frame Stack. T HIEF1 (P2 ) geht daher leer aus.
Abbildung 3.6: Parallele Ausführung eines CES Programms unter Verwendung von
Cop/Thief Work-Stealing mit drei Prozessoren am Beispiel von Mer-
gesort aus Listing 2.9
51
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
mergesort(n/4, size,
compare; left1 ;)
mergesort(n/4, size,
compare; right1 ;)
T HIEF2
COP1 COP2
P0 P1 P2
(c) Auf dem Frame Stack von P0 wurde der oberste mergesort Task Frame entsprechend 3.6a und 3.6b
ersetzt und der gestohlene mergesort Task Frame durch den neuen T HIEF2 Frame ersetzt. Auf
dem Frame Stack von P1 wurde ein neuer COP2 Frame erzeugt, welcher bei seiner Bearbeitung dem
T HIEF2 das Signal zum Beenden geben wird. Zuoberst wurde der von P0 gestohlene mergesort
Task Frame kopiert. P0 bearbeitet seinen obersten mergesort Task Frame. Diesmal wird sich
das CES Unterprogramm mergesort nicht rekursiv aufrufen, da das zu sortierende Teilarray mit
n/4 Elementen bereits sortiert ist. P bearbeitet seinen obersten mergesort Task Frame, der sich
1
entsprechend 3.6a und 3.6b rekursiv weiter teilen wird. T HIEF1 (P2 ) stiehlt den als parallel
abarbeitbar markierten mergesort Task Frame von P0 entsprechend 3.6b. Dabei ersetzt er den
gestohlenen Task Frame durch einen neuen T HIEFk Frame und erzeugt auf seinem eigenen Frame
Stack einen neuen COPk Frame.
T HIEF3
mergesort(n/4, size,
T HIEF2
compare; right2 ;)
merge(n/2, left0 , n/2, right0 , merge(n/4, left2 , n/4, right2 , mergesort(n/4, size,
size, compare; array;) size, compare; right0 ;) compare; right1 ;)
P0 P1 P2
(d) Auf dem Frame Stack von P0 wurde der oberste mergesort Task Frame entfernt und der darauf
folgende mergesort Task Frame von P2 gestohlen und durch den neuen T HIEF3 Frame ersetzt.
Auf dem Frame Stack von P1 wurde der oberste mergesort Task Frame entsprechend 3.6a und 3.6b
ersetzt. Auf dem Frame Stack von P2 sind der neue COP3 Frame und der gestohlene mergesort Task
Frame hinzugekommen (entsprechend 3.6b und 3.6c). T HIEF3 (P0 ) versucht Arbeit zu stehlen,
doch P1 wird ihm diesmal zuvorkommen, so dass er leer ausgeht. P1 bearbeitet den obersten
mergesort Task Frame. Die Bearbeitung geht diesmal sehr schnell, so dass P1 den darauf folgenden
mergesort Task Frame zu bearbeiten beginnen wird, bevor T HIEF3 diesen stehlen konnte.
Abbildung
52 3.6: Parallele Ausführung eines CES Programms unter Verwendung von
Cop/Thief Work-Stealing mit drei Prozessoren am Beispiel von Mer-
gesort aus Listing 2.9 (Fortsetzung)
3.5 Parallele Ausführung von CES Programmen mittels Cop/Thief Work-Stealing
T HIEF3
mergesort(n/4, size,
T HIEF2
compare; right2 ;)
P0 P1 P2
(e) T HIEF3 (P0 ) versucht Arbeit von den anderen Prozessoren zu stehlen, doch es stehen keine als
parallel bearbeitbar markierten Task Frames mehr zur Verfügung, so dass er leer ausgeht. P1
bearbeitet seinen obersten mergesort Task Frame, der sich nicht durch neue Task Frames ersetzen
wird. COP3 (P2 ) wird T HIEF3 (P0 ) signalisieren, dass er sich beenden soll und sich anschließend
selbst beenden.
T HIEF2
COP1 COP2
P0 P1 P2
(f) COP3 und T HIEF3 haben sich beendet und wurden vom ihren jeweiligen Frame Stacks entfernt.
P0 bearbeitet jetzt seinen obersten merge Task Frame, P1 ebenfalls seinen obersten merge Task
Frame und T HIEF1 (P2 ) versucht erfolglos, Arbeit von den anderen Prozessoren zu stehlen.
Abbildung 3.6: Parallele Ausführung eines CES Programms unter Verwendung von
Cop/Thief Work-Stealing mit drei Prozessoren am Beispiel von Mer-
gesort aus Listing 2.9 (Fortsetzung)
53
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
beginnt der Thief, Arbeit von den anderen Prozessoren zu stehlen. Hierzu muss der
Thief mit Hilfe eines geeigneten Verfahrens sein Ziel zum Stehlen auswählen.
Blumofe hat in seiner Doktorarbeit zusammen mit Leiserson nachgewiesen, dass
Random Work-Stealing, wie es auch in Cilk [Cil] implementiert ist, für vollständig
strikte Berechnungen lineare Beschleunigung bei optimalem Kommunikationsoverhead
erzielt. [Blu95, Kap. 4.1 “A randomized work-stealing algorithm”, S. 37ff] Blumofes
Unterscheidung von nicht strikten, strikten und vollständig strikten Berechnungen mit
Threads lässt sich auf die neue Implementierung von Unterprogrammen übertragen,
wobei jeder von Blumofes Threads ein Unterprogramm repräsentiert. Bei strikten
Berechnungen verlaufen die Datenabhängigkeitskanten dementsprechend von Kind-
Unterprogrammen zu ihren Vorfahren-Unterprogrammen. Bei vollständig strikten
Berechnungen verlaufen sie ausschließlich von Kind-Unterprogrammen zu ihren Eltern-
Unterprogrammen. Vollständig strikte Berechnungen sind daher wohlstrukturiert, das
heißt, dass sich alle Datenabhängigkeiten der Kind-Unterprogramme aus ihrem Eltern-
Unterprogramm ergeben.
Bei der neuen Implementierung von Unterprogrammen können die Datenabhängig-
keitskanten zwar prinzipbedingt nicht zu den Vorfahren-Unterprogrammen verlaufen,
allerdings verlaufen sie immer zu den vom Eltern-Unterprogramm erzeugten Unter-
programmen und damit gewissermaßen zurück zum Eltern-Unterprogramm. Wie im
Abschnitt 3.3 gezeigt, ergeben sich bei der neuen Implementierung von Unterpro-
grammen ebenfalls alle Datenabhängigkeiten der Kind-Unterprogramme aus ihrem
Eltern-Unterprogramm. Damit sind prinzipiell die Erkenntnisse des von Blumofe analy-
sierten randomized work-stealing Algorithmus auch für die neue Implementierung von
Unterprogrammen anwendbar.
Blumofe beschreibt den von ihm analysierten randomized work-stealing Algorithmus
wie folgt:
Übertragen auf die parallele Ausführung der neuen Implementierung von Unterpro-
grammen mittels des Cop/Thief Work-Stealing wählt der Thief einen fremden Prozessor
(Frame Stack) gleichverteilt zufällig aus und versucht einen als parallel abarbeitbar
54
3.5 Parallele Ausführung von CES Programmen mittels Cop/Thief Work-Stealing
Listing 3.5: Gleichverteilt zufällige Auswahl des Ziels zum Stehlen in Cilk 5.4.6 –
runtime/sched.c
markierten Task Frame von unten vom fremden Frame Stack zu stehlen. Schlägt das
Stehlen fehl, so beginnt der Thief von Neuem und wählt einen anderen Prozessor
zufällig als Ziel aus.
Die Implementierung des Random Work-Stealing in Cilk sieht bei der erneuten
Auswahl eines anderen Ziels bei vorhergegangenem fehlgeschlagenen Stehlen keine
besondere Berücksichtigung des vorhergegangenen Ziels vor (siehe Listing 3.5. Das neue
Ziel wird also immer gleichverteilt zufällig aus der gesamten Menge an Prozessoren
gewählt.
Bei der Entwicklung der round-robin Laufzeitumgebung für CES wurde eine einfa-
che sequentielle Auswahl des Ziels zum Stehlen verwendet und für die anschließende
Entwicklung der parallelen Laufzeitumgebung für CES zunächst übernommen. An-
schließend wurde auch die gleichverteilt zufällige Auswahl des Ziels zum Stehlen von
Cilk in der parallelen Laufzeitumgebung für CES implementiert und mit der vorherigen
sequentiellen Auswahl verglichen, wobei bei den untersuchten Anwendungen keine
relevanten Performanceunterschiede festgestellt werden konnten. Dies schließt nicht
aus, dass es Performanceunterschiede bei anderen Anwendungen oder einer anderen
Implementierung von CES gibt.
3.5.3.1 Sequentielle Auswahl eines geeigneten Ziels zum Stehlen von Arbeit
Die sequentielle Auswahl eines geeigneten Ziels zum Stehlen von Arbeit unter Verwen-
dung von N Prozessoren oder Threads mit der ID in [0, N − 1] arbeitet nach folgendem
Algorithmus:
(IDself + 1) mod N erste Auswahl
IDvictim = (3.1)
(ID + 1) mod N folgende Auswahlen
victim
55
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
Ist IDvictim = IDself , wurde kein als parallel abarbeitbar markierter Task Frame zum
Stehlen gefunden oder schlug das Stehlen fehl, so wird die Auswahl wiederholt.
Durch den Beginn mit der auf die eigene IDself folgenden ID wird erreicht, dass
jeder Prozessor oder Thread eine eigene Besuchsreihenfolge aufweist.
3.5.3.2 Gleichverteilt zufällige Auswahl eines geeigneten Ziels zum Stehlen von
Arbeit
Die gleichverteilt zufällige Auswahl eines geeigneten Ziels zum Stehlen von Arbeit unter
Verwendung von N Prozessoren oder Threads mit der ID in [0, N − 1] nach Blumofe
arbeitet nach folgendem Algorithmus:
Ist IDvictim = IDself , wurde kein als parallel abarbeitbar markierter Task Frame
zum Stehlen gefunden oder schlug das Stehlen fehl, so wird die Auswahl wiederholt.
Dadurch kann es zur wiederholten Wahl derselben ID in Folge kommen, wobei in der
Zwischenzeit auch neue stehlbare Task Frames auf dem bereits besuchten Frame Stack
vorhanden sein können.
56
3.6 Round-Robin Laufzeitumgebung für CES (Round-Robin CES Runtime)
Allerdings kann auf die komplette Synchronisation verzichtet werden, die zum sicheren
parallelen Zugriff auf diese Datenstrukturen notwendig wäre.
Als Grundlage für die Implementierung der round-robin Laufzeitumgebung für CES
diente die sequentielle Laufzeitumgebung für CES von Wagner, welche um mehrere
Task Frame Stacks, die Implementierung des Cop/Thief Work-Stealing Konzepts und
um die round-robin Abarbeitung erweitert werden musste.
Wagners sequentielle Laufzeitumgebung für CES verwendet globale Variablen für
den Frame Stack, den Current Stack sowie den Zeiger auf den obersten Task Frame
auf dem Frame Stack, auf welche auch global von den vom CES Compiler (CESC) für
die CES Unterprogramme generierten C Unterprogrammen zugegriffen wird. Damit
fehlt dem vom CESC von Wagner generierten C Quelltext eine Abstraktion, eine
Art Programminterface zu den Laufzeitumgebungen für CES, die die Verwendung
unterschiedlicher Laufzeitumgebungen für CES ermöglicht. Im Zuge der Entwicklung
und Implementierung der round-robin Laufzeitumgebung für CES wurde daher, wie
bereits im Unterabschnitt 2.3.1 erläutert, die Codegenerierung des CESC um ein
Makro-Interface erweitert und die sequentielle Laufzeitumgebung für CES von Wagner
entsprechend angepasst.
Sowohl bei der round-robin als auch bei der parallelen Abarbeitung mittels meh-
rerer (virtueller) Prozessoren muss es für jeden Prozessor einen eigenen Frame Stack
und für einen jeden Frame Stack einen eigenen Zeiger auf seinen jeweiligen obersten
Task Frame geben. Für die round-robin Laufzeitumgebung wäre es denkbar, nur einen
Current Stack wie bei der sequentiellen Laufzeitumgebung zu verwenden, da nur ein
CES Unterprogramm zurzeit real ausgeführt wird und nur für dieses die CES Unterpro-
grammaufrufe gemerkt werden müssen. Um jedoch maximale Vorarbeit für die parallele
Laufzeitumgebung für CES zu leisten, wurde auch jeweils ein Current Stack für jeden
virtuellen Prozessor eingeführt.
Darüber hinaus muss bei der Ausführung der vom CESC für die CES Unterprogram-
me generierten C Unterprogramme durch die Laufzeitumgebung diesen der jeweilige
Prozessor oder der jeweilige Frame Stack mit dem Zeiger auf ihren (obersten) Task
Frame und der jeweilige Current Stack übergeben werden, damit diese auf den dem
virtuellen Prozessor zugehörigen Datenstrukturen arbeiten.
Gegenüber der sequentiellen Laufzeitumgebung für CES müssen die zur Abarbeitung
mit einem einzelnen Prozessor benötigten Datenstrukturen entsprechend der Anzahl
der virtuellen Prozessoren vervielfältigt werden und die Abarbeitungsschleife der Frame
57
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
Stacks um das Round-Robin Konzept erweitert werden. Ein jeder virtueller Prozes-
sor benötigt zur pseudoparallelen Abarbeitung von CES Programmen einen eigenen
Frame Stack, Current Stack und Frame Stack Pointer. Die Frame Stacks müssen
zum Programmstart wie in Abbildung 3.4 gezeigt mit den initialen COP Frames,
T HIEF Frames und dem Task Frame des initialen CES Unterprogramms initialisiert
werden. Darüber hinaus müssen die beiden speziellen Cop und Thief Unterprogramme
implementiert werden, wobei die round-robin Abarbeitung berücksichtigt werden muss.
Die vormals einzelnen globalen Variablen für die sequentielle Abarbeitung eines CES Pro-
gramms mittels eines einzelnen Prozessors wie der Frame Stack frame_stack, Current
Stack current_stack und Frame Stack Pointer fsp wurden zu der in Listing 3.6 ge-
zeigten Struktur THREAD_DATA zusammengefasst. Mit Hilfe eines Array dieser Struktur
werden die einzelnen Datenstrukturen einfach für jeden virtuellen Prozessor bereitge-
stellt:
THREAD_DATA thread_data[CES_THREADS]; /**< thread data for each thread */
3.6.1.2 Aufbau der Task Frames und Storage Frames der round-robin
Laufzeitumgebung für CES
Der Aufbau der Task Frames TASK_FRAME wurde geringfügig gegenüber dem der se-
quentiellen Laufzeitumgebung für CES angepasst, da der Schalter parallel nun zu
den Informationen der Task gehört und nicht mehr zu den Informationen des Pretty
Printer. Der Schalter parallel darf bei der round-robin Laufzeitumgebung nicht mehr
durch die bedingte Übersetzung der Unterstützung des Pretty Printer mit deaktiviert
werden.
58
3.6 Round-Robin Laufzeitumgebung für CES (Round-Robin CES Runtime)
0 31
Funktionszeiger auf Unterprogramm
Array von Zeigern auf Parameter des Unterprogramms
(Ein-, Ein/Aus- und Ausgabeparameter)
hhh
hhhh hh h
h
hhhh
Task
h
hhhhhhhhh
hhhh h
hhhhhhhhh
hhhh h
hhhhhhhhh
hhhh h
h
hhh
parallel flag
Abbildung 3.7 zeigt den gegenüber Abbildung 2.7 geänderten Aufbau der Struktur
TASK_FRAME der round-robin Laufzeitumgebung für CES, dessen Definition in C in
Listing 3.7 wiedergegegeben ist.
Der Aufbau der Storage Frames STORAGE_FRAME musste gegenüber dem der sequen-
tiellen Laufzeitumgebung für CES (siehe Abbildung 2.8) nicht angepasst werden.
3.6.1.3 Round-Robin Abarbeitung der Task Frames auf den Frame Stacks
59
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
Listing 3.8: Schleife zur round-robin Abarbeitung des jeweils obersten Task Frame aus
run_time.c der round-robin Laufzeitumgebung für CES
Der Cop signalisiert bei der Abarbeitung seines COP Frame seinem zugehörigen Thief
die vollständige Abarbeitung des gestohlenen Task Frame, der durch den T HIEF
Frame ersetzt wurde.
In der Implementierung der round-robin Laufzeitumgebung für CES besitzt der
Cop einen Zeiger auf den T HIEF Frame seines zugehörigen Thief (siehe Listing 3.9).
60
3.6 Round-Robin Laufzeitumgebung für CES (Round-Robin CES Runtime)
1 /**
2 * Cop CES Task Procedure.
3 * @param[in,out] fsp the frame stack pointer.
4 * @param[in] csp the current frame stack pointer.
5 */
6 void func_cop(TASK_FRAME ** fsp, TASK_FRAME * csp) {
7 /* kill thief */
8 COP_FSP->thief->killed = TRUE;
9 /* move task frame stack pointer to next task */
10 (*fsp) -= 1;
11 }
Der T HIEF Frame (siehe Listing 3.11) besitzt einen Schalter killed, der angibt, ob
sich der Thief beenden soll, da er von seinem zugehörigen Cop das Signal erhalten
hat. Das spezielle Cop Unterprogramm muss daher nur den Schalter killed seines
zugehörigen Thief setzen, um dem Thief die Abarbeitung des gestohlenen Task Frame
zu signalisieren.
Listing 3.9 zeigt die Definition des COP Frame COP_TASK_FRAME und Listing 3.10
zeigt die Definition des speziellen Cop Unterprogramms in C.
Der Thief stiehlt so lange bei der Abarbeitung seines T HIEF Frame als parallel abar-
beitbar markierte Task Frames von fremden Prozessoren, bis er von seinem zugehörigen
Cop das Signal erhält, dass der gestohlene Task Frame vollständig abgearbeitet wurde,
der sich ursprünglich an seiner Stelle auf seinem Frame Stack befand.
In der Implementierung der round-robin Laufzeitumgebung für CES besitzt der Thief
zwei für ihn notwendige Informationen in seinem T HIEF Frame (siehe Listing 3.11):
den Schalter killed, über den er das Signal zum Beenden von seinem Cop erhält,
und den Index des Frame Stack auf dem er sich befindet (threadIndex). Den Index
benötigt er, um ausschließlich die Frame Stacks von den anderen virtuellen Prozessoren
zu Task Frames zu stehlen.
Listing 3.11 zeigt die Definition des T HIEF Frame THIEF_TASK_FRAME und Lis-
61
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
Die Bildschirmausgabe des Pretty Printer während der Ausführung von Mergesort
aus Listing 3.3 befindet sich aufgrund ihrer Länge im Anhang in Abschnitt B.2.
62
3.7 Die POSIX Threads (Pthreads) Bibliothek für Threads in C
1 /**
2 * Thief CES Task Procedure.
3 * @param[in,out] fsp the frame stack pointer.
4 * @param[in] csp the current frame stack pointer.
5 */
6 void func_thief(TASK_FRAME ** fsp, TASK_FRAME * csp) {
7 int index = (THIEF_FSP->threadIndex + 1) % threads; /* look at right neighbor thread first '
for work to steal from */
8 while ((!THIEF_FSP->killed) && (index != THIEF_FSP->threadIndex)) {
9 /* thief is alive and is going to look for task frames to steal */
10 THREAD_DATA * td = &thread_data[index];
11 TASK_FRAME * ffsp = FS_BOTTOM(td);
12
13 while (ffsp < td->fsp) {
14 if ((ffsp->fnptr_task != func_storage) && (ffsp->parallel)) {
15 /* steal task frame */
16 COP_TASK_FRAME * cop;
17 THIEF_TASK_FRAME * thief;
18
19 /* 1. create cop task frame on top of own task frame stack */
20 ...
21
22 /* 2. copy foreign task frame on top of own frame stack */
23 ...
24
25 /* 3. overwrite foreign task frame with theif task frame */
26 ...
27
28 /* 4. initialize cop and thief parameters */
29 cop->thief = thief;
30 thief->killed = FALSE;
31 thief->threadIndex = index;
32
33 return; /* thief’s work is done for now */
34 } else {
35 ffsp++; /* jump over storage frame or unstealable task frame */
36 }
37 }
38 index = (index + 1) % threads; /* increment thread index to look for work to steal from'
*/
39 }
40 /* the thief can only be removed from the thread’s task frame stack if it has been killed '
but has not stolen anything */
41 if (THIEF_FSP->killed) {
42 /* thief has been killed by its cop */
43 (*fsp) -= 1;
44 }
45 }
63
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
Listing 3.13 zeigt die Pthreads API zur Erzeugung eines Thread [IBM, Abschnitt
“Creating Threads”]. pthread_create(...) erzeugt einen neuen Thread mit den op-
tionalen initialen Laufzeitattributen attributes und speichert eine Referenz auf den
Thread in der Variablen thread. Der neue Thread bearbeitet das per Funktionszeiger1
angegebene C Unterprogramm thread_subroutine, das das optionale initiale Argu-
ment argument übergeben bekommt. Wird anstelle eines optionalen Parameters NULL
übergeben, so werden die Standardwerte der jeweiligen Pthreads Implementierung
verwendet.
Beim Erzeugen eines Threads wird zur Laufzeit eine Referenz (englisch: handle)
vom Typ pthread_t auf den neuen Thread generiert, um auf diesen im weiteren
Programmverlauf zuzugreifen.
Zwischen Threads besteht keine Eltern-Kind-Beziehung, ein Thread führt nicht auto-
matisch Liste seiner erzeugten Threads und kennt auch nicht seinen erzeugenden Thread.
Es ist Aufgabe des Benutzers von Pthreads, derartige Informationen gegebenenfalls zu
speichern und den Threads zugänglich zu machen.
Die Laufzeitattribute attributes bestimmen, ob ein Thread detached oder joinable
ist, sowie welche Scheduling- und Stack-Parameter er verwendet. Die Eigenschaft
detached oder joinable wird im Unterabschnitt 3.7.3 erläutert.
Listing 3.17 zeigt den grundlegenden Aufbau eines C Unterprogramms eines Thread,
wie er von pthread_create gefordert wird.
Listing 3.14: Die Pthreads API zum Beenden und Abbrechen von Threads
Listing 3.14 zeigt die Pthreads API zur Beendigung und zum Abbruch eines Thread
[IBM, Abschnitt “Terminating Threads”]. Ein Thread kann sich mittels return oder
1
Tutorial zu Funktionszeigern und deren Syntax in C und C++: http://www.newty.de/fpt/
64
3.7 Die POSIX Threads (Pthreads) Bibliothek für Threads in C
Listing 3.15 zeigt die Pthreads API zur Zusammenführung von Threads [IBM, Ab-
schnitt “Joining Threads”]. pthread_join(...) erlaubt es einem Thread, auf die
Beendigung eines anderen Thread zu warten, der die Eigenschaft joinable hat. Das Un-
terprogramm blockiert den aufrufenden Thread, bis der angegebene Thread vollständig
beendet ist. Hat der angegebene Thread hingegen die Eigenschaft detached, so kehrt
pthread_join(...) sofort mit einer Fehlermeldung zurück.
pthread_join(...) ermöglicht dem angegebenen Thread, Informationen über die
Variable status beim Aufruf von pthread_exit(...) (siehe Unterabschnitt 3.7.2) an
den wartenden Thread zu übergeben.
int pthread_yield();
Listing 3.16 zeigt die Pthreads API zur Unterbrechnung von Threads [IBM, Abschnitt
“Scheduling Threads”]. pthread_yield(...) erlaubt es einem Thread, seinen Prozessor
abzugeben und in die Warteschlange zurückzukehren, bis er vom Scheduler wieder
einen Prozessor zugeteilt bekommt.
65
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
66
3.7 Die POSIX Threads (Pthreads) Bibliothek für Threads in C
67
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
68
3.7 Die POSIX Threads (Pthreads) Bibliothek für Threads in C
1 $ ./helloworld.exe
2 Hello, world from Pthreads working for thread number 1!
3 Hello, world from Pthreads working for thread number 2!
4 Hello, world from Pthreads working for thread number 3!
5 Hello, world from Pthreads working for thread number 4!
6 Hello, world from Pthreads working for thread number 5!
7 Thread 1 succeeded!
8 Thread 2 succeeded!
9 Thread 3 succeeded!
10 Thread 4 succeeded!
11 Thread 5 succeeded!
Listing 3.19: Bildschirmausgabe von Hello World mit Pthreads in C aus Listing 3.18
69
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
2. Initialisierung der Frame Stacks der Threads mit den initialen Cops und Thieves
und dem initialen CES Unterprogramm program(argc, argv;;) wie in Unter-
abschnitt 3.5.2 erläutert und in Abbildung 3.4 gezeigt. (Zeilen 9–15)
4. Jeder der Threads führt das C Unterprogramm exec_top_of_fs aus, das die
jeweilige Thread ID übergeben bekommt und sofort seine Arbeit aufnimmt
und seinen eigenen Frame Stack abarbeitet. (Zeilen 17–21) Dies beinhaltet die
vollständige parallele Ausführung des CES Programms mit Hilfe des Cop/Thief
Work-Stealing, wie es im Unterabschnitt 3.5.2 beschrieben wurde. Die Synchroni-
sation wird im folgenden Abschnitt 3.9 erläutert.
70
3.8 Verwendung von Pthreads zur parallelen Ausführung eines CES Programms
Listing 3.20: Verwendung von Pthreads zur parallelen Ausführung eines CES Pro-
gramms
71
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
Während ein Thief den fremden Frame Stack seines Ziels von unten nach oben nach
stehlbaren als parallel abarbeitbar markierten Task Frames scannt, arbeitet die Abar-
beitungsschleife exec_top_of_fs des Ziels zeitgleich denselben Frame Stack von oben
nach unten ab und fügt gegebenenfalls dem Frame Stack zuoberst neue Frames hinzu.
Scannen mehrere Thieves gleichzeitig denselben Frame Stack eines Thread, so würde
eine komplizierte Synchronisation zwischen den Thieves und der Abarbeitungsschleife
72
3.10 Primitiven zur Synchronisation
des Thread benötigt. Zur Vereinfachung wird daher eine Beschränkung von einem
scannenden Thief pro Frame Stack eingeführt. Dies reduziert die Synchronisazion auf
die Thieves untereinander bei der Auswahl ihres Ziels und anschließend auf einen Thief
und die Abarbeitungsschleife seines Ziels während des Scannens.
Ein Thief darf niemals den in der Ausführung befindlichen Task Frame seines Ziels
stehlen. Außerdem muss beim Stehlen eines als parallel ausführbar markierten Task
Frame sichergestellt werden, dass sich die Abarbeitungsschleife exec_top_of_fs des
Ziels und der Thief nicht in die Quere kommen, wenn der Tief den Frame kopiert und
anschließend durch einen neuen T HIEF Frame ersetzt. Es muss auf jeden Fall verhin-
dert werden, dass ein Task Frame mehrfach abgearbeitet wird, gar nicht abgearbeitet
wird oder gar korrumpiert wird. Dies erfordert eine Synchronisation zwischen Thief
und Abarbeitungsschleife während des Stehlens.
Signalisiert ein Cop seinem zugehörigen Thief die vollständige Abarbeitung des ge-
stohlenen Task Frame, so findet eine Kommunikation zwischen zwei Threads statt:
zwischen dem Thread der den COP Frame abarbeitet und dem Thread der den
T HIEF Frame abarbeitet. Diese gerichtete Kommunikation zwischen Cop und Thief
muss gegebenenfalls synchronisiert werden.
73
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
3.10.1.1 Mutexes
Listing 3.21 zeigt die Pthreads API für Mutexes [IBM, Abschnitt “Using Mutexes”].
Mit Mutex (Abkürzung für Mutual Exclusion) wird sowohl das Verfahren zum wech-
selseitigen Ausschluss bei nebenläufiger Programmierung als auch ein Objekt bzw.
eine Datenstruktur bezeichnet, mit Hilfe derer ein sogenanntes Mutual Exclusion Lock
realisiert wird, welches eben diesen wechselseitigen Ausschluss sicherstellt. In Pthreads
bezeichnet Mutex die Datenstruktur zur Realisierung eines Mutual Exclusion Lock,
das mit einer binären Semaphore identisch ist.
Mittels der Mutexes lassen sich kritische Abschnitte implementieren, um Daten oder
Resourcen vor dem gleichzeitigen Zugriff durch mehrere Threads zu schützen.
Ein Mutex ist vom Typ pthread_mutex_t und muss vor seiner Verwendung entweder
mittels des statischen Initialisierers PTHREAD_MUTEX_INITIALIZER oder mit Hilfe von
pthread_mutex_init(...) initialisiert werden. Nach seiner Verwendung muss ein
Mutex mittels pthread_mutex_destroy(...) zerstört werden.
Ein Mutex besitzt zwei mögliche Zustände: gesperrt und entsperrt. Nach seiner Initiali-
sierung ist er zunächst entsperrt. Mittels pthread_mutex_lock(...) und pthread_mutex_trylock(...
kann ein Thread einen Mutex zu Beginn eines kritischen Abschnitts sperren und
nach dessen Ende mittels pthread_mutex_unlock(...) wieder entsperren. Bei der
Verwendung von pthread_mutex_lock(...) zum Sperren eines Mutex wird der auf-
rufende Thread so lange blockiert, bis der Mutex wieder entsperrt ist, falls der
Mutex bereits durch einen anderen Thread gesperrt ist. Bei der Verwendung von
pthread_mutex_trylock(...) zum Sperren eines Mutex kehrt der Aufruf hingegen
in jedem Fall sofort zurück. Der Thread erfährt über den Rückgabewert, ob er den
Mutex erfolgreich gesperrt hat oder ob der Mutex bereits durch einen anderen Thread
gesperrt ist.
74
3.10 Primitiven zur Synchronisation
1 pthread_mutex_lock(&mutex);
2 while (!condition)
3 pthread_cond_wait(&cvar, &mutex);
4 ...
5 pthread_mutex_unlock(&mutex);
Listing 3.22 zeigt die Pthreads API für Conditon Variables [IBM, Abschnitt “Using
Condition Variables”]. Condition Variables erlauben es einem Thread, auf ein bestimm-
tes Ereignis oder eine bestimmte Voraussetzung bzw. Bedingung zu warten. Mittels der
Condition Variables ist es unter anderem möglich, komplexe Synchronisationsobjekte2
wie beispielsweise Barrieren, langlebige Locks und Semaphoren zu implementieren.
Eine Condition Variable ist vom Typ pthread_cond_t und muss vor ihrer Verwen-
dung entweder mittels des statischen Initialisierers PTHREAD_COND_INITIALIZER oder
mit Hilfe von pthread_cond_init(...) initialisiert werden. Nach ihrer Verwendung
muss eine Condition Variable mittels pthread_cond_destroy(...) zerstört werden.
Eine Condition Variable wird immer zusammen mit einem Mutex und üblicherweise
darüber hinaus mit weiteren benutzerdefinierten Variablen verwendet, welche angeben,
ob die Voraussetzung bzw. Bedingung erfüllt ist. Der Mutex serialisiert hierbei den
Zugriff auf die benutzerdefinierten Variablen.
Mittels pthread_cond_wait(...) oder pthread_cond_timedwait(...) kann ein
Thread mit Hilfe der Condition Variable cvar auf das Eintreten des Ereignisses
condition warten, wobei er den Mutex mutex entsperrt, seinen Prozessor abgibt
und in die Warteschlange zurückkehrt. Wird der Thread durch einen anderen Thread
über den Eintritt des Ereignisses informiert, so bekommt er vom Scheduler wieder einen
Prozessor zugeteilt und sperrt automatisch wieder den Mutex. Bei der Verwendung
von pthread_cond_wait(...) wartet der Thread solange, bis er durch einen ande-
ren Thread informiert wird. Bei der Verwendung von pthread_cond_timedwait(...)
wartet er hingegen maximal die durch timeout angegebene Zeitspanne.
2
Siehe hierzu beispielsweise [IBM], Abschnitt “Creating Complex Synchronization Objects” für
jeweils eine Implementierung von langlebigen Locks (Long Locks), Semaphoren und Write-Priority
Read/Write Locks.
75
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
Listing 3.24: Die Pthreads API für Read-Write Locks (RW Locks)
3
Die Spezifikation von Pthreads erlaubt es, dass bei der Verwendung von pthread_cond_signal(...)
auch mehr als nur einem Thread das Ereignis signalisiert wird.
76
3.10 Primitiven zur Synchronisation
Listing 3.24 zeigt die Pthreads API für Read-Write Locks [IBM, Abschnitt “Using
Read-Write Locks”]. Read-Write Locks erlauben es entweder mehreren Threads, gleich-
zeitig auf eine Ressource lesend zuzugreifen oder nur einem einzigen Thread, auf diese
schreibend zuzugreifen. Sie stellen damit ein Multiple-Reader Single-Writer Lock dar.
Das RW Lock wird entweder zum gemeinsamen Lesen oder zum alleinigen Schreiben
gesperrt und anschließend entsperrt. Es erfolgt keine Priorisierung des Lesens oder
Schreibens.
Ein Read/Write Lock ist vom Typ pthread_rwlock_t und muss vor seiner Ver-
wendung entweder mittels des statischen Initialisierers PTHREAD_RWLOCK_INITIALIZER
oder mit Hilfe von pthread_rwlock_init(...) initialisiert werden. Nach seiner Ver-
wendung muss ein Read/Write Lock mittels pthread_rwlock_destroy(...) zerstört
werden.
Mittels pthread_rwlock_rdlock(...) bzw. pthread_rwlock_tryrdlock(...) kann
ein RW Lock zum gemeinsamen Lesen gesperrt werden. Die beiden Varianten verhal-
ten sich entsprechend dem Sperren eines Mutex (siehe Unterunterabschnitt 3.10.1.1),
wobei der aufrufende Thread blockiert wird, solange ein anderer Thread das RW Lock
zum alleinigen Schreiben gesperrt hat. Mittels pthread_rwlock_wrlock(...) bzw.
pthread_rwlock_trywrlock(...) kann ein RW Lock entsprechend zum alleinigen
Schreiben gesperrt werden, wobei der aufrufende Thread blockiert wird, solange an-
dere Threads das RW Lock zum gemeinsamen Lesen gesperrt haben oder ein an-
derer Thread das RW Lock zum alleinigen Schreiben gesperrt hat. Mit Hilfe von
pthread_rwlock_unlock(...) wird das RW Lock im Anschluss an das Lesen oder
Schreiben wieder entsperrt.
77
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
Prozessoren stellen mit ihren Maschinenbefehlen Operationen bereit, von denen einige
atomar ablaufen. Die Atomarität bezieht sich bei Einkernprozessoren auf die Nicht-
unterbrechbarkeit durch Interrupts, bei Mehrkernprozessoren darüber hinaus auf eine
interne Synchronisation zwischen den Prozessoren.
Ältere Einkernprozessoren stellen üblicherweise einfache Operationen wie Bit Test and
Set und Bit Test and Clear zur Verfügung, die atomar ein Bit testen und anschließend
setzen oder löschen und das Ergebnis des vorangehenden Tests zurückgeben. Auch Lese-
und Schreiboperationen mit der Datenwortbreite des Prozessors sind meist atomar.
Diese Operationen sind nicht durch Interrupts unterbrechbar und können daher zur
Synchronisation zwischen Interrupt Service Routines (ISR) und dem eigentlichen
Programm genutzt werden.
Moderne Einkern- und Mehrkernprozessoren können Lese- und Schreiboperationen
allerdings umsortieren (out-of-order execution) und Leseoperationen spekulativ durch-
führen (speculative execution), solange sich das Ergebnis der Operationen dadurch
nicht verändert. Um dennoch eine bestimmte Reihenfolge der Speicheroperationen zu
erzwingen, bieten diese Prozessoren üblicherweise sogenannte Memory Barriers oder
auch Memory Fences genannte Operationen an, deren Funktion architekturabhängig
ist.
Darüber hinaus stellen moderne Einkern- und Mehrkernprozessoren noch komplexere
atomare Operationen zur Verfügung, wie zum Beispiel Fetch and Increment, Fetch
and Decrement, Fetch and Add, Swap, Compare and Swap, Double Word Compare
and Swap und Load Linked/Store Conditional (LL/SC), deren Verfügbarkeit ebenfalls
architekturabhängig ist.
Die folgenden Ausführungen zur Implementierung beschränken sich daher auf die
aktuellen Prozessoren der Intel Architecture, 32-bit (IA-32). Stellenweise wird auch
auf die IBM Performance Optimized With Enhanced RISC (POWER) Architektur
eingegangen.
Die GNU Compiler Collection (GCC) stellt in ihrer neusten Version 4.2.x eine
Auswahl an allgemeinen atomaren Operationen als eingebaute Befehle/Funktionen
bereit. [ST08, Kap. 5.45 “Built-in functions for atomic memory access”, S. 301ff] Darüber
hinaus stellt die GCC auch einen Großteil der IA-32 spezifischen atomaren Operationen
78
3.10 Primitiven zur Synchronisation
als eingebaute Befehle/Funktionen bereit. [ST08, Kap. 5.48.5 “X86 Built-in Functions”,
S. 319ff] Der IBM XL C Compiler stellt ebenfalls eingebaute Befehle/Funktionen für
die atomaren Operationen der POWER Architektur bereit. [Int05, Kap. 10 “Built-in
functions for POWER and PowerPC architectures”, S. 355ff]
Die in die Compiler integrierten eingebauten Befehle/Funktionen besitzen gegenüber
eigenen Implementierungen in Form externer Funktionen oder Inline-Assembler den
Vorteil, dass der Compiler diese bei der Codegenerierung, insbesondere beim Instruction
Reordering, entsprechend berücksichtigen kann. Dadurch wird sichergestellt, dass
Befehle aus einem durch die atomaren Operationen geschützten Bereich nicht durch
den Compiler möglicherweise herausgeschoben werden.
Da die Implementierung der parallelen Laufzeitumgebung für CES jedoch zunächst
unter der älteren, aber derzeit verbreiteteren Version 3.4.4 bzw. 3.4.6 der GCC begonnen
wurde, wurden die atomaren Operationen mit Hilfe des GCC eigenen Inline-Assembler
implementiert. Dieser ermöglicht es, durch die Angabe von Constraints im Assembler-
quelltext dem Compiler Hinweise auf die Auswirkungen der Assemblerbefehle auf die
Prozessoregister und den Hauptspeicher zu geben. Damit können ebenfalls möglicher-
weise unerlaubte Umsortierungen beim Instruction Reordering des Compiler verhindert
werden.
Bei den Prozessoren der IA-32 ab dem i486 Prozessor sind Lese- und Schreiboperationen
mit den Datenwortbreiten 8-Bit (byte), 16-Bit (word) und 32-Bit (doubleword) immer
atomar, wenn das Datenwort entsprechend seiner Datenwortbreite im Speicher ausge-
richtet (englisch: aligned) ist. Zusätzlich sind bei den Prozessoren der IA-32 ab dem
Pentium Prozessor auch alle Lese- und Schreiboperationen mit einer Datenwortbreite
von 64-Bit (quadword) atomar, sofern das Datenwort an einer 64-Bit Speichergrenze
ausgerichtet ist. Darüber hinaus sind bei den aktuellen Prozessoren der IA-32 ab dem
P6 Prozessor auch alle Speicherzugriffe auf unausgerichtete Datenworte der Breiten
16-Bit, 32-Bit und 64-Bit atomar, sofern das Datenwort in eine Cachezeile passt. [Int07d,
Kap. 7.1.1 “Guaranteed Atomic Operations”, S. 7-3]
Die IA-32 stellt mit den Streaming SIMD Extensions (SSE) und SSE2 Erweiterungen
drei verschiedene Operationen, sogenannte Memory Fences, zur Erzwingung einer
bestimmten Speicheroperationsreihenfolge zur Verfügung: mfence, lfence und sfence.
[Int07d, Kap. 7.2.2 “Memory Ordering in P6 and More Recent Processor Families”, S. 7-
9 & 7-12][Int07a, Kap. 7.5.5 “FENCE Instructions”, S. 7-11f] Die POWER Architektur
79
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
stellt ähnliche Operationen unter der Bezeichnung Memory Barriers zur Verfügung.
SFENCE (Store Fence) Serialisiert alle Schreiboperationen, die vor der sfence In-
struktion im Programminstruktionsstrom auftraten, beeinflusst jedoch keine
Leseoperationen. Das heißt, dass alle Schreiboperationen vor der sfence Instruk-
tion vor auf die sfence Instruktion folgenden Schreiboperationen global sichtbar
sind. [Int07a, Kap. 7.5.5.1 “SFENCE Instruction”, S. 7-11][Int07c, Kap. 4.1
“Instructions (N–Z), SFENCE—Store Fence”, S. 4-380]
LFENCE (Load Fence) Serialisiert alle Leseoperationen, die vor der lfence Instrukti-
on im Programminstruktionsstrom auftraten, beeinflusst jedoch keine Schreibope-
rationen. Das heißt, dass alle Leseoperationen vor der lfence Instruktion vor auf
die lfence Instruktion folgenden Leseoperationen global sichtbar sind. [Int07a,
Kap. 7.5.5.2 “LFENCE Instruction”, S. 7-11f][Int07b, Kap. 3.2 “Instructions
(A–M), LFENCE—Load Fence”, S. 3-576]
MFENCE (Memory Fence) Serialisiert alle Schreib- und Leseoperationen, die vor der
mfence Instruktion im Programminstruktionsstrom auftraten. [Int07a, Kap. 7.5.5.3
“MFENCE Instruction”, S. 7-12][Int07b, Kap. 3.2 “Instructions (A–M), MFENCE—
Memory Fence”, S. 3-619]
Die GCC stellt den SSE Maschinenbefehl sfence ab der Version 3 und die SSE2 Ma-
schinenbefehle lfence und mfence ab der Version 4 als eingebaute Befehle zur Verfü-
gung: [ST08, Kap. 5.48.5 “X86 Built-in Functions”, S. 320 & 324]
__builtin_ia32_sfence();
__builtin_ia32_lfence();
__builtin_ia32_mfence();
Die Store Fence Operation kann beispielsweise bei dem Producer/Consumer Beispiel
aus Listing B.1 zur Synchronisation zwischen Producer und Consumer verwendet
werden, wie in den Kommentaren im Listing angedeutet.
Die Prozessoren der IA-32 und POWER Architekturen stellen neben den einfachen
atomaren Speicheroperationen auch noch eine Reihe an komplexen atomaren read-
modify-write Speicheroperationen zur Verfügung. Die IA-32 stellt hierzu den Maschi-
nenbefehlprefix lock zur Verfügung, der zusammen mit einer Reihe von Maschinenbe-
fehlen verwendet werden kann. Durch den vorangestellten lock Prefix werden diese
read-modify-write Instruktionen zu atomaren read-modify-write Instruktionen und
80
3.10 Primitiven zur Synchronisation
Listing 3.26: Implementierung eines einfachen Lock mit Hilfe der atomaren Operation
Swap
können somit zur zuverlässigen Kommunikation zwischen den Prozessoren eines Mehr-
prozessorsystems verwendet werden. [Int07d, Kap. 7.1 “Locked Atomic Operations”,
S. 7-2–7-8][Int07d, Kap. 2.6.5 “Controlling the Processor”, S. 2-30]
Der lock Prefix kann bei den aktuellen Prozessoren der IA-32 zusammen mit
den folgenden Maschinenbefehlen verwendet werden: bts, btr, btc, xadd, cmpxchg,
cmpxchg8b, cmpxchg16b, xchg, inc, dec, not, neg, add, adc, sub, sbb, and, or, xor.
[Int07d, Kap. 7.1.2.2 “Software Controlled Bus Locking”, S. 7-5]
Swap
Die atomare Operation Swap erlaubt es, ein Datenwort im Speicher atomar auszutau-
schen. Listing 3.25 zeigt die formale Definition von Swap. Die IA-32 stellt die Operation
in Form des Maschinenbefehls xchg (Exchange) bereit, der einen impliziten lock Prefix
besitzt.
1 <type> Swap(<type> * pointer, <type> newValue) {
2 <type> oldValue ← *pointer;
3 *pointer ← newValue;
4 return oldValue;
5 }
Listing 3.26 demonstriert die Verwendung der atomaren Operation Swap zur Imple-
mentierung eines einfachen Lock in Anlehung an das Assembler-Beispiel aus [Int07a],
Bsp. 8-4 “Spin-wait Loop and PAUSE Instructions”, S. 8-17.
81
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
Im Anhang B im Abschnitt B.3 befindet sich der C Quelltext der 32-Bit und
64-Bit Implementierungen von Compare and Swap in C unter Verwendung von Inline-
Assembler für die IA-32: Listing B.4, Listing B.5, Listing B.6 und Listing B.7. Diese
Implementierungen werden zur Implementierung der Synchronisation der parallelen
Laufzeitumgebung für CES im Abschnitt 3.12 verwendet.
Die GCC stellt Compare and Swap ab Version 4 als eingebaute Funktionen bereit:
[ST08, Kap. 5.45 “Built-in functions for atomic memory access”, S. 302]
bool __sync_bool_compare_and_swap(<type> * ptr, <type> oldval, <type> newval);
<type> __sync_val_compare_and_swap(<type> * ptr, <type> oldval, <type> newval);
Der IBM XL C stellt Compare and Swap ebenfalls als eingebaute Funktionen bereit:
[Int05, Kap. 10 “Built-in functions for POWER and PowerPC architectures”, S. 365]
int __compare_and_swap(volatile int * addr, int * old_val_addr, int new_val);
82
3.11 Synchronisation der parallelen Laufzeitumgebung für CES (Pthreads)
Die ähnliche atomare Operation Fetch and Sub lässt sich ganz einfach durch Angabe
eines negativen Werts bei der Verwendung von Fetch and Add realisieren.
Die IA-32 stellt darüber hinaus noch die ähnlichen atomaren Operationen Increment
(inc), Decrement (dec), (Add (add bzw. adc) und Sub (sub bzw. sbb) bereit, welche
ebenfalls zur Erlangung der Atomarität explizit mit dem lock Prefix verwendet werden
müssen.
83
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
parallele Laufzeitumgebung für CES entwickelt werden. Mit Hilfe dieser Laufzeit-
umgebung soll die parallele Ausführung von CES Programmen erstmals verifiziert
werden, um möglichst früh mögliche Probleme aufzuspüren. Gleichzeitig dient sie als
Ausgangsbasis der Untersuchung der Optimierung der späteren Synchonisation mittels
der atomaren Operationen.
Zur Synchronisation des Cop/Thief Work-Stealing der parallelen Laufzeitumgebung
wurden ausschließlich die Mutexes (siehe Unterunterabschnitt 3.10.1.1) verwendet. Die
Condition Variables (siehe Unterunterabschnitt 3.10.1.2) sind für die Synchronisati-
onsanforderungen ungeeignet, da sie dem zumeist längeren Warten eines Thread auf
ein Ereignis dienen, welches durch einen anderen Thread signalisiert wird, wobei der
wartende Thread seinen Prozessor abgibt. Der Thief wartet zwar gewissermaßen auf
ein Ereignis, nämlich die Signalisierung der vollständigen Abarbeitung des gestohlenen
Task Frame durch seinen Cop, jedoch soll er in der Zwischenzeit selbst aktiv als parallel
abarbeitbar markierte Task Frames stehlen. Ebenfalls ungeeignet für die Synchronisa-
tionsanforderungen sind die Read-Write Locks (siehe Unterunterabschnitt 3.10.1.3),
die gut für häufg gelesene und selten veränderte Datenstrukturen sind. Bei der neu-
en Implementierung von Unterprogrammen unterliegt der Frame Stack jedoch einer
ständigen Veränderung.
Die Struktur THREAD_DATA wurde um einen Mutex thief_mutex für die Thieves er-
weitert, der die Bedingung eines einzelnen Thief zur Zeit pro Frame Stack sicherstellt.
Weiterhin wurde die Struktur auch noch um einen mittels eines weiteren Mutex
thief_fsp_top_mutex geschützten Thief Frame Stack Top Pointer thief_fsp_top
erweitert, der sicherstellt, dass ein Thief nicht über den obersten Frame hinausläft oder
mit der Abarbeitungsschleife seines Zielthread in Konflikt gerät.
Listing 3.30 zeigt die gegenüber der round-robin Laufzeitumgebung veränderte
Definition der Struktur THREAD_DATA in C.
84
3.11 Synchronisation der parallelen Laufzeitumgebung für CES (Pthreads)
3.11.1.2 Parallele Abarbeitung der Task Frames auf den Frame Stacks
Zur parallelen Abarbeitung des CES Programms wird pro physikalische Central Pro-
cessing Unit (CPU) ein Thread mit eigenem Frame Stack erzeugt, der jeweils das
Unterprogramm mit der Frame Stack Abarbeitungsschleife exec_top_of_fs ausführt.
Die Definition des Unterprogramms musste daher geringfügig angepasst werden, um
den Anforderungen als Thread-Unterprogramm zu genügen.
Listing 3.31 zeigt den angepassten Quelltext des Unterprogramms mit der Frame Stack
Abarbeitungsschleife. Das Unterprogramm exec_top_of_fs bekommt seine Thread-
ID wie in Unterabschnitt 3.7.5 erläutert über den Thread-Unterprogrammparameter
übergeben (Zeilen 1–2). Mit Hilfe der ID greift das Unterprogramm auf den Frame
Stack seines Threads zu (Zeile 3). Die eigentliche Frame Stack Abarbeitungsschleife
(Zeilen 7–20) wurde vom Pretty Printer befreit und um die Synchronisation mit den
Thieves erweitert. Der Pretty Printer kann bei der parallelen Abarbeitung der Task
Frames nicht mehr sinnvoll umgesetzt werden, da dabei mehrere Threads gleichzeitig
Ausgaben auf dem Bildschirm tätigen würden, deren Zusammenhang nicht mehr zu
rekonstruieren wäre. Stattdessen wurde ein optionaler Trace-Modus implementiert, bei
dem einige wenige Ablaufinformationen zusammen mit der ID des Threads auf dem
Bildschirm ausgegeben werden (Zeilen 5 und 22). Das Cop- und Thief Unterprogramm
machen ebenfalls von dem Trace-Makro Gebrauch. Nach der vollständigen Abarbeitung
seines Frame Stacks liefert das Unterprogramm beim Beenden seinen Status an den
zusammenführenden Thread zurück (Zeile 23).
85
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
Das Cop Unterprogramm func_cop musste aufgrund der geänderten T HIEF Fra-
mes leicht angepasst werden. Es setzt nun nicht mehr den Schalter killed, sondern
verändert den Funktionszeiger des T HIEF Frame, indem es ihn auf das spezielle
C Unterprogramm func_nop zeigen lässt, das keine Funktionalität beinhaltet und
lediglich den obersten Frame vom Frame Stack entfernt. Der Thief erkennt entweder
selber diese Änderung und beendet sich, wobei er seinen T HIEF Frame vom Frame
Stack entfernt, oder das No OPeration (NOP) Unterprogramm wird ausgeführt und
entfernt den T HIEF Frame vom Frame Stack.
Listing 3.32 zeigt die Definition des angepassten Cop Unterprogramms func_cop.
Der Aufbau der Struktur der T HIEF Frames wurde gegenüber dem der round-robin
Laufzeitumgebung stark verändert. Die Felder killed und threadIndex wurden ent-
fernt, so dass lediglich der Funktionszeiger auf das Thief Unterprogramm übrig bleibt.
Listing 3.33 zeigt die gegenüber der round-robin Laufzeitumgebung für CES ver-
änderte Definition der Struktur der T HIEF Frames in C. Das komplett angepasste
Thief Unterprogramm ist im Anhang im Abschnitt B.4 in Listing B.8 wiedergegegeben.
86
3.11 Synchronisation der parallelen Laufzeitumgebung für CES (Pthreads)
1 /**
2 * Cop CES Task Procedure.
3 * @param[in,out] fsp the frame stack pointer.
4 * @param[in] csp the current frame stack pointer.
5 */
6 void func_cop(TASK_FRAME ** fsp, TASK_FRAME * csp) {
7 /* kill thief */
8 ((COP_TASK_FRAME *)*fsp)->thief->fnptr_thief_task = func_nop;
9 /* move frame stack pointer to next task */
10 --(*fsp);
11 }
Der Thief ermittelt nun die ID des Thread auf dem er gerade ausgeführt wird,
mit Hilfe der ihm übergebenen Adresse seines Frame Stack fsp (Listing B.8, Zeile 7)
und bekommt das Signal seines Cop über eine Veränderung seines Funktionszeigers
fnptr_thief_task mitgeteilt (Zeile 13). Letzteres dient bereits der Vorarbeit für die
mittels der atomaren Operationen synchronisierten parallelen Laufzeitumgebung für
CES.
Das Ziel der Synchronisation des Scannens eines fremden Frame Stack ist es zu
verhindern, dass mehrere Thieves gleichzeitig auf denselben Frame Stack zugreifen und
der Thief während des Scannens des Frame Stack über den obersten Frame hinausläuft.
Die Struktur THREAD_DATA wurde um einen Mutex thief_mutex für die Thieves
erweitert, der die Bedingung eines einzelnen Thief zur Zeit pro Frame Stack sicher-
stellt. Nach der Auswahl eines Zielthreads zum Stehlen (Listing B.8, Zeile 15) nach
einem der Verfahren aus Unterabschnitt 3.5.3 versucht der Thief zuerst, den frem-
den Frame Stack für sich mittels pthread_mutex_trylock(...) zu sperren (Zeilen
24–25). Gelingt ihm dies nicht, so wählt er einen neuen Thread als Ziel. Beim Been-
den des Thief Unterprogramms entsperrt der Thief den fremden Frame Stack mittels
pthread_mutex_unlock(...) wieder (Zeilen 68 und 72).
87
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
Weiterhin wurde die Struktur THREAD_DATA auch noch um einen mittels eines
weiteren Mutex thief_fsp_top_mutex geschützten Thief Frame Stack Top Pointer
thief_fsp_top erweitert, der sicherstellt, dass ein Thief nicht über den obersten Frame
hinausläft oder mit der Abarbeitungsschleife seines Zielthread in Konflikt gerät. Die
Abarbeitungsschleife exec_top_of_fs aktualisiert vor der Abarbeitung des jeweils
obersten Task Frame den Thief Frame Stack Top Pointer (Listing 3.31, Zeilen 8–11).
Der Thief überwacht während des Scannens des fremden Frame Stack den durch
die Abarbeitungsschleife seines Ziels aktualisierten Zeiger auf den obersten Frame
thief_fsp_top (Listing B.8, Zeile 28). Theoretisch kann dennoch die Situation ein-
treten, dass der Thief über den obersten Frame hinaus auf den Frame Stack seines
Ziels zugreift, da sich der Wert von thief_fsp_top jederzeit im Anschluss an seine
Überprüfung ändern kann. Der Fall wird jedoch abgefangen, wie im folgenden Abschnitt
noch genauer erklärt.
Stößt der Thief beim Scannen des fremden Frame Stack auf einen als parallel ab-
arbeitbar markierten Task Frame (Listing B.8, Zeile 29), so versucht er diesen zu
stehlen (Zeile 34ff). Hierzu muss er sich mit der Abarbeitungsschleife exec_top_of_fs
seines Ziels synchronisieren, indem er den Mutex thief_fsp_top_mutex sperrt (Zei-
le 37). Dies blockiert für den gesamten Vorgang des Stehlens die weitere Arbeit der
Abarbeitungsschleife.
Sobald der Thief die Abarbeitungsschleife seines Ziels blockiert hat, überprüft er
erneut alle wichtigen Parameter des zu stehlenden Task Frame. Der Thief stellt sicher,
dass sich der gewählte Task Frame wirklich unterhalb des obersten Frame auf dem
Frame Stack des Ziels befindet und der Task Frame auch wirklich als parallel ausführbar
markiert ist (Zeile 40). Die Abarbeitungsschleife des Ziels hätte in der Zeit bis zum
Blockiern den gewählten Task Frame bereits abgearbeitet haben können und sogar
bereits durch einen neuen ersetzt haben können.
Sind alle Parameter in Ordnung, so stiehlt der Thief den gewählten als parallel
abarbeitbar markierten Task Frame wie in Unterabschnitt 3.5.2 bereits ausführlich
beschrieben (Zeilen 45–65). Bei der Veränderung des fremden Frame Stack müssen
keine besonderen Vorkehrungen getroffen werden, da sowohl die Abarbeitungsschleife
des Ziels als auch der fremde Frame Stack für weitere Thieves blockiert sind. Es kann
kein anderer Thread außer dem Thief auf den fremden Frame Stack zugreifen. Die
88
3.12 Synchronisation der parallelen Laufzeitumgebung für CES (CAS64)
Veränderung des eigenen Frame Stack ist ebenfalls unbedenklich möglich, obwohl dieser
von anderen Thieves gescannt werden kann. Die fremden Thieves müssen sich zum
Stehlen zunächst mit der Abarbeitungsschleife des Thread des Thief synchronisieren
und führen anschließend die erneute Überprüfung ihres gewählten Task Frame durch.
Gegebenenfalls schlägt dann das Stehlen fehl, wenn die fremden Thieves den vom Thief
gerade gestohlenen Task Frame im Visier hatten.
Für die Signalisierung der vollständigen Abarbeitung des gestohlenen Task Frame an
den Thief durch seinen zugehörigen Cop ist keine explizite Synchronisation erforderlich.
Der Cop verändert den Funktionszeiger des T HIEF Frame, indem er ihn auf das
spezielle C Unterprogramm func_nop zeigen lässt. Das Schreiben eines Datenworts
mit der Datenwortbreite eines Zeigers (hier 32-Bit) ist bei der IA-32 bei ausgerichteter
Adresse immer atomar (siehe Unterunterabschnitt 3.10.3.1).
89
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
Ideal wäre es dabei, wenn der Thief beim Stehlen die Frame Stack Abarbeitungs-
schleife nicht mehr blockieren würde oder nur dann, wenn es zu einer Kollision kommen
kann.
Die Datenstruktur THREAD_DATA wurde abermals überarbeitet. Listing 3.34 zeigt die
Definition der überarbeiteten Struktur THREAD_DATA der mittels der atomaren Opera-
tionen synchronisierten parallelen Laufzeitumgebung für CES.
Der Mutex thief_mutex, der die Bedingung eines einzelnen Thief zur Zeit pro Frame
90
3.12 Synchronisation der parallelen Laufzeitumgebung für CES (CAS64)
0 31
Funktionszeiger auf Unterprogramm
parallel flag
Array von Zeigern auf Parameter des Unterprogramms
(Ein-, Ein/Aus- und Ausgabeparameter)
hhhh Task
hhh hhhhh
h
hhhh h
hhhhhhhhh
hhhh h
hhhhhhhhh
hhhh h
hhhhhhhhh
hhhh h
hh
o
Zeiger auf Namenszeichenkette Debug
3.12.1.2 Aufbau der Task Frames, Storage Frames und Cop Frames
Der Aufbau der Task Frames und Storage Frames wurde noch einmal geringfügig
gegenüber dem Aufbau der round-robin Laufzeitumgebung für CES verändert, um die
atomare Operation Compare and Swap zur Synchronisation der parallelen Laufzeitum-
gebung zu verwenden.
Abbildung 3.8 zeigt den neuen Aufbau der Task Frames, deren Definition in Lis-
ting 3.35 wiedergegeben ist. Der Schalter parallel wurde direkt unter den Funkti-
91
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
1 typedef struct {
2 void (*fnptr_storage)(TASK_FRAME ** /* fsp */, TASK_FRAME * /* csp */); /**< reference '
to the storage’s dummy task C procedure */
3 int reserved_parallel; /**< padding for parallel flag in TASK_FRAME */
4 int value __attribute__ ((aligned (8))); /**< placeholder for the real value for '
address calculations; 8 byte aligned */
5 } STORAGE_FRAME;
onszeiger verschoben. Damit bilden der Funktionszeiger fnptr_task und der Schalter
parallel eine Einheit. Beide sind bei der IA-32 jeweils 32-Bit breit. Zusammen bilden
sie somit ein 64-Bit breites Datenwort. Die Notwendigkeit hierfür und der daraus
resultierende Nutzen wird in Unterabschnitt 3.12.3 näher erläutert.
Der Aufbau der Storage Frames und Cop Frames wurde entsprechend an den neuen
Aufbau der Task Frames angepasst. Die angepasste Definition der Storage Frames
ist in Listing 3.36 wiedergegeben. Listing 3.37 zeigt die gegenüber der round-robin
Laufzeitumgebung für CES veränderte Definition der Struktur der COP Frames.
92
3.12 Synchronisation der parallelen Laufzeitumgebung für CES (CAS64)
Das Thief Unterprogramm wurde aufgrund der gegenüber der Implementierung mittels
der Synchronisationsprimitiven von Pthreads geänderten Synchronisation zwischen
dem Thief und der Frame Stack Abarbeitungsschleife komplett überarbeitet.
Das komplett überarbeitete Thief Unterprogramm ist im Anhang im Abschnitt B.5 in
Listing B.9 wiedergegeben. Die geänderte Synchronisation ist im Unterabschnitt 3.12.3
beschrieben.
Das Ziel der Synchronisation des Scannens eines fremden Frame Stack durch die Thieves
besteht, wie bereits in Unterabschnitt 3.11.2 beschrieben, aus zwei Teilaspekten. Es ist
zu verhindern, dass mehrere Thieves gleichzeitig auf denselben Frame Stack zugreifen
und dass der Thief während des Scannens über den obersten Frame des Frame Stack
hinausläuft.
Die erste Bedingung, sicherzustellen, dass nur ein Thief zur Zeit auf einen Frame
Stack zugreifen kann, ist hinsichtlich der angestrebten Optimierung der sequentiellen
93
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
94
3.12 Synchronisation der parallelen Laufzeitumgebung für CES (CAS64)
onszeiger und dem Schalter parallel eines Task Frame zusammen atomar gegen einen
neuen Thief auszutauschen. Nur ein Vergleich mit Austausch beider Werte zugleich
kann sicherstellen, dass ein als parallel abarbeitbar markierter Task Frame gestohlen
wird. Ein alleiniger Vergleich mit Austausch des Funktionszeigers würde nicht sicher-
stellen können, dass der an der Position auf dem fremden Frame Stack befindliche
Task Frame wirklich parallel abarbeitbar ist. Die Frame Stack Abarbeitungsschleife des
Ziels könnte den Frame inzwischen durch einen neuen ersetzt haben, der sequentiell
abzuarbeiten ist. Der Vergleich mit dem vorherigen Funktionszeiger würde nur sicher-
stellen, dass es sich um einen Aufruf desselben Unterprogramms handelt. Da nicht
Unterprogramme, sondern ihre Aufrufe als parallel ausführbar markiert werden, wäre
dies nicht ausreichend.
Weitere Voraussetzung für das reibungslose atomare Stehlen eines Task Frame ist die
Möglichkeit, diese Einheit auch atomar lesen zu können. Nur so kann sichergestellt wer-
den, dass beide Informationen (Funktionszeiger und Schalter) auch wirklich zueinander
gehören und nicht zu zwei unterschiedlichen Task Frames. Würden beide Informationen
getrennt voneinander gelesen, so könnte die Frame Stack Abarbeitungsschleife des Ziels
den Task Frame bereits zwischenzeitlich ersetzt haben.
Beim Scannen des fremden Frame Stack liest der Thief die Einheit aus dem Funk-
tionszeiger und dem Schalter parallel jeweils atomar (Listing B.9, Zeile 36) und
überprüft den Zustand des Schalters (Zeile 39). Ist der Task Frame als parallel aus-
führbar markiert, so versucht er diesen mit Hilfe der CAS Operation atomar gegen
einen neuen Thief auszutauschen (Zeile 47). Gelingt dem Thief das nicht, so bricht er
das Stehlen ab und wählt ein neues Ziel, da es zu einem Konflikt zwischen ihm und
der Frame Stack Abarbeitungsschleife gekommen sein muss (Zeilen 48–50). Entweder
arbeitet die Abarbeitungsschleife den betreffenden Task Frame gerade ab oder sie
hat ihn gegen einen neuen ersetzt. Diese Konfliktsituation führt in keinem Fall zu
einem ungültigen Zustand des fremden Frame Stack, da die CAS Operation in dem
Fall keinen Austausch vornimmt. Ist der Austausch hingegen erfolgreich durchgeführt
worden, so beginnt der Thief einen neuen Cop auf seinem eigenen Frame Stack zu
erzeugen und den gestohlenen Task Frame darüberzukopieren (Zeilen 55–70). Hierbei
ist es notwendig zu beachten, dass ein anderer Thief seinen Frame Stack gerade scannen
könnte. Daher muss der Thief unbedingt sicherstellen, dass er beim Kopieren nicht
den Schalter parallel mit kopiert und erst anschließend löscht. Ansonsten könnte der
andere Thief versuchen, den während des Kopierens unvollständigen Task Frame zu
stehlen. Auf das Problem des Stehlens unvollständiger Task Frames wird im Folgenden
noch einmal genauer eingegangen.
Wichtig bei dem atomaren Ersetzen eines Task Frame durch einen neuen Thief ist,
95
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
dass mit dem Ersetzen der Einheit aus Funktionszeiger und dem Schalter parallel
der neue Thief vollständig ist. Da die Frame Stack Abarbeitungsschleife des Ziels beim
Stehlen nicht mehr blockiert wird, kann sie theoretisch direkt nach dem Austausch
den Frame abarbeiten. Würde der T HIEF Frame noch aus weiteren Daten bestehen,
so würde er zu dem Zeitpunkt jedoch unvollständig sein. Durch die Reduzierung des
T HIEF Frame auf den Funktionszeiger und den deaktivierten Schalter parallel
ist der Frame jedoch bereits nach dem atomaren Austauschen vollständig. Durch die
bereits in Unterunterabschnitt 3.11.1.4 beschriebene Reduzierung des T HIEF Frame
auf den Funktionszeiger und den Schalter parallel und die Signalisierung über die
Veränderung des Funktionszeigers wurde diese Anforderung umgesetzt.
Aus der direkten Überprüfung des fremden Frame Stack Pointers fsp im Thief
resultiert, dass der Thief Änderungen des Frame Stack Pointers seines Ziels live sieht,
welche durch das auf dem Zielprozessor in der Ausführung befindliche Unterprogramm
durchgeführt werden. Daher muss bei der Erzeugung neuer Frames auf dem Frame
Stack sichergestellt werden, dass ein Thief dennoch immer ausschließlich vollständige
Task Frames stiehlt.
Der Thief erkennt einen stehlbaren als parallel abarbeitbar markierten Task Frame
anhand des gesetzten Schalters parallel im Task Frame. Durch die fehlende Blo-
ckierung der Frame Stack Abarbeitungsschleife seines Ziels mit der anschließenden
erneuten Überprüfung des fremden Frame Stack Pointer kann zunächst nicht sicherge-
stellt werden, dass der Thief den fremden Frame Stack nicht über den obersten Frame
hinaus scannt. Für die korrekte Funktion der hier beschriebenen Synchronisation des
Stehlens ist es daher unbedingt notwendig, dass oberhalb des Frame Stack Pointer der
Schalter parallel immer deaktiviert ist. Dadurch wird verhindert, dass der Thief einen
vermeintlich als parallel ausführbar markierten Task Frame mit ungültigem Inhalt
stiehlt.
Weiterhin ist es notwendig, beim Erzeugen neuer Frames oberhalb des Frame Stack
Pointers sicherzustellen, dass der Schalter parallel erst dann gesetzt wird, wenn
der betreffende parallel abarbeitbare Task Frame ansonsten vollständig initialisiert
wurde. Dies kann nicht alleine durch die Reihenfolge der Befehle im Quelltext realisiert
werden, da der Prozessor die resultierenden Schreiboperationen mittels des instruction
reordering umsortieren könnte. Daher wurden einerseits das Makro zum Finalisieren
eines Unterprogramms (siehe Listing 3.39), welches die gesammelten Task Frames
des Current Stack auf den Frame Stack kopiert und andererseits die Bedeutung des
Schalters parallel angepasst.
Im Gegensatz zu der bisherigen Implementierung wurde die Bedeutung des Schalters
parallel folgendermaßen geändert. Der Schalter besitzt nun drei Zustände: „sequentiell
96
3.12 Synchronisation der parallelen Laufzeitumgebung für CES (CAS64)
289 /**
290 * Macro to finalize task procedure. The macro call must be placed at the end
291 * of the task procedure after any other task code.
292 *
293 * Moves the contents of the current stack to the top of the frame stack.
294 * @author Jens Remus \<JREMUS@de.ibm.com\>
295 *
296 * @param NAME the name of the task.
297 */
298 #define RUNTIME_TASK_FINALIZE(NAME)\
299 {\
300 memcpy((*fsp), csp, (cs_top - csp) * sizeof(TASK_FRAME));\
301 SFENCE;\
302 THREAD_DATA * td = &thread_data[OWN_ID(fsp)];\
303 TASK_FRAME * fs_top = *fsp + (cs_top - csp);\
304 while (*fsp < fs_top) {\
305 if ((*fsp)->parallel == 1) {\
306 (*fsp)->parallel = td->parallel_counter;\
307 td->parallel_counter++;\
308 if (td->parallel_counter < 2)\
309 td->parallel_counter = 2;\
310 }\
311 ++(*fsp);\
312 }\
313 --(*fsp);\
314 }
Listing 3.39: Definition des Makros zur Finalisierung eines Unterprogramms – run_
time.h
97
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
wird sichergestellt, dass die Abarbeitung (Zeilen 171–178) erst nach dem Ausführen
der atomaren Schreiboperation erfolgt. Ansonsten könnte es sein, dass die Frame Stack
Abarbeitungsschleife den Funktionszeiger liest, bevor das Zurücksetzen des Schalters
für die anderen Prozessoren sichtbar ist. Dann befände sich der Frame bereits in der
Abarbeitung und könnte dennoch durch einen Thief gestohlen werden.
Damit der Thief erkennen kann, ob der zu stehlende Task Frame inzwischen durch
die Frame Stack Abarbeitungsschleife durch einen ähnlichen parallel abarbeitbaren
Task Frame ersetzt wurde, enthält der Schalter parallel in der Codierung seiner drei
Zustände noch einen sogenannten „ABA“-Zähler.
Jeder Frame Stack besitzt einen eigenen „ABA“-Zähler, welcher beim Kopieren der Task
Frames vom Current Stack auf den Frame Stack verwendet wird (Listing 3.39, Zeilen 306–
309). Da der Zähler nach 232 − 2 erzeugten als parallel abarbeitbar markierten Task
Frames überläuft, ist er keine hundertprozentig sichere Lösung des Problems, jedoch
eine für diese Anwendung ausreichend sichere. Zwischen dem Scannen des fremden
Frame Stack und dem Stehlen müssten nicht nur diese 232 − 2 parallel abarbeitbaren
Task Frames auf diesem einen Frame Stack neu erzeugt, sondern auch bereits vollständig
abgearbeitet worden sein. Darüber hinaus würde aus dem Stehlen eines solchen Task
Frame kein ungültiger Zustand resultieren. Es würde nur nicht sichergestellt sein, dass
immer der unterste als parallel abarbeitbar markierte Task Frame eines Frame Stack
gestohlen würde. Dies würde lediglich zu einer vorzeitigen Blockierung der Frame Stack
Abarbeitungsschleife des betroffenen Frame Stack durch einen T HIEF Frame und
damit zum Stehlen führen.
98
3.13 Überprüfung der Korrektheit der parallelen Laufzeitumgebung für CES
99
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)
100
4 Leistungsbewertung der parallelen
Laufzeitumgebung für CES und
Vergleich mit der sequentiellen
Laufzeitumgebung für CES
Zur Bewertung der Leistung der parallelen Laufzeitumgebung für CES wurden an-
hand einer Reihe von Programmbeispielen Laufzeitmessungen sowohl mit der neuen
parallelen Laufzeitumgebung für CES als auch mit der angepassten sequentiellen Lauf-
zeitumgebung für CES vorgenommen und miteinander verglichen und in Beziehung
gesetzt.
Es wird ausschließlich die parallele Ausführung eines nebenläufigen CES Programms
mit Hilfe der beiden parallelen Laufzeitumgebungen für CES mit der sequentiellen
Ausführung desselben CES Programms mit Hilfe der sequentiellen Laufzeitumgebung
für CES verglichen.
Ein anderer Ansatz wäre der Vergleich eines auf die parallele Ausführung optimierten
CES Programms mit einem auf die sequentielle Ausführung optimierten CES Pro-
gramm. Ein weiterer Ansatz wäre der Vergleich von CES zu C oder anderen Systemen
der transparenten nebenläufigen Programmierung. Beide Ansätze verlagern die Unter-
suchung in Richtung der Optimierung der Testanwendungen hinsichtlich der jeweiligen
Testumgebungen, was über die Grenzen dieser Arbeit hinausgeht. Das Ziel dieser Arbeit
ist die Entwicklung und Untersuchung einer parallelen Laufzeitumgebung für CES.
Daher begrenzen sich die durchgeführten Untersuchungen auf CES.
Im Folgenden werden zunächst einige Grundlagen der Leistungsbewertung paralleler
Anwendungen erläutert. Anschließend werden die untersuchten Anwendungen und die
Ergebnisse ihrer Laufzeitmessungen unter Berücksichtigung der Grundlagen präsentiert.
Hierzu werden eine jede untersuchte Anwendung und ihre Implementierung in CES kurz
vorgestellt, die zu erwartenden Ergebnisse genannt und abschließend die gemessenen
Ergebnisse aufbereitet und diskutiert.
101
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
4.1 Grundlagen
Das Amdahlsche Gesetz (englisch: Amdahl’s Law) nach Gene M. Amdahl besagt, dass
die Beschleunigung S (englisch: speedup) eines Programms durch parallele Ausführung
vor allem vom seriellen Anteil s und nur geringfügig vom parallelen Anteil p des
Problems abhängig ist (mit s + p = 1). Weiterhin besagt es, dass die maximal erzielbare
Beschleunigung unter Verwendung von N Prozessoren N und damit linear ist. [Amd67]
s+p 1
S= = (Amdahlsches Gesetz)
s + p/N s + (1 − s)/N
Die maximale erzielbare Beschleunigung SN →∞ eines Programms ergibt sich durch
Berechnung des Grenzwerts des Amdahlschen Gesetzes für die Prozessorenanzahl N
gegen unendlich und unter Vernachlässigung des Kommunikationsoverheads wie folgt:
1 1
SN →∞ = lim = (4.1)
N →∞ s + (1 − s)/N s
Wie man sieht, wird die Beschleunigung ausschließlich vom seriellen Anteil s des
Problems nach oben begrenzt. Bei einem seriellen Anteil von 10 % ist die Beschleunigung
demnach auf 10,0 limitiert. Abbildung 4.1 zeigt die maximale Beschleunigung eines
Programms in Abhängigkeit seines seriellen Anteils.
Die theoretische maximale erzielbare Beschleunigung Ss→0 eines Programms für
eine gegebene Prozessorenanzahl N ergibt sich durch Berechnung des Grenzwerts des
Amdahlschen Gesetzes für den seriellen Anteil s des Problems gegen null wie folgt:
1
Ss→0 = lim =N (4.2)
s→0 s + (1 − s)/N
Damit ergibt sich für ein Programm in der Theorie maximal eine lineare Beschleuni-
gung bei der parallelen Ausführung. In der Praxis ist jedoch auch eine superlineare
Beschleunigung möglich, welche unter anderem aus der Aggregation der Prozessorcaches
102
4.1 Grundlagen
2000
Beschleunigung
1500
1000
800x
500 400x
200x
100x
50x 33x 25x
0
0% 1% 2% 3% 4%
Serieller Anteil
resultiert. Mit einer größeren Cachegröße wird die Speicherzugriffszeit drastisch redu-
ziert. Abbildung 4.2 zeigt den Bereich, in dem sich parallele Skalierung üblicherweise
bewegt.
Abbildung 4.3 illustriert die Anwendung des Amdahlschen Gesetzes anhand eines
einfachen Beispiels.
Das Gustafsonsche Gesetz (englisch: Gustafson’s Law) nach John L. Gustafson besagt,
dass die Beschleunigung S eines Programms hauptsächlich von der Anzahl an Prozes-
soren N abhängt, wenn man den seriellen Anteil s des Problems konstant halten kann
und gleichzeitig den parallelen Anteil p vervielfältigen kann. [Gus88]
S = s + N · p = N − (N − 1) · s (Gustafsonsches Gesetz)
Das Gesetz geht davon aus, dass der serielle Anteil s eines Problems bei der Ver-
vielfältigung des parallelen Anteils p konstant bleibt, so dass die theoretische Laufzeit
mit einem einzelnen Prozessor T1 = s + N · p beträgt und mit N Prozessoren nur
TN = s + p.
103
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
104
4.1 Grundlagen
105
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
Abbildung 4.4 illustriert die Anwendung des Gustafsonschen Gesetzes anhand eines
einfachen Beispiels.
Das Amdahlsche Gesetz basiert auf der Annahme einer fixen Problemgröße, wohingegen
das Gustafsonsche Gesetzt eine vielfache Problemgröße annimmt.
Das Amdahlsche Gesetz geht davon aus, dass sowohl der serielle Anteil als auch
der parallele Anteil eines Programms bei der Erhöhung der Prozessoranzahl konstant
bleiben und sich der parallele Anteil gleichmäßig auf die Prozessoren verteilt.
Das Gustafsonsche Gesetzt geht hingegen davon aus, dass der serielle Anteil eines
Programms bei der Erhöhung der Prozessoranzahl ebenfalls konstant bleibt, jedoch
gleichzeitig der parallele Anteil entsprechend der Prozessoranzahl vervielfältigt werden
kann, wobei ein jeder Prozessor genau einen parallelen Anteil zugeteilt bekommt.
Beide Gesetze können anhand folgender Metapher verdeutlicht werden: Angenommen
man reist in einem Auto zwischen zwei 60 km voneinander entfernten Städten und hat
nach einer Stunde Fahrzeit bereits die Hälfte der Strecke mit einer Durchschnittsge-
schwindigkeit von 30 km/h zurückgelegt.
Amdahls Gesetz: Egal wie schnell man die letzte Hälfte fährt: es ist unmöglich, eine
Gesamtdurchschnittsgeschwindigkeit von 90 km/h zu erreichen, bevor man die
Zielstadt erreicht hat. Da man bereits eine Stunde unterwegs war und eine
totale Distanz von 60 km/h zurückzulegen hat, würde man selbst bei unendlicher
Geschwindigkeit nur eine Gesamtdurchschnittsgeschwindigkeit von 60 km/h erzielen
können.
Gustafsonsches Gesetz: Unter der Annahme, es seien genug Zeit und Weg zum Reisen
gegeben, ist es letztenendes möglich, eine Gesamtdurchschnittsgeschwindigkeit
von 90 km/h zu erreichen, egal wie lange oder wie langsam man bereits gereist ist.
Im vorliegenden Fall könnte die Gesamtdurchschnittsgeschwindigkeit von 90 km/h
erreicht werden, indem man eine weitere Stunde mit einer Durchschnittsgeschwin-
digkeit von 150 km/h fährt.
106
4.2 Mess- und Auswertungsverfahren
Listing 4.1: Laufzeitmessung in CES mit Hilfe der CES Stopwatch Library
4.2.1 Messverfahren
Für die folgenden CES Programme wurden die Laufzeiten des jeweiligen Kernalgo-
rithmus unter Verwendung der angepassten sequentiellen Laufzeitumgebung für CES
und der beiden im Rahmen dieser Arbeit entwickelten parallelen Laufzeitumgebun-
gen für CES gemessen. Hierzu wurde eine eigens entwickelte CES Stopwatch Library
(stopwatch.h und stopwatch.ces) verwendet, welche das Messen von Zeitdifferenzen
mit Hilfe der ISO Zeitmessfunktionen time() und clock() der POSIX Zeitmessfunktion
times direkt aus CES heraus ermöglicht.
Listing 4.1 zeigt die Verwendung der CES Stopwatch Library zur Laufzeitmessung
innerhalb eines CES Programms. Beim Aufruf von stopwatch_start wird ein neuer
Markierungspunkt angelegt und mit der aktuellen Systemzeit initialisiert. Beim Aufruf
von stopwatch_stop wird die aktuelle Systemzeit mit der gespeicherten Zeitinformation
im Markierungspunkt verglichen und die Differenz formatiert auf der Standardausgabe
ausgegeben.
Die Laufzeiten der CES Programme wurden mit Hilfe eines Perl Skripts (benchmark.
pl) automatisiert gemessen und in einer SQLite Datenbank gespeichert. Mit Hilfe
eines weiteren Perl Skripts (figure.pl) und der Datenbank wurden die Messergebnisse
anschließend ausgewertet und als gnuplot Datendatei mit passender gnuplot Kom-
mandodatei gespeichert. Nach manuellen Anpassungen der Kommandodatei wurde mit
Hilfe von gnuplot schließlich eine EPS Graphik erzeugt.
Alle Laufzeitmessungen wurden auf einem Server der IBM Deutschland Entwicklung
GmbH, welcher insgesamt über acht Intel® Xeon® CPUs vom Typ E5345 mit jeweils
2,33 GHz und 4096 KB Cache verfügt, unter einem 32-Bit Red Hat Enterprise Linux mit
Kernel 2.6.9-42.0.10.ELsmp mit der GCC 4.2.2 durchgeführt. Weitere Informationen
zu dem Testserver finden sich im Anhang D.
Es wurden jeweils 25 Zeitmessungen für jede Kombination aus Threadanzahl N und
verwendeter Laufzeitumgebung für CES durchgeführt und in der Datenbank gespeichert.
107
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
Bei der Verwendung der parallelen Laufzeitumgebungen wurde die Threadanzahl N von
1 bis 8 variiert, so dass bei den Messungen mit mehreren Threads davon ausgegangen
werden kann, dass jeder Thread vom Betriebssystem auf einen eigenen Prozessor des
Testservers gescheduled wurde.
4.2.2 Auswertungsverfahren
Die Beschleunigung S der parallelen Ausführung einer Anwendung berechnet sich aus
den für die jeweilige Anzahl N an Prozessoren oder Threads gemessenen Laufzeiten tN
wie folgt:
t1
S= (4.3)
tN
tsequentiell
Srel = (4.4)
tparallel,N
Die Gleichung 4.4 normiert die aus der gemessenen Laufzeit resultierende Beschleunigun-
gen der untersuchten Anwendung unter Verwendung der parallelen Laufzeitumgebungen
für CES bezüglich der gemessenen Laufzeit unter Verwendung der sequentiellen Lauf-
zeitumgebung für CES. Die im Folgenden präsentierten Beschleunigungen sind daher
immer relativ zu der sequentiellen Laufzeit unter Verwendung der sequentiellen Laufzeit-
umgebung für CES. Dies ermöglicht den Vergleich der Laufzeitumgebungen innerhalb
der CES Umgebung, weil es den Synchronisationsoverhead zwischen den parallelen
Laufzeitumgebungen und der sequentiellen Laufzeitumgebung berücksichtigt.
Zur Auswertung wurden jeweils die 25 Zeitmessungen jeder Kombination aus Thread-
anzahl und verwendeter Laufzeitumgebung für CES herangezogen. Es wurden die
relativen Beschleunigungen der parallelen Ausführung mittels der beiden parallelen
Laufzeitumgebungen für CES im Vergleich zur sequentiellen Ausführung mittels der
sequentiellen Laufzeitumgebung für CES anhand der Gleichung 4.4 ermittelt. Für
die Laufzeit tsequentiell unter Verwendung der sequentiellen Laufzeitumgebung wurde
jeweils die minimale Laufzeit der 25 Messungen verwendet. Für die Laufzeiten tparallel,N
unter Verwendung der parallelen Laufzeitumgebungen für CES wurden jeweils die
minimale, maximale und durchnittliche Laufzeit der jeweils 25 Messungen verwendet
und somit jeweils die maximale, minimale und durschnittliche relative Beschleunigung
pro Threadanzahl berechnet.
108
4.2 Mess- und Auswertungsverfahren
Die Graphen zeigen jeweils die relative Beschleunigung S (Speedup) über der Anzahl
an Threads N der mittels der Pthreads Synchronisierungsprimitiven synchronisierten
parallelen Laufzeitumgebung für CES (Pthreads) und der mittels der atomaren Opera-
tionen synchronisierten parallelen Laufzeitumgebung für CES (CAS64) jeweils im Bezug
zu der sequentiellen Laufzeitumgebung für CES. Die ermittelten relativen Beschleuni-
gungen werden jeweils als Fehlerbalken ihrer jeweiligen minimalen, durschschnittlichen
und maximalen relativen Beschleunigung dargestellt. Zusätzlich werden die theoreti-
sche maximale Beschleunigung ohne Synchronisationsoverhead und die theoretischen
maximalen relativen Beschleunigungen der der beiden parallelen Laufzeitumgebungen
als gestrichelte Geraden dargestellt.
5
Speedup
2
sequentielle Laufzeitumgebung für CES
1 parallele Laufzeitumgebung für CES (CAS64)
parallele Laufzeitumgebung für CES (Pthreads)
0
1 2 3 4 5 6 7 8
Threads
Abbildung 4.5 zeigt eine Beispielauswertung eines Benchmark. Die blaue gestrichel-
te Gerade zeigt die theoretische maximale Beschleunigung Smax = N bei paralleler
Ausführung ohne Synchronisationsoverhead. Sie repräsentiert die perfekte lineare Skalie-
rung bezogen auf die sequentielle Ausführung mit der sequentiellen Laufzeitumgebung
für CES. Befindet sich die relative Beschleunigung auf dieser Geraden, dann ist die
parallele Ausführung exakt N -mal so schnell wie die sequentielle Ausführung. Die
relativen Beschleunigungen der mittels der atomaren Operationen synchronisierten
109
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
parallelen Laufzeitumgebung für CES (CAS64) sind jeweils als rote Fehlerbalken dar-
gestellt. Die relativen Beschleunigungen der mittels der Synchronisationsprimitiven
der POSIX Threads Bibliothek synchronisierten parallelen Laufzeitumgebung für CES
sind entsprechend jeweils als grüne Fehlerbalken dargestellt. Die beiden gestrichelten
Geraden in den zugehörigen Farben zeigen die jeweilige theoretische maximale relative
Beschleunigung Srel,max = N · Sparallel,1 der beiden parallelen Laufzeitumgebungen für
CES.
Aus den Graphen lassen sich folgende grundlegenden Informationen entnehmen:
• Die untere Linie der Fehlerbalken repräsentiert die jeweilige minimale gemessene
relative Beschleunigung, die obere Linie repräsentiert entsprechend die jeweilige
maximale gemessene relative Beschleunigung und die dazwischenliegende Mar-
kierung mittels eines „ד repräsentiert die durschschnittliche gemessene relative
Beschleunigung.
• Der Punkt (1; 1) auf der gestrichelten blauen Geraden repräsentiert das bes-
te Messergebnis der sequentiellen Ausführung des CES Programms mit Hilfe
der sequentiellen Laufzeitumgebung. Die sequentielle Ausführung mittels einer
der beiden parallelen Laufzeitumgebungen und einem einzelnen Thread kann
prinzipbedingt nicht darüberliegen, da bei ihnen der Synchronisationsoverhead
hinzukommt.
• Der Werts der relativen Beschleunigung unter Verwendung eines einzelnen Thread
Sparallel,1 auf der gestrichelten roten bzw. grünen Geraden repräsentiert die Per-
formance der parallelen Laufzeitumgebung gegenüber der sequentiellen Lauf-
zeitumgebung. Da der Wert immer kleiner oder gleich eins ist, repräsentiert er
darüberhinaus ein Maß für den notwendigen Synchronisationsoverhead der paralle-
len Ausführung. Beträgt die relative Beschleunigung beispielsweise Sparallel,1 = 0,8,
so ist die sequentielle Ausführung mittels der jeweiligen parallelen Laufzeitumge-
bung für CES aufgrund des Synchronisationsoverheads 0,8 mal so schnell wie die
sequentielle Ausführung mittels der sequentiellen Laufzeitumgebung für CES. Aus
dem Wert lässt sich ebenfalls die relative Laufzeit trel = 1/Sparallel,1 = 1/0,8 = 1,25
ermitteln. Der Wert der relativen Beschleunigung für einen Thread Sparallel,1
entspricht darüberhinaus direkt der Steigung der Geraden der relativen linearen
Skalierung Srel,max (N ) = N · Sparallel,1 . Dies ermöglicht daher auch die Bewertung
des parallelen Synchronisationsoverhead anhand der Steigung der Geraden.
110
4.3 Artifizielle Testprogramme
Die folgenden beiden Testprogramme sind keine Beispiele sinnvoller paralleler An-
wendungen. Das Fibonacci-Zahlen CES Programm, ein ausdrücklichs Negativbeispiel
der Parallelisierung, dient dem Leser lediglich zum Vergleich von CES mit anderen
Systemen zur transparenten nebenläufigen Programmierung. Das Aufsummieren der na-
türlichen Zahlen von 1–N CES Programm diente zunächst als primitives Testprogramm
und wird hier zur Untersuchung der Auswirkung der Granularität der Unterprogramme
auf die Skalierung der relativen Beschleunigung verwendet.
4.3.1 Fibonacci-Zahlen
Mit M n = M (n1 +n2 ) = M n1 · M n2 kann die Potenzierung der Matrix sequentiell sehr
1
Die Fibonacci Zahlenfolge und insbesondere deren rekursive Berechnung ist ein denkbar schlechtes
Beispiel für die Parallelisierung, da zur Berechnung von f ib(n−1) durch die Rekursion f ib(n−1−1) =
f ib(n − 2) und somit f ib(n − 2) in jeder Rekursionsstufe zweimal berechnet wird. Damit steigt
der Aufwand gegenüber der iterativen seriellen Berechnung exponentiell an, so dass die parallele
Berechnung auf mehreren Prozessoren sogar langsamer als die iterative serielle Berechnung auf
einem Prozessor ist. Das Beispiel wird hier dennoch vorgestellt und untersucht, da es in dieser
Form sowohl in der Literatur zu paralleler Programmierung vorkommt [LP98, S. 2f][RK07, S. 3]
als auch als (Einführungs-)Beispiel von anderen Systemen zur parallelen Programmierung genutzt
wird [Int07e, S. 50ff][Lea00, S. 2ff][Sup07, S. 4ff][Blu95, S. 54ff][Ope07, S. 179f] und zur Motivation
neuer multi-core CPUs verwendet wird [Sie07]. Dem Leser soll somit ein einfacher Vergleich der
Implementierung in CES mit den Implementierungen der anderen Systeme ermöglicht werden.
111
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
5
Speedup
0
1 2 3 4 5 6 7 8
Threads
Abbildung 4.6 zeigt das Ergebnis der Leistungsmessung des CES Unterprogramms
fibonacci. Die Beschleunigung für einen Thread beträgt für die Synchronisation
mittels CAS64 S¯1 = 0,74 und für die Synchronisation mittels Pthreads S¯1 = 0,29.
Der parallele Synchronisationsoverhead ist groß gegenüber der feinen Granularität der
Unterprogramme. Ist dieser parallele Synchronisationsoverhead einmal bezahlt, skaliert
die Anwendung linear.
112
4.3 Artifizielle Testprogramme
1 /**
2 * CES Subroutine FIBONACCI:
3 * Recursive computation of the nth Fibonacci number.
4 *
5 * @param[in] n the Fibonacci number to compute.
6 * @param[out] result the nth Fibonacci number.
7 */
8 $fibonacci(uint32_t n;;uint64_t result){
9 if ($n$ <= 1)
10 $result$ = $n$;
11 else {
12 uint32_t n1 = $n$ - 1;
13 uint32_t n2 = $n$ - 2;
14
15 $parallel fibonacci(uint32_t n1;;uint64_t fib1);$
16 $parallel fibonacci(uint32_t n2;;uint64_t fib2);$
17 $add_uint64(fib1, fib2;;result);$
18 }
19 }$
1 /**
2 * CES Subroutine ADD_UINT64:
3 * Adds the two numbers \a a and \a b of type uint64_t (result := a + b).
4 *
5 * @param[in] a the number a.
6 * @param[in] b the number b.
7 * @param[out] result the sum of a and b.
8 */
9 $add_uint64(uint64_t a, uint64_t b;;uint64_t result){
10 $result$ = $a$ + $b$;
11 }$
113
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
Das Programm berechnet die Summe der natürlichen Zahlen von 1–n durch rekursive
Zerlegung des Intervalls. Enthät ein Teilintervall weniger als die durch granularity
bestimmte Menge an Zahlen, so summiert es diese in einer Schleife auf. Dadurch kann
die Granularität des Unterprogramms sumnm variiert werden, um die Performance
in Abhängigkeit des Arbeitsaufwands eines Unterprogramms zu untersuchen. Zur
Verifikation berechnet das Programm die Summe zusätzlich mit Hilfe des Gaußschen
Summenalgorithmus:
n
n(n + 1)
sumgauss(n) = i= (4.8)
X
i=1
2
Der CES Quelltext der parallelen Aufsummierung der natürlichen Zahlen von 1–
n durch rekursive Zerlegung ist in Listing 4.5 und Listing 4.6 wiedergegeben, die
Implementierung des Gaußschen Summenalgorithmus in CES in Listing 4.4.
Bei der Variation der Granularität des Unterprogramms sumnm ist zu erwarten, dass
das Programm bei ausreichend großem n mit steigender Granularität steiler skaliert und
beim Überschreiten einer bestimmten Granularität schlechter skaliert. Bei zu geringer
Granularität des Unterprogramms ist der Ausführungs- und Synchronisierungsoverhead
im Verhältnis zum Aufwand das Unterprogramms zu gering und das Programm weist
eine flache, aber lineare Skalierung aufgrund ausreichender paralleler Arbeit auf. Wenn
die Granularität allerdings für das gewählte n zu groß ist, so dass nicht ausreichend
parallele Arbeit zur Verfügung steht, dann skaliert das Programm nicht.
Abbildung 4.7 zeigt das Ergebnis der Leistungsmessung des CES Unterprogramms
sumnm zur Aufsummierung der natürlichen Zahlen von 1–n mit der Granularität 1. Bei
der geringen Granularität ist eine flache, aber perfekte Skalierung zu erwarten. Diese
Erwartung wird durch das Messergebnis bestätigt. Die Beschleunigung für einen Thread
beträgt für die Synchronisation mittels CAS64 S¯1 = 0,77 und für die Synchronisation
mittels Pthreads S¯1 = 0,31.
Abbildung 4.8 und Abbildung 4.9 zeigen die Ergebnisse der Leistungsmessung bei
Variation der Granularität des Unterprogramms sumnm für die CAS64 und Pthreads Syn-
chronisation der parallelen Laufzeitumgebung für CES. Mit steigender Granularität ist
eine zunehmend steilere, anfangs perfekte, aber zunehmend schlechtere Skalierung zu
erwarten. Die Ergebnisse der Pthreads Synchronisation bestätigt diese Erwartungen,
die Ergebnisse der CAS64 Synchronisation hingegen nicht. Allerdings wird angenom-
men, dass die wesentlich höhere Performance der CAS64 Synchronisation dazu führt,
114
4.3 Artifizielle Testprogramme
5
Speedup
0
1 2 3 4 5 6 7 8
Threads
Abbildung 4.7: Skalierung der Implementierung des Aufsummierens der ersten N na-
türlichen Zahlen in CES
dass das gewählte n nicht ausreicheichend groß genug ist, damit alle acht Prozessoren
ausgelastet werden können.
115
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
5
Speedup
0
1 2 3 4 5 6 7 8
Threads
Abbildung 4.8: Skalierung der Implementierung des Aufsummierens der ersten N na-
türlichen Zahlen in CES (CAS64)
5
Speedup
0
1 2 3 4 5 6 7 8
Threads
Abbildung 4.9: Skalierung der Implementierung des Aufsummierens der ersten N na-
türlichen Zahlen in CES (Pthreads)
116
4.3 Artifizielle Testprogramme
1 /**
2 * CES Subroutine SUMGAUSS:
3 * Summiert die ersten n aufeinander folgenden natürlichen Zahlen mit Hilfe der
4 * Gaußschen Summenformel.
5 * @see http://de.wikipedia.org/wiki/Gaußsche_Summenformel
6 *
7 * @param[in] n die obere Grenze.
8 * @param[out] result die Summe der natürlichen Zahlen von 1 bis n.
9 */
10 $sumgauss(uint32_t n;; uint64_t result){
11 $result$ = (uint64_t)$n$*((uint64_t)$n$+1)/2;
12 }$
Listing 4.4: Aufsummierung der natürlichen Zahlen von 1–n in CES (CES Unterpro-
gramm sumgauss) – sumn.ces
1 /**
2 * CES Subroutine SUMNM:
3 * Summiert die aufeinander folgenden natürlichen Zahlen von n bis m unter
4 * Anwendung des divide and conquer (D&C) Lösungsansatzes.
5 *
6 * @param[in] n die untere Grenze.
7 * @param[in] m die obere Grenze.
8 * @param[out] result die Summe der natürlichen Zahlen von n bis m.
9 */
10 $sumnm(uint32_t n, uint32_t m;;uint64_t result){
11 assert($n$ <= $m$);
12
13 /* sum or divide and conquer? */
14 if ($m$ - $n$ < granularity) {
15 /* sum numbers between [n:m] */
16 uint32_t k = $n$;
17 uint64_t sum = $n$;
18 while (k < $m$) {
19 sum += ++k;
20 }
21 $result$ = sum;
22 }
23 else {
24 /* divide and conquer */
25 uint32_t split1 = ((uint64_t)$m$+(uint64_t)$n$)/2;
26 uint32_t split2 = split1 + 1;
27 $parallel sumnm(n, uint32_t split1;;uint64_t sum1);$
28 $parallel sumnm(uint32_t split2, m;;uint64_t sum2);$
29 $add_uint64(sum1, sum2;;result);$
30 }
31 }$
Listing 4.5: Aufsummierung der natürlichen Zahlen von 1–n in CES (CES Unterpro-
gramm sumnm) – sumn.ces
117
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
1 /**
2 * CES Subroutine ADD_UINT64:
3 * Addiert die beiden Zahlen a und b vom Typ uint64_t (result := a + b).
4 *
5 * @param[in] a die Zahl a.
6 * @param[in] b die Zahl b.
7 * @param[out] result die Summe der beiden Zahlen a und b.
8 */
9 $add_uint64(uint64_t a, uint64_t b;;uint64_t result){
10 $result$ = $a$ + $b$;
11 }$
Listing 4.6: Aufsummierung der natürlichen Zahlen von 1–n in CES (CES Unterpro-
gramm add_uint64) – sumn.ces
118
4.4 N-Damen Problem
5
Speedup
0
1 2 3 4 5 6 7 8
Threads
Abbildung 4.10: Skalierung der divide and conquer Implementierung des N -Damen
Problems in CES
Abbildung 4.10 zeigt das Ergebnis der Leistungsmessung des CES Unterprogramms
nqueens. Die Beschleunigung für einen Thread beträgt für die Synchronisation mittels
CAS64 S¯1 = 0,82 und für die Synchronisation mittels Pthreads S¯1 = 0,59. Die mit
119
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
1 /**
2 * CES Subroutine NQUEENS:
3 * Solves the N-Queen-Problem.
4 *
5 * @param[in] n the size of the game board and the number of queens to place.
6 * @param[out] count the number of distinct solutions.
7 */
8 $nqueens(int n;; uint32_t count){
9 board_t board = new_board($n$); /* initial empty board */
10
11 #define ZERO 0 /* define-trick to pass value as argument to CES call */
12
13 /* solve N-Queens-Problem starting at row 0 */
14 $nqueens_recursive(n, board_t board, int ZERO;; count);$
15 }$
120
4.4 N-Damen Problem
1 /**
2 * CES Subroutine NQUEENS_RECURSIVE:
3 * Trys to place a queen on row \a row.
4 * The subroutine tries to place one queen per row, one row after the other from the
5 * top to the bottom of the game board. If row == n a solution has been found
6 * and the solutions counter is incremented.
7 *
8 * @param[in] n the size of the game board and the number of queens to place.
9 * @param[in] board the game board.
10 * @param[in] row the row that the subroutine should try to place a queen on.
11 * @param[in,out] count the distinct solutions counter.
12 */
13 $nqueens_recursive(int n, board_t board, int row;;uint32_t count){
14 if ($n$ == $row$) {
15 /* a solution has been found: increment counter and free board */
16 $count$ = 1;
17 } else {
18 /* try to place queen on board in row, spawn childs and free board */
19 $count$ = 0;
20 int col;
21 for (col = 0; col < $n$; ++col) {
22 if (test_board($board$, $row$, col)) {
23 /* this column in this row is a valid place for a queen on the board */
24 board_t child_board = new_board($n$);
25 copy_board(child_board, $board$, $n$);
26 child_board[$row$] = col;
27 int child_row = $row$ + 1;
28 $parallel nqueens_recursive(n, board_t child_board, int child_row;; uint32_t '
child_count);$
29 $sum_uint32(child_count; count;);$
30 }
31 }
32 }
33 /* free parent board memory */
34 free_board($board$);
35 }$
1 /**
2 * CES Subroutine SUM_UINT32:
3 * sum := sum + a
4 *
5 * @param[in] a the Number a.
6 * @param[in,out] sum the sum of sum and a.
7 */
8 $sum_uint32(uint32_t a; uint32_t sum;){
9 $sum$ += $a$;
10 }$
121
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
1 /**
2 * C Subroutine TEST_BOARD:
3 * Tests if a queen can be placed at (row, col) without attacking already played
4 * queens.
5 * @pre The function assumes that the queens placed in the rows above row are
6 * conflict-free (i.e. not attacking each other) and that there are no
7 * queens placed below row.
8 *
9 * @param board the game board.
10 * @param row the row to place the new queen.
11 * @param col the column to place the new queen.
12 * @return True, if a queen can be placed at (row, col), else false.
13 */
14 static inline int test_board(const board_t board, const int row, const int col) {
15 int i;
16 /* only check against previous rows if row > 0 */
17 for (i = 1; i <= row; ++i) {
18 int col_i = board[row-i];
19 /* vertical up-right diagonal up-left diagonal */
20 if ((col_i == col) || (col_i == col+i) || (col_i == col-i))
21 return 0; /* failure: abort and return false */
22 }
23 return 1; /* success: return true */
24 }
4.5 Sortieralgorithmen
4.5.1 Mergesort
122
4.5 Sortieralgorithmen
5
Speedup
0
1 2 3 4 5 6 7 8
Threads
Mergesort (mergesort.ces) für ein unsortiertes Array mit 10×106 zufälligen2 32 Bit
Integern (int). Die Beschleunigung für einen Thread beträgt für die Synchronisation
mittels CAS64 S¯1 = 0,95 und für die Synchronisation mittels Pthreads S¯1 = 0,71. Ein
Teil der abfallenden Skalierung lässt sich durch das serielle merge Unterprogramm
begründen.
2
Die Implementierung des Zufallszahlengenerators von C stellt sicher, dass bei gleicher Arraygröße
immer dieselbe Zufallszahlenfolge als Eingabe verwendet wurde.
123
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
1 /**
2 * CES Subroutine SORT_MERGESORT:
3 * Implements John von Neumann’s Mergesort algorithm.
4 *
5 * This subroutine sorts the items of size \a size of the array \a array of
6 * length \a n using the comparison function \a compare.
7 * The comparison function must be of type \a compare_t which takes two
8 * pointers to two elements to compare and returns an integer based on
9 * the result of the comparison:
10 * - If *element1 < *element2, compare should return an integer < 0.
11 * - If *element1 == *element2, compare should return 0.
12 * - If *element1 > *element2, compare should return an integer > 0.
13 *
14 * @param[in] n the number of elements.
15 * @param[in] size the size of an element (hint: sizeof()).
16 * @param[in] compare the pointer to the element comparison function of type
17 * compare_t.
18 * @param[in,out] array the array to sort.
19 */
20 $sort_mergesort(size_t n, size_t size, compare_t compare; ptr_t array;){
21 if ($n$ > 1) {
22 /* create a temporary copy of the array */
23 ptr_t temp = malloc($n$ * $size$);
24 memcpy(temp, $array$, $n$ * $size$);
25 /* sort temporary copy into array */
26 $mergesort_internal(n, size, compare; ptr_t temp, array;);$
27 $free_ptr(ptr_t temp;;);$
28 }
29 }$
124
4.5 Sortieralgorithmen
1 /**
2 * CES Subroutine MERGESORT_INTERNAL:
3 * Internal implementation of parallel Mergesort using pointers to array elements
4 * and a copy of the array to be sorted.
5 * The Recursion will continously swap src’s and dst’s meaning, i.e. sort elements
6 * by moving them from src to dst or dst to src, but assures that the last step is
7 * always to move all elements from src to dst.
8 * @see Iwanowski, Sebastian: "Grundlagen der Theoretischen Informatik",
9 * Kapitel 4 "Einführung in die Komplexitätstheorie", Folie 13, WS 2007/08.
10 * http://www.fh-wedel.de/fileadmin/mitarbeiter/iw/Lehrveranstaltungen/2007WS/GTI/GTI4.pdf
11 *
12 * @param[in] n the number of elements.
13 * @param[in] size the size of an element (hint: sizeof()).
14 * @param[in] compare the pointer to the element comparison function of type
15 * compare_t.
16 * @param[in,out] src the copy of the array to sort.
17 * @param[in,out] dst the array to sort. The sorted result will be stored here.
18 */
19 $mergesort_internal(size_t n, size_t size, compare_t compare; ptr_t src, ptr_t dst;){
20 if ($n$ <= 1) {
21 /* array has zero or one element(s) and is sorted by default */
22 } else {
23 /* divide and conquer */
24 size_t nleft = $n$ / 2;
25 size_t nright = $n$ - nleft;
26 ptr_t right_src = $src$ + nleft * $size$;
27 ptr_t right_dst = $dst$ + nleft * $size$;
28
29 $parallel mergesort_internal(size_t nleft, size, compare; dst, src;);$
30 $parallel mergesort_internal(size_t nright, size, compare; ptr_t right_dst, ptr_t '
right_src;);$
31 $merge(src, size_t nleft, ptr_t right_src, size_t nright, size, compare; dst;);$
32 }
33 }$
125
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
1 /**
2 * CES Subroutine MERGE:
3 * Merges two sorted arrays \a left_src and \a right_src into one sorted array \a dst.
4 *
5 * @param[in] left_src the first array.
6 * @param[in] nleft the number of elements of the first array.
7 * @param[in] right_src the second array.
8 * @param[in] nright the number of elements of the second array.
9 * @param[in] size the size of an element (hint: sizeof()).
10 * @param[in] compare the pointer to the element comparison function of type
11 * compare_t.
12 * @param[in,out] dst the destination array.
13 */
14 $merge(ptr_t left_src, size_t nleft, ptr_t right_src, size_t nright, size_t size, compare_t '
compare; ptr_t dst;){
15 ptr_t dst_ptr = $dst$;
16 ptr_t left_ptr = $left_src$;
17 ptr_t leftend_ptr = $left_src$ + ($nleft$ * $size$);
18 ptr_t right_ptr = $right_src$;
19 ptr_t rightend_ptr = $right_src$ + ($nright$ * $size$);
20
21 while ((left_ptr < leftend_ptr) && (right_ptr < rightend_ptr)) {
22 if ($compare$(left_ptr, right_ptr) <= 0) {
23 /* append left element to destination array */
24 memcpy(dst_ptr, left_ptr, $size$);
25 left_ptr += $size$;
26 } else {
27 /* append right element to destination array */
28 memcpy(dst_ptr, right_ptr, $size$);
29 right_ptr += $size$;
30 }
31 dst_ptr += $size$;
32 }
33 /* append remaining left elements to destination array */
34 if (left_ptr < leftend_ptr) {
35 memcpy(dst_ptr, left_ptr, leftend_ptr - left_ptr);
36 dst_ptr += leftend_ptr - left_ptr;
37 }
38 /* append remaining right elements to destination array */
39 if (right_ptr < rightend_ptr) {
40 memcpy(dst_ptr, right_ptr, rightend_ptr - right_ptr);
41 dst_ptr += rightend_ptr - right_ptr;
42 }
43 }$
126
4.5 Sortieralgorithmen
4.5.2 Quicksort
Der Quelltext der Implementierung von Quicksort in CES ist in Listing 4.14, Lis-
ting 4.15, Listing 4.16 und Listing 4.17 wiedergegeben.
5
Speedup
0
1 2 3 4 5 6 7 8
Threads
Abbildung 4.12 zeigt das Ergebnis der Leistungsmessung des CES Programms
Quicksort (quicksort.ces) für ein unsortiertes Array mit 10 × 106 zufälligen2 32 Bit
Integern (int). Die Beschleunigung für einen Thread beträgt für die Synchronisation
mittels CAS64 S¯1 = 0,94 und für die Synchronisation mittels Pthreads S¯1 = 0,81. Ein
Teil der abfallenden Skalierung lässt sich durch das serielle partition Unterprogramm
begründen.
127
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
1 /**
2 * CES Subroutine SORT_QUICKSORT:
3 * Implements Sir Charles Antony Richard Hoare’s Quicksort algorithm.
4 *
5 * This subroutine sorts the items of size \a size of the array \a array of
6 * length \a n using the comparison function \a compare.
7 * The comparison function must be of type \a compare_t which takes two
8 * pointers to two elements to compare and returns an integer based on
9 * the result of the comparison:
10 * - If *element1 < *element2, compare should return an integer < 0.
11 * - If *element1 == *element2, compare should return 0.
12 * - If *element1 > *element2, compare should return an integer > 0.
13 *
14 * @param[in] n the number of elements.
15 * @param[in] size the size of an element (hint: sizeof()).
16 * @param[in] compare the pointer to the element comparison function of type
17 * compare_t.
18 * @param[in,out] array the array to sort.
19 */
20 $sort_quicksort(size_t n, size_t size, compare_t compare; ptr_t array;){
21 ptr_t left = $array$;
22 ptr_t right = $array$ + ($n$-1) * $size$;
23
24 $quicksort_internal(ptr_t left, ptr_t right, size, compare;;);$
25 }$
1 /**
2 * CES Subroutine QUICKSORT_INTERNAL:
3 * Internal implementation of parallel Quicksort using pointers to array elements.
4 *
5 * @param[in] left the left array border element pointer.
6 * @param[in] right the right array border element pointer.
7 * @param[in] size the size of an array element (hint: sizeof()).
8 * @param[in] compare the pointer to the element comparison function of type
9 * compare_t.
10 *
11 * @note The array elements between \a left and \a right might get swapped
12 * around (get sorted). This is somewhat a side-effect requarding CES’s
13 * idea. Exceptional use of such technique here as the use of pointers is
14 * faster then an index based approach.
15 */
16 $quicksort_internal(ptr_t left, ptr_t right, size_t size, compare_t compare;;){
17 if ($left$ < $right$) {
18 ptr_t pivot = partition($left$, $right$, $size$, $compare$);
19
20 /* define-trick to pass values as argument to CES calls */
21 #define PIVOT_MINUS_SIZE (pivot - $size$)
22 #define PIVOT_PLUS_SIZE (pivot + $size$)
23
24 $parallel quicksort_internal(left, ptr_t PIVOT_MINUS_SIZE, size, compare;;);$
25 $parallel quicksort_internal(ptr_t PIVOT_PLUS_SIZE, right, size, compare;;);$
26 }
27 }$
128
4.5 Sortieralgorithmen
1 /**
2 * C Subroutine PARTITION:
3 * Partitions the array and returns pointer to pivot element.
4 *
5 * @param left the left array border element pointer.
6 * @param right the right array border element pointer.
7 * @param size the size of an array element.
8 * @param compare a function pointer to the array element comparison function.
9 * @return the pivot array pointer (left <= pivot <= right).
10 *
11 * @note Uses SWAP macro.
12 */
13 static inline void * partition(void * left, void * right, const size_t size, const compare_t '
compare) {
14 void * i = left - size;
15 void * j = right;
16 void * pivot = right;
17 void * buffer = __builtin_alloca(size); /* buffer for swap operation on stack */
18
19 do {
20 /* search an element from the left that is greater or equal the pivot element */
21 do {i += size;} while (compare(i, pivot) < 0);
22 /* search an element from the right that is less than or equal the pivot element */
23 do {j -= size;} while ((left < j) && (compare(j, pivot) > 0));
24
25 if (i < j) SWAP(i, j, buffer, size);
26 } while (i < j);
27
28 SWAP(i, pivot, buffer, size);
29 return i;
30 }
1 /**
2 * Marco to swap element \a e1 with element \a e2 both of size \a size using
3 * temporary buffer \a temp.
4 *
5 * @param[in] e1 the pointer to the first (array) element.
6 * @param[in] e2 the pointer to the second (array) element.
7 * @param[in] temp the pointer to the temporary buffer for an element.
8 * @param[in] size the size of an (array) element in bytes.
9 *
10 * @pre This swap operation requires memcpy() to swap the data and therefore
11 * the inclusion of stdlib.h or memory.h.
12 */
13 #define SWAP(e1, e2, temp, size)\
14 do {\
15 memcpy((temp), (e1), (size));\
16 memcpy((e1), (e2), (size));\
17 memcpy((e2), (temp), (size));\
18 } while (0)
129
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
4.6 Suchalgorithmen
4.6.2 Suche nach dem Minimum und oder Maximum eines unsortierten
Array
5
Speedup
0
1 2 3 4 5 6 7 8
Threads
Abbildung 4.13: Skalierung der Implementierung von der Suche nach dem Minimum
eines unsortierten Array in CES
130
4.6 Suchalgorithmen
5
Speedup
0
1 2 3 4 5 6 7 8
Threads
Abbildung 4.14: Skalierung der Implementierung von der Suche nach dem Maximum
eines unsortierten Array in CES
Die Beschleunigung für einen Thread beträgt sowohl für die Suche nach dem Mini-
mum als auch nach dem Maximum für die Synchronisation mittels CAS64 S¯1 = 0,67
und für die Synchronisation mittels Pthreads S¯1 = 0,29. Ist dieser parallele Synchroni-
sationsoverhead einmal bezahlt, skaliert die Anwendung linear.
131
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
132
4.7 MemCopy
4.7 MemCopy
Listing 4.20 zeigt eine divide and conquer Implementierung von MemCopy zum
parallelen Kopieren von Hauptspeicherinhalten. Die Implementierung verwendet ei-
ne Granularität von 4 MB Blöcken, welche mit der Standardimplementierung von
MemCopy in C memcpy(...) aus stdlib.h kopiert werden.
Bei dem Beispiel ist zu erwarten, dass die maximale Bandbreite des Speicherinterface
zwischen Hauptspeicher und den Prozessoren bereits mit wenigen Threads erreicht
ist. Es ist weiterhin denkbar, dass die Kopierleistung mit weiteren Threads sogar
wieder nachlässt, da der Speichercontroller die parallelen Zugriffe der Prozessoren
synchronisieren muss.
5
Speedup
0
1 2 3 4 5 6 7 8
Threads
Abbildung 4.15: Skalierung der divide and conquer Implementierung von MemCopy in
CES
Abbildung 4.15 zeigt das Ergebnis der Leistungsmessung des CES Programms Mem-
Copy (memcopy.ces) für einen zu kopierenden Hauptspeicherbereich von 1024 MB =
1 GB. Die Beschleunigung für einen Thread beträgt für die Synchronisation mittels
CAS64 S¯1 = 0,99 und für die Synchronisation mittels Pthreads S¯1 = 0,98. Wie zu
erwarten skaliert das Beispielprogramm sehr schlecht, da vermutlich der maximal
mögliche Speicherdurchsatz schon mit zwei Prozessoren erreicht ist.
133
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
1 /**
2 * CES Subroutine PMEMCOPY:
3 * Parallel MemCopy.
4 *
5 * @param[in] src the source memory address.
6 * @param[in] n the number of bytes to copy.
7 * @param[in,out] dst the destination memory address.
8 */
9 $pmemcopy(ptr_t src, size_t n; ptr_t dst;){
10 if ($n$ > granularity) {
11 /* divide and conquer */
12 int n1 = $n$ / 2;
13 int n2 = $n$ - n1;
14 ptr_t src2 = $src$ + n1;
15 ptr_t dst2 = $dst$ + n1;
16 $parallel pmemcopy(src, int n1; dst;);$
17 $parallel pmemcopy(ptr_t src2, int n2; ptr_t dst2;);$
18 } else {
19 memcpy($dst$, $src$, $n$);
20 }
21 }$
134
4.8 Numerische Integration
5
Speedup
0
1 2 3 4 5 6 7 8
Threads
1 /**
2 * CES Subroutine INTEGRATE:
3 * Computation of the integral of the function \a f between the given \a lower
4 * and \a upper bounds.
5 *
6 * @param[in] lower the lower bound.
7 * @param[in] upper the upper bound.
8 * @param[in] f the function of type function_t to integrate.
9 * @param[out] result the integral of f between lower and upper.
10 */
11 $integrate(double lower, double upper, function_t f;;double result){
12 const double f_lower = $f$($lower$);
13 const double f_upper = $f$($upper$);
14 const double initial_area = 0.5 * ($upper$ - $lower$) * (f_lower + f_upper);
15
16 $quadrature(lower, upper, f, double f_upper, double f_lower, double initial_area;; result);'
$
17 }$
135
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
1 /**
2 * CES Subroutine QUADRATURE:
3 * Parallel recursive computation of the integral of the function \a f between
4 * the \a lower and \a upper bounds using the gaussian quadrature for numerical
5 * integration.
6 *
7 * @param[in] left the lower bound.
8 * @param[in] right the upper bound.
9 * @param[in] f the function of type function_t to integrate.
10 * @param[in] f_lower the function value of f at the lower bound.
11 * @param[in] f_upper the function value of f at the upper bound.
12 * @param[in] initial_area the initial area.
13 * @param[out] result the area of f between lower and upper.
14 */
15 $quadrature(double lower, double upper, function_t f, double f_lower, double f_upper, double '
initial_area;;double result){
16 /* macros to simplify access to CES parameter variables */
17 #define lower ($lower$)
18 #define upper ($upper$)
19 #define f ($f$)
20 #define f_lower ($f_lower$)
21 #define f_upper ($f_upper$)
22 #define initial_area ($initial_area$)
23
24 const double center = 0.5 * (lower + upper);
25 const double f_center = f(center);
26
27 const double lower_area = 0.5 * (center - lower) * (f_lower + f_center);
28 const double upper_area = 0.5 * (upper - center) * (f_center + f_upper);
29 const double area = lower_area + upper_area;
30
31 const double diff = fabs(area - initial_area);
32
33 if (diff < error_tolerance) {
34 $result$ = area;
35 } else {
36 /* Divide and Conquer (D&C) */
37 $parallel quadrature(lower, double center, f, f_lower, double f_center, double '
lower_area;; double new_lower_area);$
38 $parallel quadrature(double center, upper, f, double f_center, f_upper, double '
upper_area;; double new_upper_area);$
39 $add_double(new_lower_area, new_upper_area;;result);$
40 }
41 }$
136
4.8 Numerische Integration
1 /**
2 * CES Subroutine ADD_DOUBLE:
3 * Adds the two numbers \a a and \a b of type double (result := a + b).
4 *
5 * @param[in] a the number a.
6 * @param[in] b the number b.
7 * @param[out] result the sum of a and b.
8 */
9 $add_double(double a, double b;;double result){
10 $result$ = $a$ + $b$;
11 }$
137
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
Die Mandelbrot Menge ist definiert als die Menge aller komplexen Zahlen c, für
welche die rekursiv definierte Folge komplexer Zahlen zn mit dem Bildungsgesetz
138
4.9 Mandelbrot Menge – Apfelmännchen
In der Bitmapgraphik wird die Mandelbrot Menge (schwarz) in der komplexen Ebene
dargestellt, wobei für einen jeden Bildpunkt überprüft wird, ob die rekursiv definierte
Folge beschränkt bleibt (schwarz) oder nicht (weiß). Hierzu wird die Bildpunktkoordi-
nate (x, y) in eine Koordinate c = Im · i + Re der komplexen Ebene transformiert und
die rekursive Folge mittels Gleichung 4.9 berechnet. Die Berechnung bricht ab, wenn
ein Folgeglied einen vorgegebenen Bereich verlässt oder eine vorgegebene maximale
Iterationstiefe erreicht wurde. Verlässt ein Folgeglied den zulässigen Bereich, so wird
die Folge für die Koordinate als unbeschränkt angenommen (weiß). Wird hingegen die
maximale Iterationstiefe erreicht, so wird die Folge für die Koordinate als beschränkt
angenommen (schwarz).
Die Berechnung und Visualisierung der Mandelbrot Menge ist ein sehr gut paral-
lelisierbares Beispiel, da die Berechnung eines jeden Bildpunkts der Visualisierung
unabhängig ist. In der hier präsentierten Implementierung wird das Divide and Conquer
Konzept zur rekursiven Aufteilung der Arbeit angewandt und mit einer Granularität
von Bitmappixelzeilen gearbeitet.
5
Speedup
0
1 2 3 4 5 6 7 8
Threads
Abbildung 4.18: Nahezu lineare Skalierung der divide and conquer Implementierung
der Berechnung der Mandelbrot Menge in CES
Abbildung 4.18 zeigt das Ergebnis der Leistungsmessung des CES Programms zur
Berechnung und Visualisierung der Mandelbrot Menge (mandelbrot.ces) für den
Ausschnitt Re = [−2.25; 0.75] sowie Im = [−1.25; 1.25] mit 800 Bildpunkten pro
139
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
Einheit und der maximalen Iterationstiefe von 2000 Schritten. Wie zu erwarten skaliert
das Beispielprogramm sehr gut. Die Beschleunigung für einen Thread beträgt sowohl für
die Synchronisation mittels CAS64 als auch für die Synchronisation mittels Pthreads
S¯1 = 1,00. Die Granularität ist so grob, dass der parallele Synchronisationsoverhead
im Vergleich verschwindend gering ist.
Abbildung 4.19: Graphische Darstellung der Mandelbot Menge mit farblicher Hervor-
hebung der den jeweiligen Threads zugeordneten Zeilen (8 Threads)
Anhand der graphischen Darstellung der Mandelbrot Menge lässt sich auch die auto-
matische Lastverteilung durch das work-stealing sehr gut visualisieren. Abbildung 4.19
zeigt eine mit dem CES Programm erzugte Bitmapgraphik der Mandelbrot Menge mit
farblich markierten Zeilen. Die einzelnen Bitmappixel und damit auch die einzelnen
Bitmappixelzeilen besitzen einen unterschiedlichen Arbeitsaufwand. Es ist zu erkennen,
dass der untere Thread daher im Vergleich zu den anderen einen größeren Anteil von
Zeilen des Bitmaps berechnet hat. Die lineare Skalierung zeigt, dass jeder Thread
140
4.9 Mandelbrot Menge – Apfelmännchen
141
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
169 /**
170 * CES Subroutine MANDELBROT:
171 * Generates the maldelbrot set.
172 *
173 * @param[in] y_0 the initial y coordinate of the bitmap area.
174 * @param[in] width the width of the bitmap area.
175 * @param[in] height the height of the bitmap area.
176 * @param[in,out] bmp the bitmap.
177 */
178 $mandelbrot(uint32_t width, uint32_t height, uint32_t y_0, uint32_t lines, scale_t scale, '
uint32_t maxDepth, double maxSqrAbsVal; Bitmap bmp;){
179 if ($lines$ > GRANULARITY) {
180 /* divide and conquer */
181 uint32_t lines1 = $lines$ / 2;
182 uint32_t lines2 = $lines$ - lines1;
183 uint32_t y_2 = $y_0$ + lines1;
184
185 $parallel mandelbrot(width, height, y_0, uint32_t lines1, scale, maxDepth, maxSqrAbsVal'
; bmp;);$
186 $parallel mandelbrot(width, height, uint32_t y_2, uint32_t lines2, scale, maxDepth, '
maxSqrAbsVal; bmp;);$
187 } else {
188 uint32_t x, y;
189 double Re, Im;
190 uint32_t iterations;
191
192 for (y = $y_0$; y < $y_0$ + $lines$; ++y) {
193 Im = (double)($height$ - y) / (double)$height$ * ($scale$.maxIm - $scale$.minIm) + '
$scale$.minIm;
194 for (x = 0; x < $width$; ++x) {
195 Re = (double)x / (double)$width$ * ($scale$.maxRe - $scale$.minRe) + $scale$.'
minRe;
196 iterations = mandelbrot_iterations(Re, Im, $maxDepth$, $maxSqrAbsVal$);
197 if (iterations == $maxDepth$)
198 drawPoint($bmp$, x, y, clBlack);
199 #ifdef DEBUG
200 else
201 drawPoint($bmp$, x, y, clRed + OWN_ID(fsp)); /* hack: color pixel according'
to thread’s ID */
202 #endif /* DEBUG */
203 }
204 }
205 }
206 }$
142
4.9 Mandelbrot Menge – Apfelmännchen
42 /**
43 * C Subroutine MANDELBROT_ITERATIONS:
44 * Calculates the number of iterations of the mandelbrot set function for a
45 * complex number c of the re-im-plane:
46 *
47 * c := Im_0*i + Re_0
48 * z_n := z_n^2 + c
49 * z_0 := 0
50 *
51 * @param Re_0 the real part of c.
52 * @param Im_0 the imaginary part of c.
53 * @param maxDepth the maximum number of iterations to perform.
54 * @param maxSqrAbsVal the maximum square absolute value.
55 * @return the number of iterations.
56 */
57 static inline uint32_t mandelbrot_iterations(double Re_0, double Im_0, uint32_t maxDepth, '
double maxSqrAbsVal) {
58 /* z_0 := 0 */
59 double Re = 0;
60 double Im = 0;
61 uint32_t iteration = 0;
62
63 while ((Re*Re + Im*Im < maxSqrAbsVal) && (iteration < maxDepth)) {
64 /* z_n := z_n^2 + c */
65 double Re_t = Re*Re - Im*Im + Re_0;
66 double Im_t = 2.0*Re*Im + Im_0;
67
68 Re = Re_t;
69 Im = Im_t;
70 ++iteration;
71 }
72
73 return iteration;
74 }
143
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES
144
5 Fazit
Die im Rahmen dieser Arbeit entwickelte parallele Laufzeitumgebung für C with Exe-
cution System (CES) besteht gerade einmal aus knapp 1260 (850)1 Zeilen C Quelltext,
von denen annähernd 750 (460) Zeilen Kommentar sind. Dennoch erfüllt die Laufzeit-
umgebung alle eingangs gestellten Anforderungen und alle Ziele dieser Arbeit konnten
erreicht werden.
Darüber hinaus wurde der CES Compiler (CESC) um die Unterstützung alternativer
Laufzeitumgebungen für CES erweitert. Die Korrektheit des entwickelten Synchroni-
sationsalgorithmus wurde zwar nicht formal bewiesen, jedoch konnte mit Hilfe der
entwickelten Testprogramme über mehrtägige Laufzeiten kein Fehler festgestellt werden,
obwohl diese bei früheren Entwicklungsständen bereits nach wenigen Sekunden Fehler
zutage brachten.
Anhand der unterschiedlichen Beispielanwendungen konnte gezeigt werden, dass
die parallele Ausführung der neuen Implementierung von Unterprogrammen von
Steinmacher-Burow mit Hilfe dieser einfachen parallelen Laufzeitumgebung unter
Verwendung des Cop/Thief Work-Stealing Konzepts parallele Performance liefert.
Unter Ignorierung des unten beschriebenen parallelen Overhead konnte eine nahezu
lineare Skalierung für manche Anwendungen bis zu acht Threads nachgewiesen werden.
Für rechenaufwendige Algorithmen wie beispielsweise die Mandelbrot Menge konnte
eine gute Lastverteilung des Cop/Thief Work-Stealing nachgewiesen werden.
Weiterhin wurde gezeigt, dass der Abarbeitungsoverhead pro Task Frame durch die
notwendige Synchronisation bei der parallelen Laufzeitumgebung für CES durch den
Einsatz von atomaren Operationen stark reduziert werden konnte. Der Performanceun-
terschied zwischen der sequentiellen Ausführung auf einem Prozessorkern zwischen der
sequentiellen und parallelen Laufzeitumgebung für CES ist von der Granularität der
Unterprogramme der Anwendung abhängig. Im schlimmsten Fall, bei Anwendungen
mit geringer Granularität der Unterprogramme, wie beispielsweise der Minimum/Maxi-
mum Suche aus Unterabschnitt 4.6.2, beträgt die Beschleunigung für einen Thread bei
1
Die Zeilenangaben in Klammern beziehen sich auf den reinen C Quelltext der parallelen Lauf-
zeitumgebung für CES ohne den C Quelltext der Implementierung der atomaren Operationen in
Inline-Assembler.
145
5 Fazit
146
6 Ausblick
Diese Arbeit hat erfolgreich einen Teilaspekt der Bereitstellung transparenter Neben-
läufigkeit für Anwendungen auf Basis eines Execution System (ES) untersucht und
implementiert: die parallele Ausführung der neuen Implementierung von Unterprogram-
men mit Hilfe einer parallelen Laufzeitumgebung unter Verwendung des Cop/Thief
Work-Stealing.
Basierend auf dieser Arbeit wären unter Anderem folgende weitere Untersuchungen
anzustellen:
147
6 Ausblick
• Untersuchung, ob der Thief den eigenen Frame Stack für andere Thieves sperren
sollte.
Der Prototyp C with Execution System (CES) bietet darüber hinaus noch viele
weitere interessante zu untersuchende und zu implementierende Bereiche, wie beispiels-
weise:
148
und gegebenfalls welches Konzept sprachübergreifend (ggf. sogar mit Betriebssyste-
munterstützung) durchsetzen wird:
So, what’s new, besides different syntax? Not much, but TPL does give us a
simpler way to install concurrency within code under the .NET Framework.
Now we’ve got OpenMP for Fortran and C, TBB for C++, and TPL for
C# and Java(?). Different coverages, different markets, not too different
functionality. Will one of these (or some future contender) ever be able to
cover all languages and platforms? Do we want a single method for easily
defining concurrency? (Clay Breshears, Intel [Bre07])
149
150
A Übersicht über das Makro-Interface zu
den Laufzeitumgebungen für CES
Tabelle A.1 bietet eine vollständige Übersicht über das Makro-Interface zu den Lauf-
zeitumgebungen für CES. Die Tabelle listet die Funktion eines jeden von den Laufzeit-
umgebungen zu definierenden Makros und seine jeweiligen Parameter mit Beschreibung
auf.
Tabelle A.1: Übersicht über das Makro-Interface zu den Laufzeitumgebungen für CES
Makro Parameter Beschreibung
RUNTIME_TASK_FUNCTION_ARGUMENTS
Definition der Parameterdefinitionsliste der vom CESC für
CES Unterprogramme generierten C Unterprogramme.
— —
RUNTIME_TASK_HEADER_STRUCT_PREPARAM(...)
Definition der Felder vor der Parameterliste der vom CESC für
CES Unterprogramme generierten Task Frame Strukturen.
NAME Bezeichner des CES Unterprogramms
NIN Anzahl der Eingabeparameter des CES Unterpro-
gramms
NINOUT Anzahl der Ein-/Ausgabegabeparameter des
CES Unterprogramms
NOUT Anzahl Ausgabegabeparameter des CES Unterpro-
gramms
151
A Übersicht über das Makro-Interface zu den Laufzeitumgebungen für CES
Tabelle A.1: Übersicht über das Makro-Interface zu den Laufzeitumgebungen für CES
(Fortsetzung)
Makro Parameter Beschreibung
RUNTIME_TASK_HEADER_STRUCT_POSTPARAM(...)
Definition der Felder nach der Parameterliste der vom CESC für
CES Unterprogramme generierten Task Frame Strukturen.
NAME Bezeichner des CES Unterprogramms
NIN Anzahl der Eingabeparameter des CES Unterpro-
gramms
NINOUT Anzahl der Ein-/Ausgabegabeparameter des
CES Unterprogramms
NOUT Anzahl Ausgabegabeparameter des CES Unterpro-
gramms
RUNTIME_TASK_INITIALIZE(...)
Definition des Initialisierungsabschnitts der vom CESC für
CES Unterprogramme generierten C Unterprogramme.
NAME Bezeichner des CES Unterprogramms
RUNTIME_TASK_FINALIZE(...)
Definition des Finalisierungsabschnitts der vom CESC für
CES Unterprogramme generierten C Unterprogramme.
NAME Bezeichner des CES Unterprogramms
RUNTIME_TASK_ANCESTOR(...)
Definition der anchestor Schalterverarbeitung im Initialisierungs-
abschnitt der vom CESC für CES Unterprogramme generierten
C Unterprogramme.
NAME Bezeichner des CES Unterprogramms
152
Tabelle A.1: Übersicht über das Makro-Interface zu den Laufzeitumgebungen für CES
(Fortsetzung)
Makro Parameter Beschreibung
RUNTIME_CREATE_STORAGE_CVAR(...)
Definition der Erzeugung einer CES Parametervariable für ei-
ne C Variable als Eingabe- oder Ein-/Ausgabeparameter beim
CES Unterprogrammaufruf.
NAME Bezeichner der zu erzeugenden CES Parametervaria-
ble
TYPE Variablentyp der zu erzeugenden CES Parameterva-
riable
VALUE Wert der zu erzeugenden CES Parametervariable
RUNTIME_CREATE_STORAGE_OUTPUT(...)
Definition der Erzeugung einer CES Parametervariable für einen
Ausgabeparameter beim CES Unterprogrammaufruf.
NAME Bezeichner der zu erzeugenden CES Parametervaria-
ble
TYPE Variablentyp der zu erzeugenden CES Parameterva-
riable
RUNTIME_STORAGE_REFERENCE(...)
Definition der Referenz auf eine gerade erzeugte CES Parameter-
variable beim CES Unterprogrammaufruf.
NAME Bezeichner der CES Parametervariable
153
A Übersicht über das Makro-Interface zu den Laufzeitumgebungen für CES
Tabelle A.1: Übersicht über das Makro-Interface zu den Laufzeitumgebungen für CES
(Fortsetzung)
Makro Parameter Beschreibung
RUNTIME_CREATE_TASK(...)
Definition der Erzeugung eines neuen Task Frame zum Merken
des Funktionszeigers und der Parameter beim beim CES Unter-
programmaufruf.
NAME Bezeichner des aufzurufenden CES Unterprogramms
ANCESTOR_FLAG Wert des ancestor Schalters
PARALLEL_FLAG Wert des parallel Schalters
NIN Anzahl der Eingabeparameter des aufzurufenden
CES Unterprogramms
NINOUT Anzahl der Ein-/Ausgabegabeparameter des aufzu-
rufenden CES Unterprogramms
NOUT Anzahl Ausgabegabeparameter des aufzurufenden
CES Unterprogramms
RUNTIME_NEWTASK_PARAMIN_REFERENCE(...)
Definition der Referenz auf einen Eingabeparameter eines gerade
erzeugten Task Frame beim CES Unterprogrammaufruf.
NAME Bezeichner des Eingabeparameters
N Parameternummer des Eingabeparameters
RUNTIME_NEWTASK_PARAMINOUT_REFERENCE(...)
Definition der Referenz auf einen Ein-/Ausgabeparameter eines
gerade erzeugten Task Frame beim CES Unterprogrammaufruf.
NAME Bezeichner des Ein-/Ausgabeparameters
N Parameternummer des Ein-/Ausgabeparameters
RUNTIME_NEWTASK_PARAMOUT_REFERENCE(...)
Definition der Referenz auf einen Ausgabearameter eines gerade
erzeugten Task Frame beim CES Unterprogrammaufruf.
NAME Bezeichner des Ausgabeparameters
N Parameternummer es Ausgabeparameters
154
Tabelle A.1: Übersicht über das Makro-Interface zu den Laufzeitumgebungen für CES
(Fortsetzung)
Makro Parameter Beschreibung
RUNTIME_TASK_PARAMIN_REFERENCE(...)
Definition der Referenz auf einen Eingabeparameter des laufenden
CES Unterprogramms beim CES Unterprogrammaufruf.
N Parameternummer des Eingabeparameters des lau-
fenden CES Unterprogramms
RUNTIME_TASK_PARAMINOUT_REFERENCE(...)
Definition der Referenz auf einen Ein-/Ausgabeparameter des lau-
fenden CES Unterprogramms beim CES Unterprogrammaufruf.
N Parameternummer des Ein-/Ausgabeparameters des
laufenden CES Unterprogramms
RUNTIME_TASK_PARAMOUT_REFERENCE(...)
Definition der Referenz auf einen Ausgabeparameter des laufenden
CES Unterprogramms beim CES Unterprogrammaufruf.
N Parameternummer des Ausgabeparameters des lau-
fenden CES Unterprogramms
RUNTIME_TASK_PARAMIN(...)
Definition des Zugriffs auf einen Eingabeparameter des laufenden
CES Unterprogramms.
N Parameternummer des Eingabeparameters des lau-
fenden CES Unterprogramms
RUNTIME_TASK_PARAMINOUT(...)
Definition des Zugriffs auf einen Ein-/Ausgabeparameter des
laufenden CES Unterprogramms.
N Parameternummer des Ein-/Ausgabeparameters des
laufenden CES Unterprogramms
RUNTIME_TASK_PARAMOUT(...)
Definition des Zugriffs auf einen Ausgabeparameter des laufenden
CES Unterprogramms.
N Parameternummer des Ausgabeparameters des lau-
fenden CES Unterprogramms
155
B Listings
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <unistd.h> /* usleep() */
12 #include <pthread.h> /* POSIX Threads Library */
13
14 /* global constants */
15 #define QUEUESIZE 1 /**< maximum number of items in the queue */
16 #define NPCS 2 /**< number of producers/consumers */
17 #define NITEMS 5 /**< number of items to produce/consume per p/c */
18 #define MAXPSLEEP (int)(1*1000*1000) /**< maximum microseconds to sleep to simulate production '
*/
19 #define MAXCSLEEP (int)(1.5*1000*1000) /**< maximum microseconds to sleep to simulate '
consumption */
20
21 /* global variables */
22 int queue[QUEUESIZE+1]; /* +1 to distinguish full from empty queue */
23 volatile int queue_pindex = 0; /* producer index */
24 volatile int queue_cindex = 0; /* consumer index */
25 pthread_mutex_t queue_pindex_mutex = PTHREAD_MUTEX_INITIALIZER;
26 pthread_mutex_t queue_cindex_mutex = PTHREAD_MUTEX_INITIALIZER;
27 pthread_cond_t queue_notFull = PTHREAD_COND_INITIALIZER;
28 pthread_cond_t queue_notEmpty = PTHREAD_COND_INITIALIZER;
29
30 /* helper macros */
31 #define QUEUE_COUNT(pindex, cindex) (pindex < cindex ? pindex - cindex + QUEUESIZE + 1 : pindex'
- cindex)
32 #define QUEUE_ISEMPTY(pindex, cindex) (pindex == cindex)
33 #define QUEUE_ISFULL(pindex, cindex) (QUEUE_COUNT(pindex, cindex) == QUEUESIZE)
34
35 /**
36 * Producer Thread Subroutine
37 *
38 * @param argument the thread id as int.
39 * @return noting.
40 */
41 void * thread_producer(void * argument) {
42 int id = (int)argument;
43 int item = id * NITEMS;
44 int count = 0;
45
46 printf("Producer %d: Ready ...\n", id);
47 for (count = 0; count < NITEMS; ++count) {
48 /* produce one item before entering the critical section */
156
B.1 Producer/Consumer Demonstrationsprogramm mit Pthreads in C
49 item++;
50 usleep(rand() % MAXPSLEEP); /* caution: rand() is not thread safe; doesn’t matter here '
though */
51 printf("Producer %d: Produced item %d ...\n", id, item);
52
53 /* append item to the queue */
54 pthread_mutex_lock(&queue_pindex_mutex); /* BEGIN CRITICAL SECTION */
55 while (QUEUE_ISFULL(queue_pindex, queue_cindex)) {
56 printf("Producer %d: Queue is full, going to sleep ...\n", id);
57 pthread_cond_wait(&queue_notFull, &queue_pindex_mutex);
58 printf("Producer %d: Awoke, checking queue ...\n", id);
59 }
60 queue[queue_pindex] = item;
61 /* caution: store fence is missing here to assuere that the item is fully
62 * stored before pindex changes */
63 queue_pindex = (queue_pindex + 1) % (QUEUESIZE + 1);
64 printf("Producer %d: Appended item %d to the queue [%d] ...\n", id, item, QUEUE_COUNT('
queue_pindex, queue_cindex));
65 pthread_mutex_unlock(&queue_pindex_mutex); /* END CRITICAL SECTION */
66 pthread_cond_signal(&queue_notEmpty); /* awake at least one consumer */
67 }
68 printf("Producer %d: Terminating ...\n", id);
69 pthread_exit(NULL);
70 }
71
72 /**
73 * Consumer Thread Subroutine
74 *
75 * @param argument the thread id as int.
76 * @return noting.
77 */
78 void * thread_consumer(void * argument) {
79 int id = (int)argument;
80 int item;
81 int count = 0;
82
83 printf("Consumer %d: Ready ...\n", id);
84 for (count = 0; count < NITEMS; ++count) {
85 /* take one item from the queue */
86 pthread_mutex_lock(&queue_cindex_mutex); /* BEGIN CRITICAL SECTION */
87 while (QUEUE_ISEMPTY(queue_pindex, queue_cindex)) {
88 printf("Consumer %d: Queue is empty, going to sleep ...\n", id);
89 pthread_cond_wait(&queue_notEmpty, &queue_cindex_mutex);
90 printf("Consumer %d: Awoke, checking queue ...\n", id);
91 }
92 item = queue[queue_cindex];
93 /* caution: memory fence is missing here to assure that the item is fully
94 * read before cindex changes.
95 * a load fence would not be enough to prevent the CPU from
96 * reordering the item read and the cindex write */
97 queue_cindex = (queue_cindex + 1) % (QUEUESIZE + 1);
98 printf("Consumer %d: Took item %d from the queue [%d] ...\n", id, item, QUEUE_COUNT('
queue_pindex, queue_cindex));
99 pthread_mutex_unlock(&queue_cindex_mutex); /* END CRITICAL SECTION */
100 pthread_cond_signal(&queue_notFull); /* awake at least one producer */
101
102 /* consume item */
103 usleep(rand() % MAXCSLEEP); /* caution: rand() is not thread safe; doesn’t matter here '
though */
104 printf("Consumer %d: Consumed item %d ...\n", id, item);
105 }
106 printf("Consumer %d: Terminating ...\n", id);
107 pthread_exit(NULL);
157
B Listings
108 }
109
110 /**
111 * C Main Subroutine
112 *
113 * @param argc the argument count.
114 * @param argc the argument vector.
115 * @return the return code.
116 */
117 int main(int argc, char ** argv) {
118 pthread_t producer[NPCS], consumer[NPCS];
119 int i;
120
121 /* create producers */
122 for (i = 0; i < NPCS; ++i)
123 pthread_create(&producer[i], NULL, thread_producer, (void *)i);
124
125 /* create consumers */
126 for (i = 0; i < NPCS; ++i)
127 pthread_create(&consumer[i], NULL, thread_consumer, (void *)i);
128
129 /* wait for producers and consumers to finish their work */
130 for (i = 0; i < NPCS; ++i)
131 pthread_join(producer[i], NULL);
132 for (i = 0; i < NPCS; ++i)
133 pthread_join(consumer[i], NULL);
134
135 return EXIT_SUCCESS;
136 }
B.1.2 Bildschirmausgabe
158
B.1 Producer/Consumer Demonstrationsprogramm mit Pthreads in C
159
B Listings
160
B.2 Bildschirmausgabe des Pretty Printer der round-robin Laufzeitumgebung für CES
49 2 cop(;;)
50 1 thief(;;)
51 ****************************** THREAD 0 ******************************
52 *****************EXECUTE 5****************
53 17 mergesort(nright=1, ...; right;) PARALLEL
54 16 merge(left, nleft=1, right, nright=1, ...; left;)
55 11 thief(;;)
56 10 merge(left, nleft=2, right, nright=2, ...; array;)
57 1 cop(;;)
58 ****************************** THREAD 1 ******************************
59 *****************EXECUTE 5****************
60 8 mergesort(nright=1, ...; right;) PARALLEL
61 7 merge(left, nleft=1, right, nright=1, ...; right;)
62 2 cop(;;)
63 1 thief(;;)
64 ****************************** THREAD 0 ******************************
65 *****************EXECUTE 6****************
66 16 merge(left, nleft=1, right, nright=1, ...; left;)
67 11 thief(;;)
68 10 merge(left, nleft=2, right, nright=2, ...; array;)
69 1 cop(;;)
70 ****************************** THREAD 1 ******************************
71 *****************EXECUTE 6****************
72 7 merge(left, nleft=1, right, nright=1, ...; right;)
73 2 cop(;;)
74 1 thief(;;)
75 ****************************** THREAD 0 ******************************
76 STORAGE
77 ****************************** THREAD 1 ******************************
78 STORAGE
79 ****************************** THREAD 0 ******************************
80 STORAGE
81 ****************************** THREAD 1 ******************************
82 STORAGE
83 ****************************** THREAD 0 ******************************
84 STORAGE
85 ****************************** THREAD 1 ******************************
86 STORAGE
87 ****************************** THREAD 0 ******************************
88 STORAGE
89 ****************************** THREAD 1 ******************************
90 STORAGE
91 ****************************** THREAD 0 ******************************
92 *****************EXECUTE 7****************
93 11 thief(;;)
94 10 merge(left, nleft=2, right, nright=2, ...; array;)
95 1 cop(;;)
96 thief: Looking for something to steal from thread 1...
97 thief: There was nothing to steal from thread 1...
98 thief: Giving up (looked at all other threads)...
99 ****************************** THREAD 1 ******************************
100 *****************EXECUTE 7****************
161
B Listings
101 2 cop(;;)
102 1 thief(;;)
103 cop: Killing thief...
104 ****************************** THREAD 0 ******************************
105 *****************EXECUTE 8****************
106 11 thief(;;)
107 10 merge(left, nleft=2, right, nright=2, ...; array;)
108 1 cop(;;)
109 thief: Giving up (looked at all other threads)...
110 thief: Got killed by cop...
111 ****************************** THREAD 1 ******************************
112 *****************EXECUTE 8****************
113 1 thief(;;)
114 thief: Looking for something to steal from thread 0...
115 thief: There was nothing to steal from thread 0...
116 thief: Giving up (looked at all other threads)...
117 ****************************** THREAD 0 ******************************
118 *****************EXECUTE 9****************
119 10 merge(left, nleft=2, right, nright=2, ...; array;)
120 1 cop(;;)
121 ****************************** THREAD 1 ******************************
122 *****************EXECUTE 9****************
123 1 thief(;;)
124 thief: Looking for something to steal from thread 0...
125 thief: There was nothing to steal from thread 0...
126 thief: Giving up (looked at all other threads)...
127 ****************************** THREAD 0 ******************************
128 STORAGE
129 ****************************** THREAD 1 ******************************
130 *****************EXECUTE 10****************
131 1 thief(;;)
132 thief: Looking for something to steal from thread 0...
133 thief: There was nothing to steal from thread 0...
134 thief: Giving up (looked at all other threads)...
135 ****************************** THREAD 0 ******************************
136 STORAGE
137 ****************************** THREAD 1 ******************************
138 *****************EXECUTE 11****************
139 1 thief(;;)
140 thief: Looking for something to steal from thread 0...
141 thief: There was nothing to steal from thread 0...
142 thief: Giving up (looked at all other threads)...
143 ****************************** THREAD 0 ******************************
144 STORAGE
145 ****************************** THREAD 1 ******************************
146 *****************EXECUTE 12****************
147 1 thief(;;)
148 thief: Looking for something to steal from thread 0...
149 thief: There was nothing to steal from thread 0...
150 thief: Giving up (looked at all other threads)...
151 ****************************** THREAD 0 ******************************
152 STORAGE
162
B.2 Bildschirmausgabe des Pretty Printer der round-robin Laufzeitumgebung für CES
Listing B.3: Reduzierte Bildschirmausgabe des Pretty Printer während der round-robin
Ausführung von Mergesort aus Listing 3.3
163
B Listings
1 /**
2 * Compare and Swap 32-Bit (DWord)
3 *
4 * Atomic Compare and Swap operation.
5 * @see atomic_value_cas32, atomic_value_cas64, atomic_bool_cas64
6 * @author Jens Remus \<JREMUS@de.ibm.com\>
7 *
8 * @param ptr pointer to the variable.
9 * @param oldVal the old value.
10 * @param newVal the new value.
11 * @result true, if CAS suceeded, else false.
12 *
13 * \note CMPXCHG compares its destination (first) operand to the value in AL, AX
14 * or EAX (depending on the operand size of the instruction). If they are
15 * equal, it copies its source (second) operand into the destination and
16 * sets the zero flag. Otherwise, it clears the zero flag and copies the
17 * destination register to AL, AX or EAX.\n
18 * The destination can be either a register or a memory location. The
19 * source is a register.
20 * @see The NASM Development Team: "NASM - The Netwide Assembler", 2003,
21 * Chapter B.4.30, Page 129.
22 *
23 * @todo Do I need to add "memory" to the clobber list?
24 * @todo Fix problem with clobber list for bool variant of CAS.
25 */
26 extern inline int atomic_bool_cas32(volatile uint32_t * ptr, const uint32_t expVal, const '
uint32_t newVal) {
27 unsigned char result;
28
29 __asm__ __volatile__ (
30 "lock; cmpxchgl %3, %0\n\t"
31 "sete %1"
32 : "=m" (*ptr), "=qm" (result)
33 : "a" (expVal), "r" (newVal)
34 : "cc"//, "eax"
35 );
36
37 return (int)result;
38 }
Listing B.4: Implementierung der atomaren Operation Compare and Swap 32-Bit mit
booleschem Rückgabewert in C mit Inline-Assembler – atomic_ia32.h
164
B.3 Atomare Operationen in C mit Inline-Assembler für IA-32
1 /**
2 * Compare and Swap 32-Bit (DWord)
3 *
4 * Atomic Compare and Swap operation.
5 * @see atomic_bool_cas32, atomic_value_cas64, atomic_bool_cas64
6 * @author Jens Remus \<JREMUS@de.ibm.com\>
7 *
8 * @param ptr pointer to the variable.
9 * @param oldVal the old value.
10 * @param newVal the new value.
11 * @result the actual old value of the variable \a ptr points to.
12 *
13 * \note CMPXCHG compares its destination (first) operand to the value in AL, AX
14 * or EAX (depending on the operand size of the instruction). If they are
15 * equal, it copies its source (second) operand into the destination and
16 * sets the zero flag. Otherwise, it clears the zero flag and copies the
17 * destination register to AL, AX or EAX.\n
18 * The destination can be either a register or a memory location. The
19 * source is a register.
20 * @see The NASM Development Team: "NASM - The Netwide Assembler", 2003,
21 * Chapter B.4.30, Page 129.
22 */
23 extern inline uint32_t atomic_value_cas32(volatile uint32_t * ptr, const uint32_t expVal, const'
uint32_t newVal) {
24 uint32_t result;
25
26 __asm__ __volatile__ (
27 "lock; cmpxchgl %3, %0"
28 : "=m" (*ptr), "=a" (result)
29 : "1" (expVal), "r" (newVal)
30 : "cc"
31 );
32
33 return result;
34 }
Listing B.5: Implementierung der atomaren Operation Compare and Swap 32-Bit mit
datentypabhängigem Rückgabewert in C mit Inline-Assembler – atomic_
ia32.h
165
B Listings
1 /**
2 * Compare and Swap 64-Bit (QWord)
3 *
4 * Atomic Compare and Swap operation.
5 * @see atomic_value_cas64, atomic_value_cas32, atomic_bool_cas32
6 * @author Jens Remus \<JREMUS@de.ibm.com\>
7 *
8 * @param ptr pointer to the variable.
9 * @param oldVal the old value.
10 * @param newVal the new value.
11 * @result true, if CAS succeeded, else false.
12 *
13 * @note This is a larger and more unwieldy version of CMPXCHG: it compares the
14 * 64-bit (eight-byte) value stored at [mem] with the value in EDX:EAX.
15 * If they are equal, it sets the zero flag and stores ECX:EBX into the
16 * memory area. If they are unequal, it clears the zero flag and stores
17 * the memory contents into EDX:EAX.\n
18 * CMPXCHG8B can be used with the LOCK prefix, to allow atomic execution.
19 * This is useful in multi-processor and multi-tasking environments.
20 * @see The NASM Development Team: "NASM - The Netwide Assembler", 2003,
21 * Chapter B.4.31, Page 129.
22 *
23 * @todo Do I need to add "memory" to the clobber list?
24 * @todo Fix problem with clobber list for bool variant of CAS.
25 */
26 extern inline int atomic_bool_cas64(volatile uint64_t * ptr, const uint64_t expVal, const '
uint64_t newVal) {
27 unsigned char result;
28
29 /**
30 * Union to access a uint64’s high and low uint32.
31 * @attention This union assumes INTEL memory order!
32 * @note This union prevents GCC’s warning "dereferencing type-punned
33 * pointer will break strict-aliasing rules" which caused a runtime
34 * error when using the inlined functions.
35 */
36 union uint64_t__uint32_t {
37 uint64_t uint64;
38 struct {
39 uint32_t low;
40 uint32_t high;
41 } uint32;
42 };
43 #define UINT64_HIGH(X) (((union uint64_t__uint32_t)X).uint32.high)
44 #define UINT64_LOW(X) (((union uint64_t__uint32_t)X).uint32.low)
45
46 __asm__ __volatile__ (
47 "lock; cmpxchg8b %0\n\t"
48 "sete %1;"
49 : "=m" (*ptr), "=qm" (result)
50 : "A" (expVal), "c" (UINT64_HIGH(newVal)), "b" (UINT64_LOW(newVal))
51 : "cc"//, "edx", "eax"
52 );
53
54 return (int)result;
55 }
Listing B.6: Implementierung der atomaren Operation Compare and Swap 64-Bit mit
booleschem Rückgabewert in C mit Inline-Assembler – atomic_ia32.h
166
B.3 Atomare Operationen in C mit Inline-Assembler für IA-32
1 /**
2 * Compare and Swap 64-Bit (QWord)
3 *
4 * Atomic Compare and Swap operation.
5 * @see atomic_bool_cas64, atomic_value_cas32, atomic_bool_cas32
6 * @author Jens Remus \<JREMUS@de.ibm.com\>
7 *
8 * @param ptr pointer to the variable.
9 * @param oldVal the old value.
10 * @param newVal the new value.
11 * @result the actual old value of the variable \a ptr points to.
12 *
13 * @note This is a larger and more unwieldy version of CMPXCHG: it compares the
14 * 64-bit (eight-byte) value stored at [mem] with the value in EDX:EAX.
15 * If they are equal, it sets the zero flag and stores ECX:EBX into the
16 * memory area. If they are unequal, it clears the zero flag and stores
17 * the memory contents into EDX:EAX.\n
18 * CMPXCHG8B can be used with the LOCK prefix, to allow atomic execution.
19 * This is useful in multi-processor and multi-tasking environments.
20 * @see The NASM Development Team: "NASM - The Netwide Assembler", 2003,
21 * Chapter B.4.31, Page 129.
22 */
23 extern inline uint64_t atomic_value_cas64(volatile uint64_t * ptr, const uint64_t expVal, const'
uint64_t newVal) {
24 uint64_t result;
25
26 /**
27 * Union to access a uint64’s high and low uint32.
28 * @attention This union assumes INTEL memory order!
29 * @note This union prevents GCC’s warning "dereferencing type-punned
30 * pointer will break strict-aliasing rules" which caused a runtime
31 * error when using the inlined functions.
32 */
33 union uint64_t__uint32_t {
34 uint64_t uint64;
35 struct {
36 uint32_t low;
37 uint32_t high;
38 } uint32;
39 };
40 #define UINT64_HIGH(X) (((union uint64_t__uint32_t)X).uint32.high)
41 #define UINT64_LOW(X) (((union uint64_t__uint32_t)X).uint32.low)
42
43 __asm__ __volatile__ (
44 "lock; cmpxchg8b %0"
45 : "=m" (*ptr), "=A" (result)
46 : "1" (expVal), "c" (UINT64_HIGH(newVal)), "b" (UINT64_LOW(newVal))
47 : "cc"
48 );
49
50 return result;
51 }
Listing B.7: Implementierung der atomaren Operation Compare and Swap 64-Bit mit
datentypabhängigem Rückgabewert in C mit Inline-Assembler – atomic_
ia32.h
167
B Listings
1 /**
2 * Thief CES Task Procedure.
3 * @param[in,out] fsp the frame stack pointer.
4 * @param[in] csp the current frame stack pointer.
5 */
6 void func_thief(TASK_FRAME ** fsp, TASK_FRAME * csp) {
7 int own_id = OWN_ID(fsp);
8 int victim_id = own_id;
9 THREAD_DATA * victim_td;
10 TASK_FRAME * victim_fsp;
11
12 /* start with right neighbor; try to steal until thief gets killed; increment victim index '
*/
13 while ((*fsp)->fnptr_task == func_thief) {
14 /* thief is alive and is going to look for task frames to steal */
15 victim_id = (victim_id + 1) % threads; /* increment thread index to look for work to '
steal from */
16 victim_td = &thread_data[victim_id];
17 victim_fsp = FS_BOTTOM(victim_td);
18
19 /* do not try to steal from own task frame stack */
20 if (victim_id == own_id)
21 continue;
22
23 /* try to get lock for victim’s task frame stack (only one thief per victim allowed) */
24 if (pthread_mutex_trylock(&victim_td->thief_mutex))
25 continue; /* failure: continue with new victim */
26
27 /* look for work on victim task frame stack */
28 while (victim_fsp < victim_td->thief_fsp_top) {
29 if (!IS_PARALLEL_TASK_FRAME(victim_fsp)) { /* check 1 */
30 victim_fsp++; /* jump over storage frame or unstealable task frame */
31 continue;
32 }
33
34 /* try to steal task frame */
35
36 /* synchronize with exec_top */
37 pthread_mutex_lock(&victim_td->thief_fsp_top_mutex);
38
39 /* double-check */
40 if (!(victim_fsp < victim_td->thief_fsp_top) || !IS_PARALLEL_TASK_FRAME(victim_fsp)'
) { /* check 2 */
41 pthread_mutex_unlock(&victim_td->thief_fsp_top_mutex); /* unlock exec_top first'
*/
42 break; /* back-off; exec_top has modified the frame */
43 }
44
168
B.4 Thief Unterprogramm der parallelen Laufzeitumgebung für CES (Pthreads)
169
B Listings
1 /**
2 * Thief CES Task Procedure.
3 * @param[in,out] fsp the frame stack pointer.
4 * @param[in] csp the current frame stack pointer.
5 */
6 void func_thief(TASK_FRAME ** fsp, TASK_FRAME * csp) {
7 int own_id = OWN_ID(fsp);
8 #ifdef CES_STATISTICS
9 THREAD_DATA * own_td = &thread_data[own_id];
10 #endif /* CES_STATISTICS */
11 int victim_id = own_id;
12 THREAD_DATA * victim_td;
13 TASK_FRAME * victim_fsp;
14 frame_fnptr victim_fnptr;
15 frame_fnptr thief_fnptr = {{func_thief, 0}};
16 frame_fnptr cop_fnptr = {{func_cop, 0}};
17
18 /* start with right neighbor; try to steal until thief gets killed; increment victim index '
*/
19 while ((*fsp)->fnptr_task == func_thief) {
20 /* thief is alive and is going to look for task frames to steal */
21 victim_id = (victim_id + 1) % threads; /* increment thread index to look for work to '
steal from */
22 victim_td = &thread_data[victim_id];
23 victim_fsp = FS_BOTTOM(victim_td);
24
25 /* do not try to steal from own task frame stack */
26 if (victim_id == own_id)
27 continue;
28
29 /* try to get lock for victim’s task frame stack (only one thief per victim allowed) */
30 if (pthread_mutex_trylock(&victim_td->thief_mutex))
31 continue; /* failure: continue with new victim */
32
33 /* look for work on victim task frame stack */
34 while (victim_fsp < victim_td->fsp) {
35 /* atomic read of task fnptr_task and parallel flag */
36 victim_fnptr.uint64 = atomic_value_cas64(&((frame_fnptr *)victim_fsp)->uint64, 0, '
0);
37
38 /* check 1: test if task frame is stealable */
39 if (victim_fnptr.fnptr.parallel < 2) {
40 victim_fsp++; /* jump over storage frame or unstealable task frame */
41 continue; /* continue with next frame */
42 }
43
44 /* try to steal task frame */
45
170
B.5 Thief Unterprogramm der parallelen Laufzeitumgebung für CES (CAS64)
171
B Listings
1 /** @file
2 * CES Programm: Parallel Test
3 *
4 * This program tests if the parallel runtime executes a task more than one time by error.
5 *
6 * @author Jens Remus \<JREMUS@de.ibm.com\>
7 */
8
9 #include <assert.h>
10 #include <stdio.h> /* printf, scanf */
11 #include <stdlib.h> /* atoi, malloc */
12 #include <stdint.h> /* uint32_t, uint64_t; note: requires C99 */
13 #include <inttypes.h> /* SCNu32, PRIu32, PRIu64; note: requires C99 */
14
15
16 /* global variables */
17 uint32_t * counter = NULL; /**< task execution counter array */
18
19
20 /* CES subroutines */
21
22 /**
23 * CES Subroutine PROGRAM
24 *
25 * @param[in] argc the argument count.
26 * @param[in] argv the argument vector.
27 */
28 $program(int argc, argv_t argv;;){
29 uint32_t n;
30
31 if (($argc$ == 2) && ((n = atoi($argv$[1])) != 0)) {
32 printf("n = %"PRIu32"\n", n);
33 } else {
34 printf("Input n := ");
35 scanf("%"SCNu32, &n);
36 }
37
38 /* initialize task execution counter array */
39 int i;
40 counter = malloc(n * sizeof(uint32_t));
41 for (i = 0; i < n; ++i)
42 counter[i] = 0;
43
44 for (i = 0; i < n; ++i) {
45 $parallel dummy(uint32_t i;;);$
46 }
172
B.6 Testprogramme zur Verifikation der parallelen Laufzeitumgebung für CES
47
48 $printresult(uint32_t n;;);$
49 }$
50
51 /**
52 * CES Subroutine DUMMY:
53 * Increments task frame counter atomically.
54 *
55 * @param[in] id the task ID.
56 */
57 $dummy(uint32_t id;;){
58 counter[$id$]++;
59 }$
60
61 /**
62 * CES Subroutine PRINTRESULT
63 *
64 * @param[in] n the number of tasks.
65 */
66 $printresult(uint32_t n;;){
67 /* check task execution counter array */
68 int i;
69 int n = $n$;
70 int errors = 0;
71 for (i = 0; i < n; ++i)
72 if (counter[i] != 1)
73 ++errors;
74 printf("Task execution errors: %d\n", errors);
75 }$
173
B Listings
B.6.2 Testprogramm 2
1 /** @file
2 * CES Programm: Parallel Test
3 *
4 * This program tests if the parallel runtime executes a task more than one time by error.
5 *
6 * @author Jens Remus \<JREMUS@de.ibm.com\>
7 */
8
9 #include <assert.h>
10 #include <stdio.h> /* printf, scanf */
11 #include <stdlib.h> /* atoi, malloc */
12 #include <stdint.h> /* uint32_t, uint64_t; note: requires C99 */
13 #include <inttypes.h> /* SCNu32, PRIu32, PRIu64; note: requires C99 */
14
15 #include "atomic.h" /* atomic operations; note: this include file only exists in parallel '
runtime directory! */
16
17 /* number of parallel tasks to create */
18 #ifndef NTASKS
19 #define NTASKS 2
20 #endif
21
22
23 /* global variables */
24
25 int32_t counter[NTASKS]; /**< task execution counter array */
26
27
28 /* CES subroutines */
29
30 /**
31 * CES Subroutine PROGRAM
32 *
33 * @param[in] argc the argument count.
34 * @param[in] argv the argument vector.
35 */
36 $program(int argc, argv_t argv;;){
37 /* initialize task execution counter array */
38 int i;
39 for (i = 0; i < NTASKS; ++i)
40 counter[i] = 0;
41
42 /* start endless test loop */
43 $forever(;;);$
44 }$
45
46 /**
47 * CES Subroutine FOREVER
48 */
49 $forever(;;){
174
B.6 Testprogramme zur Verifikation der parallelen Laufzeitumgebung für CES
175
C Verzeichnisse und Dateien
Tabelle C.1: Übersicht über die Verzeichnisse und Dateien der Arbeit
Ordner- oder Da- Beschreibung
teiname
doc\ Der LATEX Quelltext dieser Arbeit sowie das daraus
generierte PDF Dokument.
papers\ Verwendete Materialien bei der Erstellung dieser
Arbeit.
src\ Der Quelltext dieser Arbeit.
src\ces\ Der C Quelltext von CES, dem CESC, der Lauf-
zeitumgebungen für CES sowie der CES Beispielpro-
gramme.
src\ces\cesc\ Der C Quelltext des CESC von Wagner mit den
Modifikationen von Remus.
src\ces\demo\ Der CES Quelltext der in dieser Arbeit entwickelten
Beispielprogramme für CES.
src\ces\runtime\ Der C Quelltext der Laufzeitumgebungen für CES.
src\ces\runtime\ Der C Quelltext der sequentiellen Laufzeitumgebung
sequential\ für CES von Wagner mit den Modifikationen von
Remus.
src\ces\runtime\ Der C Quelltext der in dieser Arbeit entwickelten
round-robin\ round-robin Laufzeitumgebung für CES.
src\ces\runtime\ Der C Quelltext der in dieser Arbeit entwickelten
parallel\ parallelen Laufzeitumgebung für CES.
176
D Konfiguration des Testservers
processor : 1
vendor_id : GenuineIntel
cpu family : 6
model : 15
model name : Intel(R) Xeon(R) CPU E5345 @ 2.33GHz
stepping : 7
cpu MHz : 2333.947
cache size : 4096 KB
physical id : 0
siblings : 4
core id : 1
cpu cores : 4
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
177
D Konfiguration des Testservers
fpu : yes
fpu_exception : yes
cpuid level : 10
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts '
acpi mmx fxsr sse sse2 ss ht tm pbe lm pni monitor ds_cpl est tm2 xtpr
bogomips : 4666.17
processor : 2
vendor_id : GenuineIntel
cpu family : 6
model : 15
model name : Intel(R) Xeon(R) CPU E5345 @ 2.33GHz
stepping : 7
cpu MHz : 2333.947
cache size : 4096 KB
physical id : 0
siblings : 4
core id : 2
cpu cores : 4
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 10
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts '
acpi mmx fxsr sse sse2 ss ht tm pbe lm pni monitor ds_cpl est tm2 xtpr
bogomips : 4666.19
processor : 3
vendor_id : GenuineIntel
cpu family : 6
model : 15
model name : Intel(R) Xeon(R) CPU E5345 @ 2.33GHz
stepping : 7
cpu MHz : 2333.947
cache size : 4096 KB
physical id : 0
siblings : 4
core id : 3
cpu cores : 4
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 10
wp : yes
178
D.1 Systemkonfiguration hdclb063
flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts '
acpi mmx fxsr sse sse2 ss ht tm pbe lm pni monitor ds_cpl est tm2 xtpr
bogomips : 4666.20
processor : 4
vendor_id : GenuineIntel
cpu family : 6
model : 15
model name : Intel(R) Xeon(R) CPU E5345 @ 2.33GHz
stepping : 7
cpu MHz : 2333.947
cache size : 4096 KB
physical id : 1
siblings : 4
core id : 4
cpu cores : 4
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 10
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts '
acpi mmx fxsr sse sse2 ss ht tm pbe lm pni monitor ds_cpl est tm2 xtpr
bogomips : 4666.22
processor : 5
vendor_id : GenuineIntel
cpu family : 6
model : 15
model name : Intel(R) Xeon(R) CPU E5345 @ 2.33GHz
stepping : 7
cpu MHz : 2333.947
cache size : 4096 KB
physical id : 1
siblings : 4
core id : 5
cpu cores : 4
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 10
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts '
acpi mmx fxsr sse sse2 ss ht tm pbe lm pni monitor ds_cpl est tm2 xtpr
bogomips : 4666.21
179
D Konfiguration des Testservers
processor : 6
vendor_id : GenuineIntel
cpu family : 6
model : 15
model name : Intel(R) Xeon(R) CPU E5345 @ 2.33GHz
stepping : 7
cpu MHz : 2333.947
cache size : 4096 KB
physical id : 1
siblings : 4
core id : 6
cpu cores : 4
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 10
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts '
acpi mmx fxsr sse sse2 ss ht tm pbe lm pni monitor ds_cpl est tm2 xtpr
bogomips : 4666.22
processor : 7
vendor_id : GenuineIntel
cpu family : 6
model : 15
model name : Intel(R) Xeon(R) CPU E5345 @ 2.33GHz
stepping : 7
cpu MHz : 2333.947
cache size : 4096 KB
physical id : 1
siblings : 4
core id : 7
cpu cores : 4
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 10
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts '
acpi mmx fxsr sse sse2 ss ht tm pbe lm pni monitor ds_cpl est tm2 xtpr
bogomips : 4666.22
180
D.2 GNU Compiler Collection (GCC)
181
182
Glossar
Assembler Ein Assmebler übersetzt ein in einer maschinennahen Assemblersprache
geschriebenes Programm in Maschinensprache.
C Eine 1972 von Dennis Ritchie an den Bell Telephone Laboratories entwickelte
imperative Programmiersprache.
Cilk Eine am MIT unter der Leitung von Charles Leiserson entwickelte, auf ANSI C
basierende Programmiersprache zur parallelen Programmierung von SMP Com-
putern. http://supertech.csail.mit.edu/cilk/.
Compiler Ein Compiler erzeugt aus einem Programm in einer Quellsprache ein se-
mantisch äquivalentes Programm in einer Zielsprache. Üblicherweise übersetzt
ein Compiler einen in einer Programmiersprache geschriebenen Quelltext in
Assemblersprache.
183
Glossar
Mooresches Gesetz Die 1965 von Gordon E. Moore (Intel; siehe [Inta]) vorhergesagte
und 1975 korrigierte Entwicklung der Transistorzahl auf einem Prozessor. Das
Mooresche Gesetz (englisch: Moore’s Law; siehe [Intc]) besagt, dass sich die
Transistorenzahl auf einem Chip etwa alle zwei Jahre verdoppelt. Nach Kritikern
und Moore selbst wird das „Gesetz“ noch etwa 10 bis 15 Jahre Bestand haben,
bis eine fundamentale Grenze erreicht ist.
Thread Ein Thread (deutsch: Aktivitätsträger oder auch Faden) bezeichnet in der
Informatik einen Ausführungsstrang der Abarbeitung der Software.
184
Literaturverzeichnis
[AHL06] Agrawal, Kunal ; He, Yuxiong ; Leiserson, Charles E.: An Empirical
Evaluation of Work Stealing with Parallelism Feedback. In: Proceedings of
the International Conference on Distributed Computing Systems (ICDCS).
Lissabon, Portugal, Juli 2006
[Amd67] Amdahl, Gene M.: Validity of the Single Processor Approach to Achieving
Large-Scale Computing Capabilities. In: AFIPS Conference Proceedings
Bd. 30, AFIPS Press, April 1967, 483–485. – Gene Amdahl hat der Veröf-
fentlichung seines kompletten Artikels in den FAQ der Usenet Newsgroup
news:/comp.sys.super zugestimmt, die jeden 20. eines Monats dort gepos-
tet wird.
[Bre07] Breshears, Clay: "This is like deja vu all over again". Version: 28. Sep-
tember 2007. http://softwareblogs.intel.com/2007/09/28/
this-is-like-deja-vu-all-over-again/, Abruf: 2008-01-12. In-
tel Software Network Blogs
185
Literaturverzeichnis
[DMS] Dice, Dave ; Moir, Mark ; Scherer III, William: Quickly Reacquirable
Locks
[HLMS05] Hendler, Danny ; Lev, Yossi ; Moir, Mark ; Shavit, Nir: A dynamic-sized
nonblocking work stealing deque. 2005
[Intb] Intel (Hrsg.): Intel® Threading Building Blocks 2.0 for Open Source.
http://threadingbuildingblocks.org/, Abruf: 2007-12-10
186
Literaturverzeichnis
[Int07e] Intel (Hrsg.): Intel® Threading Building Blocks – Tutorial. Revision 1.7.
Intel, 19. Dezember 2007. http://threadingbuildingblocks.org/ver.
php?fid=91, Abruf: 2008-01-14
[Jon06] Jones, Toby: Lock-Free Algorithms. In: Rabin, Steve (Hrsg.): Game
Programming Gems 6. 1. Ausgabe. Charles River Media, Inc., 2006. – ISBN
1–58450–450–1, Kapitel 1.1, S. 5–15
187
Literaturverzeichnis
[Sie07] Siewert, Sam: SoC drawer: The Cell Broadband Engine chip: High-speed
offload for the masses. In: IBM developerWorks (2007), 17. April. http://
www.ibm.com/developerworks/linux/library/pa-soc12/, Abruf: 2008-
02-12
[Sut05a] Sutter, Herb: The Free Lunch Is Over: A Fundamental Turn Toward
Concurrency in Software. In: Dr. Dobb’s Journal 30 (2005), Nr. 3. http:
//www.gotw.ca/publications/concurrency-ddj.htm, Abruf: 2007-12-04
188
Literaturverzeichnis
[Sut07] Sutter, Herb: The Concurrency Land Rush: 2007-20?? Version: 1. De-
zember 2007. http://herbsutter.spaces.live.com/blog/cns!
2D4327CC297151BB!362.entry, Abruf: 2007-12-05
[Sut08a] Sutter, Herb: Break Amdahl’s Law! In: Dr. Dobb’s Portal (2008), 17. Ja-
nuar. http://www.ddj.com/cpp/205900309, Abruf: 2008-01-18
[Sut08b] Sutter, Herb: Going Superlinear. In: Dr. Dobb’s Portal (2008),
30. Januar. http://www.ddj.com/hpc-high-performance-computing/
206100542, Abruf: 2008-02-11
[TG07] Tian, Xinmin ; Geva, Robert: Intel® C++ STM Compiler, Prototype
Edition. Version: 16. November 2007. http://softwarecommunity.intel.
com/articles/eng/1460.htm, Abruf: 2007-12-10. Intel Software Network:
What If Software
[Wag07] Wagner, Sven: Konzeption und Entwicklung eines neuen Compiler “CESC„
zur Implementierung von Prozeduren als atomare Tasks, Fachhochschule
Gießen-Friedberg, Diplomarbeit, August 2007
189
190
Stichwortverzeichnis
191
Stichwortverzeichnis
pthread_rwlock_init . . . . . . 76, 77
pthread_rwlock_rdlock . . . 76, 77
pthread_rwlock_t . . . . . . . . . 76, 77
pthread_rwlock_tryrdlock 76, 77
pthread_rwlock_trywrlock 76, 77
pthread_rwlock_unlock . . . 76, 77
pthread_rwlock_wrlock . . . 76, 77
pthread_t . . . . . 64, 65, 68, 71, 158
pthread_yield . . . . . . . . . . . . . . . . 65
sfence . . . . . . . . . . . . . . . . . . . . . . . . . 79, 80
192
Eidesstattliche Erklärung
Ich erkläre hiermit an Eides Statt, dass ich die vorliegende Arbeit selbstständig und
ohne Benutzung anderer als der angegebenen Hilfsmittel angefertigt habe; die aus
fremden Quellen direkt oder indirekt übernommenen Gedanken sind als solche kenntlich
gemacht.
Die Arbeit wurde bisher in gleicher oder ähnlicher Form keiner anderen Prüfungs-
kommission vorgelegt und auch nicht veröffentlicht.
................................... ...................................
Ort, Datum Unterschrift (Vor- und Nachname)
193