Está en la página 1de 215

Edited

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

Konzeption und Entwicklung einer


Cop/Thief Work-Stealing
Laufzeitumgebung zur parallelen
Ausführung von Unterprogrammen

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

2 C with Execution System (CES) 7


2.1 Die neue Implementierung von Unterprogrammen . . . . . . . . . . . . . 7
2.1.1 Activation Frames . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.1.2 Task Frames . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.2 CES Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.3 CES Compiler (CESC) . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.3.1 Erweiterung des CES Compiler um die Unterstützung alternativer
Laufzeitumgebungen für CES . . . . . . . . . . . . . . . . . . . 23
2.3.2 Vom CES Compiler generierter C Quelltext für ein CES Unter-
programm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.3.3 Vom CES Compiler generierter C Quelltext für einen CES Un-
terprogrammaufruf . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.3.4 Vom CES Compiler generierter C Quelltext für den Zugriff auf
CES Parametervariablen . . . . . . . . . . . . . . . . . . . . . . 28
2.4 Sequentielle Laufzeitumgebung für CES (Sequential CES Runtime) . . 30
2.4.1 Aufbau der Task Frames der sequentiellen Laufzeitumgebung für
CES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.4.2 Aufbau der Storage Frames der sequentiellen Laufzeitumgebung
für CES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.4.3 Sequentielle Ausführung von CES Programmen mit der sequenti-
ellen Laufzeitumgebung für CES . . . . . . . . . . . . . . . . . 34

v
Inhaltsverzeichnis

3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime) 39


3.1 Problem der Identifizierung potentieller Parallelitäten zwischen Unter-
programmaufrufen bei der herkömmlichen Implementierung von Unter-
programmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.2 Identifizierung potentieller Parallelitäten zwischen Unterprogrammauf-
rufen bei der neuen Implementierung von Unterprogrammen . . . . . . 40
3.3 Datenabhängigkeiten zwischen Task Frames . . . . . . . . . . . . . . . 40
3.4 Markierung parallel ausführbarer CES Unterprogrammaufrufe zur Über-
setzungszeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.5 Parallele Ausführung von CES Programmen mittels Cop/Thief Work-
Stealing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.5.1 Work-Stealing . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.5.2 Cop/Thief Work-Stealing . . . . . . . . . . . . . . . . . . . . . . 47
3.5.3 Auswahl eines geeigneten Ziels zum Stehlen von Arbeit beim
(Cop/Thief) Work-Stealing . . . . . . . . . . . . . . . . . . . . 49
3.5.3.1 Sequentielle Auswahl eines geeigneten Ziels zum Stehlen
von Arbeit . . . . . . . . . . . . . . . . . . . . . . . . 55
3.5.3.2 Gleichverteilt zufällige Auswahl eines geeigneten Ziels
zum Stehlen von Arbeit . . . . . . . . . . . . . . . . . 56
3.6 Round-Robin Laufzeitumgebung für CES (Round-Robin CES Runtime) 56
3.6.1 Änderungen gegenüber der sequentiellen Laufzeitumgebung für
CES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
3.6.1.1 Unterstützung mehrerer Frame und Current Stacks . 58
3.6.1.2 Aufbau der Task Frames und Storage Frames der round-
robin Laufzeitumgebung für CES . . . . . . . . . . . . 58
3.6.1.3 Round-Robin Abarbeitung der Task Frames auf den
Frame Stacks . . . . . . . . . . . . . . . . . . . . . . . 59
3.6.1.4 Implementierung des Cop Unterprogramms . . . . . . 60
3.6.1.5 Implementierung des Thief Unterprogramms . . . . . . 61
3.6.2 Beispiel der Ausgabe des Pretty Printer der round-robin Lauf-
zeitumgebung für CES . . . . . . . . . . . . . . . . . . . . . . . 62
3.7 Die POSIX Threads (Pthreads) Bibliothek für Threads in C . . . . . . 62
3.7.1 Erzeugung von Threads . . . . . . . . . . . . . . . . . . . . . . 64
3.7.2 Beendigung und Abbruch von Threads . . . . . . . . . . . . . . 64
3.7.3 Zusammenführung von Threads . . . . . . . . . . . . . . . . . . 65
3.7.4 Unterbrechung von Threads . . . . . . . . . . . . . . . . . . . . 65
3.7.5 Das Thread-Unterprogramm . . . . . . . . . . . . . . . . . . . 66
3.7.6 Beispiel der Verwendung von Pthreads zur Programmierung von
Threads in C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
3.8 Verwendung der POSIX Threads Bibliothek zur parallelen Ausführung
eines CES Programms . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

vi
Inhaltsverzeichnis

3.9 Notwendigkeit der Synchronisierung des Cop/Thief Work-Stealing unter


Verwendung von Threads . . . . . . . . . . . . . . . . . . . . . . . . . 72
3.9.1 Thief: Synchronisation des Scannens eines fremden Frame Stack
eines anderen Thread . . . . . . . . . . . . . . . . . . . . . . . 72
3.9.2 Thief: Synchronisation des Stehlens eines als parallel abarbeitbar
markierten Task Frame von einem fremden Frame Stack eines
anderen Thread . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
3.9.3 Cop: Synchronisation der Signalisierung der vollständigen Abar-
beitung des gestohlenen Task Frame an den zugehörigen Thief
auf einem fremden Frame Stack eines anderen Thread . . . . . 73
3.10 Primitiven zur Synchronisation . . . . . . . . . . . . . . . . . . . . . . 73
3.10.1 Synchronisationsprimitiven der POSIX Threads Bibliothek . . 73
3.10.1.1 Mutexes . . . . . . . . . . . . . . . . . . . . . . . . . . 74
3.10.1.2 Condition Variables . . . . . . . . . . . . . . . . . . . 74
3.10.1.3 Read-Write Locks (RW Locks) . . . . . . . . . . . . . 76
3.10.2 Verwendung atomarer Operationen zur Entwicklung neuer per-
formanter Synchronisationsprimitiven oder Datenstrukturen . . . 77
3.10.3 Atomare Operationen als Synchronisationsprimitiven . . . . . . 78
3.10.3.1 Einfache atomare Lese- und Schreiboperationen sowie
Memory Barriers/Fences . . . . . . . . . . . . . . . . 79
3.10.3.2 Komplexe atomare Speicheroperationen . . . . . . . . 80
3.11 Synchronisation der parallelen Laufzeitumgebung für CES mittels der
Synchronisierungsprimitiven von POSIX Threads (Pthreads) . . . . . 83
3.11.1 Änderungen gegenüber der round-robin Laufzeitumgebung für CES 84
3.11.1.1 Aufbau der Struktur THREAD_DATA . . . . . . . . 84
3.11.1.2 Parallele Abarbeitung der Task Frames auf den Frame
Stacks . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
3.11.1.3 Implementierung des Cop Unterprogramms . . . . . . 86
3.11.1.4 Implementierung des Thief Unterprogramms . . . . . 86
3.11.2 Thief: Synchronisation des Scannens eines fremden Frame Stack
eines anderen Thread . . . . . . . . . . . . . . . . . . . . . . . . 87
3.11.3 Thief: Synchronisation des Stehlens eines als parallel abarbeitbar
markierten Task Frame von einem fremden Frame Stack eines
anderen Thread . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
3.11.4 Cop: Synchronisation der Signalisierung der vollständigen Abar-
beitung des gestohlenen Task Frame an den zugehörigen Thief
auf einem fremden Frame Stack eines anderen Thread . . . . . 89
3.12 Synchronisation der parallelen Laufzeitumgebung für CES mittels der
atomaren Operationen (CAS64) . . . . . . . . . . . . . . . . . . . . . . 89
3.12.1 Änderungen gegenüber der mittels der Synchronisierungsprimiti-
ven von Pthreads synchronisierten parallelen Laufzeitumgebung
für CES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
3.12.1.1 Aufbau der Struktur THREAD_DATA . . . . . . . . 90
3.12.1.2 Aufbau der Task Frames, Storage Frames und Cop Frames 91

vii
Inhaltsverzeichnis

3.12.1.3 Implementierung des Thread-Unterprogramms der Fra-


me Stack Abarbeitungsschleife . . . . . . . . . . . . . 92
3.12.1.4 Implementierung des Thief Unterprogramms . . . . . 93
3.12.2 Thief: Synchronisation des Scannens eines fremden Frame Stack
eines anderen Thread . . . . . . . . . . . . . . . . . . . . . . . 93
3.12.3 Thief: Synchronisation des Stehlens eines als parallel abarbeitbar
markierten Task Frame von einem fremden Frame Stack eines
anderen Thread . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
3.12.4 Cop: Synchronisation der Signalisierung der vollständigen Abar-
beitung des gestohlenen Task Frame an den zugehörigen Thief
auf einem fremden Frame Stack eines anderen Thread . . . . . 99
3.13 Überprüfung der Korrektheit der parallelen Laufzeitumgebung für CES 99

4 Leistungsbewertung der parallelen Laufzeitumgebung für CES und Vergleich


mit der sequentiellen Laufzeitumgebung für CES 101
4.1 Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
4.1.1 Amdahlsches Gesetz . . . . . . . . . . . . . . . . . . . . . . . . 102
4.1.2 Gustafsonsches Gesetz . . . . . . . . . . . . . . . . . . . . . . . 103
4.1.3 Beziehung zwischen dem Amdahlschen Gesetz und dem Gustaf-
sonschen Gesetz . . . . . . . . . . . . . . . . . . . . . . . . . . 106
4.2 Mess- und Auswertungsverfahren . . . . . . . . . . . . . . . . . . . . . 106
4.2.1 Messverfahren . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
4.2.2 Auswertungsverfahren . . . . . . . . . . . . . . . . . . . . . . . 108
4.2.3 Graphische Darstellung der Messergebnisse . . . . . . . . . . . 109
4.3 Artifizielle Testprogramme . . . . . . . . . . . . . . . . . . . . . . . . . . 111
4.3.1 Fibonacci-Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
4.3.2 Aufsummierung der natürlichen Zahlen (Gaußscher Summenal-
gorithmus) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
4.4 N-Damen Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
4.5 Sortieralgorithmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
4.5.1 Mergesort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
4.5.2 Quicksort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
4.6 Suchalgorithmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
4.6.1 Unvollständige Suchalgorithmen . . . . . . . . . . . . . . . . . 130
4.6.2 Suche nach dem Minimum und oder Maximum eines unsortierten
Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
4.7 MemCopy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
4.8 Numerische Integration . . . . . . . . . . . . . . . . . . . . . . . . . . 134
4.9 Mandelbrot Menge – Apfelmännchen . . . . . . . . . . . . . . . . . . . 138
4.10 Zusammenfassung der Ergebnisse der untersuchten Anwendungen der
parallelen Laufzeitumgebung für CES . . . . . . . . . . . . . . . . . . 144

5 Fazit 145

6 Ausblick 147

viii
Inhaltsverzeichnis

A Übersicht über das Makro-Interface zu den Laufzeitumgebungen für CES 151

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

C Verzeichnisse und Dateien 176

D Konfiguration des Testservers 177


D.1 Systemkonfiguration hdclb063 . . . . . . . . . . . . . . . . . . . . . . . 177
D.2 GNU Compiler Collection (GCC) . . . . . . . . . . . . . . . . . . . . . . 181
D.2.1 GCC 4.2.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
D.2.2 GCC 4.1.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
D.2.3 GCC 3.4.6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181

Glossar 183

Literaturverzeichnis 185

Stichwortverzeichnis 191

Eidesstattliche Erklärung 193

ix
Abbildungsverzeichnis

1.1 Entwicklung der Taktfrequenz und Transistorzahl [Sut05b] . . . . . . . 2


1.2 Entwicklung der Performanz von Mehrkernprozessoren im Gegensatz zu
derjenigen von Einkernprozessoren [Shi06] . . . . . . . . . . . . . . . . 2
1.3 Architektur von Anwendungen mit Execution System (ES) . . . . . . 4
1.4 Ziel des neuen Ansatzes . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.5 Blockdiagramm der Ausführung eines CES Programms . . . . . . . . . 6

2.1 Abarbeitung des Unterprogramms a des Pseudocode aus Listing 2.1


mittels der herkömmlichen Implementierung von Unterprogrammen . . 8
2.2 Abarbeitung des Unterprogramms a des Pseudocode aus Listing 2.1
mittels der neuen Implementierung von Unterprogrammen . . . . . . . 8
2.3 CES Syntax als Railroad-Diagramm . . . . . . . . . . . . . . . . . . . 14
2.3 CES Syntax als Railroad-Diagramm (Fortsetzung) . . . . . . . . . . . 15
2.4 Sequentielle Abarbeitung eines Programms, welches das Divide and
Conquer Konzept zur Aufteilung der Arbeit verwendet . . . . . . . . . 20
2.5 Sortierung eines Array mit vier Elementen mittels Mergesort aus
Listing 2.9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.6 Schema der Übersetzung eines CES Programms mit dem CES Compiler 23
2.7 Aufbau der Struktur TASK_FRAME (Sequentielle Laufzeitumgebung
für CES) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.8 Aufbau der Struktur STORAGE_FRAME (Sequentielle Laufzeitumge-
bung für CES) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.9 Sequentielle Ausführung eines CES Programms auf einem Prozessor am
Beispiel von Mergesort aus Listing 2.9 . . . . . . . . . . . . . . . . . 35
2.10 Rekursive Aufteilung des zu sortierenden Array mit vier Elementen bei
der Ausführung von Mergesort aus Listing 2.9 . . . . . . . . . . . . 36

3.1 Datenflussgraph der CES Unterprogrammaufrufe des CES Unterpro-


gramms mergesort aus Listing 2.9 . . . . . . . . . . . . . . . . . . . . . 41
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.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 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

x
Abbildungsverzeichnis

3.4 Initialisierung des Cop/Thief Work-Stealing am Beispiel der parallelen


Ausführung eines CES Programms . . . . . . . . . . . . . . . . . . . . 49
3.5 Cop/Thief Work-Stealing . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.6 Parallele Ausführung eines CES Programms unter Verwendung von
Cop/Thief Work-Stealing mit drei Prozessoren am Beispiel von Merge-
sort aus Listing 2.9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.7 Aufbau der Struktur TASK_FRAME (Round-Robin Laufzeitumgebung
für CES) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
3.8 Aufbau der Struktur TASK_FRAME (Parallele Laufzeitumgebung für
CES; CAS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91

4.1 Maximale Beschleunigung unter Anwendung des Amdahlschen Gesetzes


aus Gleichung 4.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
4.2 Parallele Skalierbarkeit [Sut08b] . . . . . . . . . . . . . . . . . . . . . . 104
4.3 Illustration des Amdahlschen Gesetzes für s = p [Sut08a] . . . . . . . . 104
4.4 Illustration des Gustafsonschen Gesetzes [Sut08a] . . . . . . . . . . . . 105
4.5 Beispiel der Auswertung der Messergebnisse eines Benchmark . . . . . 109
4.6 Skalierung der rekursiven Implementierung der Berechnung der Fibonacci-
Zahlen in CES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
4.7 Skalierung der Implementierung des Aufsummierens der ersten N natür-
lichen Zahlen in CES . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
4.8 Skalierung der Implementierung des Aufsummierens der ersten N natür-
lichen Zahlen in CES (CAS64) . . . . . . . . . . . . . . . . . . . . . . 116
4.9 Skalierung der Implementierung des Aufsummierens der ersten N natür-
lichen Zahlen in CES (Pthreads) . . . . . . . . . . . . . . . . . . . . . 116
4.10 Skalierung der divide and conquer Implementierung des N -Damen Pro-
blems in CES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
4.11 Skalierung der Implementierung von Mergesort in CES . . . . . . . 123
4.12 Skalierung der Implementierung von Quicksort in CES . . . . . . . . . 127
4.13 Skalierung der Implementierung von der Suche nach dem Minimum eines
unsortierten Array in CES . . . . . . . . . . . . . . . . . . . . . . . . . 130
4.14 Skalierung der Implementierung von der Suche nach dem Maximum
eines unsortierten Array in CES . . . . . . . . . . . . . . . . . . . . . . . 131
4.15 Skalierung der divide and conquer Implementierung von MemCopy in CES133
4.16 Skalierung der Implementierung des numerischen Integrierens in CES . 135
4.17 Graphische Darstellung der Mandelbot Menge . . . . . . . . . . . . . . 138
4.18 Nahezu lineare Skalierung der divide and conquer Implementierung der
Berechnung der Mandelbrot Menge in CES . . . . . . . . . . . . . . . 139
4.19 Graphische Darstellung der Mandelbot Menge mit farblicher Hervorhe-
bung der den jeweiligen Threads zugeordneten Zeilen (8 Threads) . . . 140

xi
Tabellenverzeichnis

1.1 Parallele Programmierung mit Execution System (ES) im Vergleich zu


herkömmlicher paralleler Programmierung im Bezug auf die Verantwort-
lichkeiten der Entwickler der einzelnen Systemschichten . . . . . . . . 5

2.1 Übersicht über die CES Unterprogramm Parametertypen und -variablen 13


2.2 Übersicht über die Parameter des CES Unterprogramms mergesort aus
Listing 2.9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.3 Übersicht über die Parameternummern des CES Unterprogramms mergesort
aus Listing 2.9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

A.1 Übersicht über das Makro-Interface zu den Laufzeitumgebungen für CES151


A.1 Übersicht über das Makro-Interface zu den Laufzeitumgebungen für
CES (Fortsetzung) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
A.1 Übersicht über das Makro-Interface zu den Laufzeitumgebungen für
CES (Fortsetzung) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
A.1 Übersicht über das Makro-Interface zu den Laufzeitumgebungen für
CES (Fortsetzung) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
A.1 Übersicht über das Makro-Interface zu den Laufzeitumgebungen für
CES (Fortsetzung) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155

C.1 Übersicht über die Verzeichnisse und Dateien der Arbeit . . . . . . . . 176

xii
Listings

2.1 Pseudocode zur Demonstration der unterschiedlichen Ausführung der


herkömmlichen Implementierung von Unterprogrammen mit Activation
Frames und der neuen Implementierung von Unterprogrammen mit Task
Frames . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2 Pseudocode zur Demonstration der Weiterverwendung von Ergebnissen
von Unterprogrammaufrufen bei der herkömmlichen Implementierung
von Unterprogrammen . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3 Pseudocode zur Demonstration der Weiterverwendung von Ergebnis-
sen von Unterprogrammaufrufen bei der neuen Implementierung von
Unterprogrammen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.4 CES Unterprogrammdefinition . . . . . . . . . . . . . . . . . . . . . . 16
2.5 CES Unterprogrammaufruf . . . . . . . . . . . . . . . . . . . . . . . . 16
2.6 Call-by-Value Parameterübergabe von C Variablen beim CES Unterpro-
grammaufruf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.7 Call-by-Reference Parameterübergabe von CES Parametervariablen
beim CES Unterprogrammaufruf . . . . . . . . . . . . . . . . . . . . . 18
2.8 Hello World in CES – helloworld.ces . . . . . . . . . . . . . . . . . . 18
2.9 Mergesort in CES (CES Unterprogramm mergesort) . . . . . . . . 19
2.10 Mergesort in CES (CES Unterprogramm merge) . . . . . . . . . . . 20
2.11 Vom CESC generierter C Quelltext ces_mergesort.c für das CES Un-
terprogramm aus Listing 2.9 . . . . . . . . . . . . . . . . . . . . . . . . 25
2.12 Vom CESC generierter C Quelltext ces_mergesort.h für das CES Un-
terprogramm aus Listing 2.9 . . . . . . . . . . . . . . . . . . . . . . . . 26
2.13 Vom CESC generierter C Quelltext für den ersten CES Unterprogramm-
aufruf aus Listing 2.9. Die Zeilen 1–12 entsprechen der Zeile 33 in
Listing 2.9. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.14 Vom CESC generierter C Quelltext für den Zugriff auf die CES Para-
metervariablen aus Listing 2.9. Die Zeilen 1–11 entsprechen den Zeilen
21–31 in Listing 2.9. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.15 Schleife zur sequentiellen Abarbeitung des jeweils obersten Task Frame
aus run_time.c der sequentiellen Laufzeitumgebung für CES . . . . . 30
2.16 Definition der Struktur TASK_FRAME (Sequentielle Laufzeitumgebung
für CES) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.17 Storage Unterprogramm aus run_time.c der sequentiellen Laufzeitum-
gebung für CES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.18 Definition der Struktur STORAGE_FRAME (Sequentielle Laufzeitum-
gebung für CES) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

xiii
Listings

2.19 Reduzierte Bildschirmausgabe des Pretty Printer während der sequenti-


ellen Ausführung von Mergesort aus Listing 2.9 . . . . . . . . . . . 38

3.1 Zulässige Markierung parallel ausführbarer CES Unterprogrammaufrufe 42


3.2 Unzulässige Markierung parallel ausführbarer CES Unterprogrammaufrufe 42
3.3 Parallele Implementierung von Mergesort in CES (CES Unterpro-
gramm mergesort) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
3.4 Reduzierte Bildschirmausgabe des Pretty Printer während der sequenti-
ellen Ausführung von Mergesort aus Listing 3.3 . . . . . . . . . . . 45
3.5 Gleichverteilt zufällige Auswahl des Ziels zum Stehlen in Cilk 5.4.6 –
runtime/sched.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.6 Definition der Struktur THREAD_DATA (Round-Robin Laufzeitumge-
bung für CES) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
3.7 Definition der Struktur TASK_FRAME (Round-Robin Laufzeitumge-
bung für CES) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
3.8 Schleife zur round-robin Abarbeitung des jeweils obersten Task Frame
aus run_time.c der round-robin Laufzeitumgebung für CES . . . . . . 60
3.9 Definition der Struktur COP_TASK_FRAME (Round-Robin Laufzeit-
umgebung für CES) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
3.10 Cop Unterprogramm (Round-Robin Laufzeitumgebung für CES) . . . . 61
3.11 Definition der Struktur THIEF_TASK_FRAME (Round-Robin Lauf-
zeitumgebung für CES) . . . . . . . . . . . . . . . . . . . . . . . . . . 62
3.12 Reduziertes Thief Unterprogramm (Round-Robin Laufzeitumgebung für
CES) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
3.13 Die Pthreads API zum Erzeugen von Threads . . . . . . . . . . . . . . 64
3.14 Die Pthreads API zum Beenden und Abbrechen von Threads . . . . . 64
3.15 Die Pthreads API zum Zusammenführen von Threads . . . . . . . . . 65
3.16 Die Pthreads API zum Unterbrechen von Threads . . . . . . . . . . . 65
3.17 C Unterprogramm von Threads mit Pthreads . . . . . . . . . . . . . . 66
3.18 Hello World mit Pthreads in C . . . . . . . . . . . . . . . . . . . . . . 68
3.19 Bildschirmausgabe von Hello World mit Pthreads in C aus Listing 3.18 69
3.20 Verwendung von Pthreads zur parallelen Ausführung eines CES Programms 71
3.21 Die Pthreads API für Mutexes . . . . . . . . . . . . . . . . . . . . . . 74
3.22 Die Pthreads API für Condition Variables . . . . . . . . . . . . . . . . 74
3.23 Warten auf das Eintreten eines Ereignisses . . . . . . . . . . . . . . . . 75
3.24 Die Pthreads API für Read-Write Locks (RW Locks) . . . . . . . . . . 76
3.26 Implementierung eines einfachen Lock mit Hilfe der atomaren Operation
Swap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
3.25 Swap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
3.27 Compare and Swap mit boolischem Rückgabewert . . . . . . . . . . . 82
3.28 Compare and Swap mit datentypabhängigem Rückgabewert . . . . . . 82
3.29 Fetch and Add . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
3.30 Definition der Struktur THREAD_DATA (Parallele Laufzeitumgebung
für CES; Pthreads) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85

xiv
Listings

3.31 Definition des Unterprogramms der Frame Stack Abarbeitungsschleife


(Parallele Laufzeitumgebung für CES; Pthreads) . . . . . . . . . . . . 86
3.32 Definition des Cop Unterprogramms (Parallele Laufzeitumgebung für
CES) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
3.33 Definition der Struktur THIEF_TASK_FRAME (Parallele Laufzeitum-
gebung für CES) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
3.34 Definition der Struktur THREAD_DATA (Parallele Laufzeitumgebung
für CES; CAS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
3.35 Definition der Struktur TASK_FRAME (Parallele Laufzeitumgebung
für CES; CAS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
3.36 Definition der Struktur STORAGE_FRAME (Parallele Laufzeitumge-
bung für CES; CAS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
3.37 Definition der Struktur COP_TASK_FRAME (Parallele Laufzeitumge-
bung für CES; CAS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
3.38 Definition des Unterprogramms der Task Frame Abarbeitungsschleife
(Parallele Laufzeitumgebung für CES; CAS) – run_time.c . . . . . . 93
3.39 Definition des Makros zur Finalisierung eines Unterprogramms – run_
time.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97

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

4.16 Quicksort in CES (C Unterprogramm partition) – sort.ces . . . 129


4.17 Quicksort in CES (C Makro SWAP) – sort.ces . . . . . . . . . . . . 129
4.18 Minimum-/Maximumsuche in CES (CES Unterprogramm search_minmax)
– search.ces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
4.19 Minimum-/Maximumsuche in CES (CES Unterprogramm minmax) –
search.ces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
4.20 MemCopy in CES (CES Unterprogramm pmemcopy) – memcopy.ces . 134
4.21 Numerische Integration in CES (CES Unterprogramm integrate) –
integrate.ces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
4.22 Numerische Integration in CES (CES Unterprogramm quadrature) –
integrate.ces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
4.23 Numerische Integration in CES (CES Unterprogramm add_double) –
integrate.ces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
4.24 Mandelbrot Menge in CES (CES Unterprogramm mandelbrot) – mandelbrot.
ces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
4.25 Mandelbrot Menge in CES (C Unterprogramm mandelbrot_iterations)
– mandelbrot.ces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143

B.1 Producer/Consumer Demonstrationsprogramm unter Verwendung eines


FIFO Ringpuffers zum Datenaustausch mit Pthreads in C . . . . . . . 156
B.2 Bildschirmausgabe des Producer/Consumer Demonstrationsprogramms
mit Pthreads in C aus Listing B.1 . . . . . . . . . . . . . . . . . . . . 158
B.3 Reduzierte Bildschirmausgabe des Pretty Printer während der round-
robin Ausführung von Mergesort aus Listing 3.3 . . . . . . . . . . . 160
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.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.6 Implementierung der atomaren Operation Compare and Swap 64-Bit mit
booleschem Rückgabewert in C mit Inline-Assembler – atomic_ia32.h 166
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.8 Definition des Thief Unterprogramms (Parallele Laufzeitumgebung für
CES; Pthreads) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
B.9 Definition des Thief Unterprogramms (Parallele Laufzeitumgebung für
CES; CAS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
B.10 Testprogramm 1 – test_parallel_runtime_task_execution1.ces . 172
B.11 Testprogramm 2 – test_parallel_runtime_task_execution2.ces . 174

D.1 Ausgabe von cat /proc/cpuinfo . . . . . . . . . . . . . . . . . . . . . . 177


D.2 Ausgabe von gcc -v . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
D.3 Ausgabe von gcc -v . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
D.4 Ausgabe von gcc -v . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181

xvi
Abkürzungsverzeichnis
API (Application Programming Interface) Eine Programmierschnittstelle, die von
einem Softwaresystem anderen Programmen zur Anbindung zur Verfügung gestellt
wird.

BOINC (Berkeley Open Infrastructure for Network Computing) Open-Source-Soft-


ware für Volunteer Computing und Desktop Grid Computing. http://boinc.
berkeley.edu/.

CAS (Compare and Swap) Die im Unterunterabschnitt 3.10.3.2 beschriebene atomare


Operation zum atomaren Vergleich und Austausch eines Datenworts im Speicher.
Der Austausch erfolgt ausschließlich, wenn der Vergleich erfolgreich war. CAS32
steht für eine Compare and Swap Operation auf einem 32-Bit breiten Datenwort
und CAS64 entsprechend für eine Operation auf einem 64-Bit breiten Datenwort.

CES (C with Execution System) Eine von Burkhard D. Steinmacher-Burow (IBM)


konzipierte, um seine neuartige Implementierung von Unterprogrammen und ein
Execution System zur Ausführung dieser neuartigen Unterprogramme erweiterte
Programmiersprache C.

CESC (CES Compiler) Ein von Sven Wagner (IBM) entwickelter CES nach C Com-
piler.

CPP (C Präprozessor) Der C Präprozessor (englisch: C Preprocessor) wird zur Ma-


kroexpansion vor der Übersetzung eines C Quelltexts durch einen C Compiler
verwendet.

CPU (Central Processing Unit) Der Hauptprozessor (englisch: Central Processing


Unit), ist der Prozessor eines Computers, der Programme ausführt.

ES (Execution System) Ein System zur Verwaltung und Ausführung von Arbeitspa-
keten. Das Konzept wird beispielsweise in Anwendungen für verteiltes Rechnen
eingesetzt.

FH Wedel (Fachhochschule Wedel) Die FH Wedel ist eine private Hochschule in


Wedel, Schleswig-Holstein, mit den Schwerpunkten Informatik, Wirtschaftsinge-
nieurwesen und Betriebswirtschaftslehre. http://www.fh-wedel.de/.

FIFO (First In – First Out) Prinzip einer Warteschlange (englisch: queue).

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/.

IA-32 (Intel Architecture, 32-bit) Bezeichnung für eine PC-Prozessorarchitektur von


Intel. Die IA-32 ist die weltweit verbreiteste PC-Prozessorarchitektur. Sie wird
teilweise noch als „i386“ Architektur bezeichnet.

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/.

IEEE (Institute of Electrical and Electronics Engineers) Ein weltweiter Berufsverband


von Ingenieuren aus den Bereichen Elektrotechnik und Informatik.

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.

OOP (Objektorientierte Programmierung) Ein Programmierkonzept das Daten mit


ihren Eigenschaften und möglichen Operationen zu Objekten verbindet.

OpenMP (Open Multi-Processing) Eine gemeinschaftlich von verschiedenen Hard-


ware- und Compilerherstellern entwickelte Programmierschnittstelle. Der Stan-
dard dient zur Shared-Memory-Programmierung in C/C++/Fortran von SMP Com-
putern. http://www.openmp.org/.

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

SMP (Symmetric Multiprocessing) Eine Multiprozessor-Computerarchitektur, bei


der sich mehrere identische Prozessoren einen Adressraum teilen.

SSE (Streaming SIMD Extensions) Bezeichnung für eine Reihe an Befehlssatzerwei-


terungen der Intel x86 Architektur um SIMD (Single Instruction, Multiple Data)
Befehle. Bisher existieren die Erweiterungen SSE, SSE2, SSE3, SSSE3, SSE4 und
SSE5.

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

ID Identifier eines Prozessors bzw. Thread (Frame Stack)

Pi Prozessor i

T HIEFi Thief bzw. Thief Frame i

csp Current Stack Pointer

fsp Frame Stack Pointer

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.

(Herb Sutter [Sut05a])

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

Abbildung 1.1: Entwicklung der Taktfrequenz und Transistorzahl [Sut05b]

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-

Abbildung 1.2: Entwicklung der Performanz von Mehrkernprozessoren im Gegensatz


zu derjenigen von Einkernprozessoren [Shi06]

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

ES Verwaltung der Aufgaben

Ergebnisse der Bearbeitung der


Aufgaben Aufgaben

Hardware

Abbildung 1.3: Architektur von Anwendungen mit Execution System (ES)

Execution Sys- Ziel:


hoch

tems (ES) liefern universelle trans-


transparente Ne- neuer Ansatz: parente Neben-
benläufigkeit „Erfolg generali- läufigkeit
Anwendungsentwickler

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

Abbildung 1.4: Ziel des neuen Ansatzes

4
1.2 Aufgabenstellung

Tabelle 1.1: Parallele Programmierung mit Execution System (ES) im Vergleich zu


herkömmlicher paralleler Programmierung im Bezug auf die Verantwort-
lichkeiten der Entwickler der einzelnen Systemschichten
Aufgabe ES Lösung herkömmliche Lösung
Anwendungslogik Anwendungsentwickler
parallele Ausführung ES Entwickler Anwendungsentwickler
OS, Virtualisierung, . . . Betriebssystementwickler
Hardware Hardwareentwickler

zu machen und dabei gleichzeitig die Produktivität zu steigern.


Im Gegensatz zu ähnlichen Ansätzen, wie zum Beispiel FJTask und TBB6 wird der
CES Programmquelltext nicht durch umfangreichen, immer wiederkehrenden, manuell
zu schreibenden Quelltext aufgebläht (sogenannter “boilerplate code”), da ein Compiler
dem Anwendungsentwickler diese Arbeit abnimmt.
Der CES Compiler (CESC) übersetzt CES Quellcode in C Quellcode, wobei die
neuartige Implementierung von Unterprogrammen mit Hilfe einer einfachen sequen-
tiellen Laufzeitumgebung für CES von Wagner auf einem einzelnen Prozessor(-kern)
ausgeführt wird.
Abbildung 1.5a zeigt das bestehende Konzept der Ausführung eines CES Programms
basierend auf dem Stand der Arbeit von Wagner.

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, . . .

Prozessor SMP Prozessoren

(a) Alt (b) Neu

Abbildung 1.5: Blockdiagramm der Ausführung eines CES Programms

Wagner bietet hierzu dem Programmierer die Möglichkeit, Unterprogrammaufrufe in


CES als parallel ausführbar zu deklarieren (siehe Abschnitt 2.2 und Kapitel 3).
Die hierbei konzeptionell zu lösende Aufgabe ist die Synchronisation zwischen den
einzelnen Prozessoren durch die Laufzeitumgebung, welche das hauptsächliche Problem
paralleler Anwendungen ist.
Der CESC von Wagner enthält keine Abstraktion zur Unterstützung unterschiedlicher
Laufzeitumgebungen für CES. Er generiert direkt C Quelltext, welcher ausschließlich mit
der ursprünglichen sequentiellen Laufzeitumgebung für CES von Wagner übersetzbar
ist. Daher wurde der CESC im Zuge dieser Arbeit um ein sauberes Interface zu den
Laufzeitumgebungen für CES erweitert (siehe Abschnitt 2.3).
Die parallele Laufzeitumgebung für CES dieser Arbeit liefert aufgrund 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 Integrationsal-
gorithmen. Algorithmen, die ihre parallele Verarbeitung abbrechen möchten, wie zum
Beispiel unvollständige Suchalgorithmen, werden von dem System hingegen noch nicht
gut unterstützt (siehe Unterabschnitt 4.6.1).
Die Anwendung der Cop/Thief Work-Stealing Laufzeitumgebung für CES wird an-
hand von Sortieralgorithmen, eines Suchalgorithmus, dem N Damen Problem, MemCopy
und der Mandelbrot Menge untersucht.

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.

2.1 Die neue Implementierung von Unterprogrammen

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|.

1 a(...) { ... b(...) ... c(...) }


2 b(...) { ... d(...) ... }
3 c(...) { ... }
4 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 ← x*x ... z ← 42 ... c(x, y, z) }


2 b(...) { ... return(...) }
3 c(x, y, z) { ... x ... y ... z ... }

Listing 2.2: Bei der herkömmlichen Implementierung von Unterprogrammen bekommt


ein Unterprogramm die Rückgabewerte seiner aufgerufenen Unterprogram-
me übergeben, kann diese selbst weiterverarbeiten und anschließend weite-
ren Unterprogrammen übergeben.

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.

aufgerufenen Unterprogramme. Diese werden erst im Anschluss durch ein ES ausgeführt.


Anhand der Pseudocodes in Listing 2.2 und Listing 2.3 werden die Unterschiede
bei der Weiterverarbeitung von Unterprogrammergebnissen unter der Verwendung
der neuen Implementierung von Unterprogrammen gegenüber der Verwendung der
herkömmlichen Implementierung von Unterprogrammen demonstriert. Bei der her-
kömmlichen Implementierung von Unterprogrammen bekommt das Unterprogramm a
den Rückgabewert seines aufgerufenen Unterprogramms b übergeben, kann diesen
selbst weiterverarbeiten und anschließend dem Unterprogramm c übergeben. Bei der
neuen Implementierung von Unterprogrammen kann das Unterprogramm a hingegen
nicht selber auf das Ergebnis seines aufgerufenen Unterprogramms b zugreifen. Daher
muss ein zusätzliches Hilfs-Unterprogramm h zur Weiterverarbeitung der Ergebnis-
se des Unterprogramms b verwendet werden.1 Bei der neuen Implementierung von
Unterprogrammen werden Parameter über Referenzen (im Beispiel durch das den
Variablen vorangestellte „@“ angedeutet) auf Speicherplätze übergeben. Das Unterpro-
gramm a übergibt daher dem Unterprogramm b eine Referenz auf einen Speicherplatz,
an dem das Unterprogramm b sein Ergebnis ablegen soll. Diese Referenz übergibt
es auch dem Unterprogramm h, das später das Ergebnis des Unterprogramms b von
dem Speicherplatz lesen kann. Die Referenzen werden den Unterprogrammen beim
1
In der aktuellen Implementierung von CES ist dies noch Aufgabe des Programmierers. Es ist jedoch
denkbar, diesen Umstand vor dem Programmierer zu verstecken und den Compiler automatisch
entsprechende anonyme Unterprogramme generieren zu lassen.

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.

2.1.1 Activation Frames

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

Unterprogramm ruft es das Unterprogramm b auf. Hierzu wird das Unterprogramm a


unterbrochen, sein Zustand zum Zeitpunkt des Aufrufs in seinem Activation Frame
gespeichert, ein neuer Activation Frame für das Unterprogramm b zuoberst auf dem
Call Stack erzeugt, und in das Unterprogramm b gesprungen. Der Call Stack enthält
nun die Activation Frames der Unterprogramme a und b. Das nun laufende Unterpro-
gramm b ruft seinerseits das Unterprogramm d auf. Dabei wird sein Zustand in seinem
Activation Frame gespeichert, ein neuer Activation Frame für das Unterprogramm d
zuoberst auf dem Call Stack erzeugt und in das Unterprogramm d gewechselt. Der
Call Stack enthält nun die Activation Frames der Unterprogramme a, b und d. Das
nun laufende Unterprogramm d tätigt seinerseits keine Unterprogrammaufrufe und
endet irgendwann. Beim Beenden des Unterprogramms d wird sein Activation Frame
vom Call Stack entfernt und der alte Zustand des Unterprogramms b wiederhergestellt.
Anschließend wird das Unterprogramm b fortgesetzt und kann die Ergebnisse des
Unterprogramms d weiterverarbeiten. Da es keine weiteren Unterprogramme aufruft,
endet es irgendwann, wobei sein Activation Frame vom Call Stack entfernt wird und
der alte Zustand des Unterprogramms a wiederhergestellt wird. Das nun weiterlaufende
Unterprogramm a kann die Ergebnisse des Unterprogramms b weiterverarbeiten und
ruft irgendwann das Unterprogramm c auf, worauf sein Zustand abermals gespeichert
wird, ein neuer Activation Frame für das Unterprogramm c zuoberst auf dem Call Stack
erzeugt wird und in das Unterprogramm c gesprungen wird. Der Call Stack enthält
jetzt die Activation Frames der Unterprogramme a und c. Das Unterprogramm c ruft
seinerseits keine weiteren Unterprogramme auf, so es nach einiger Zeit endet, womit sein
Activation Frame vom Call Stack entfernt wird und der Zustand des Unterprogramms a
wiederhergestellt wird. Anschließend befindet sich wieder Unterprogramm a in der
Ausführung.

2.1.2 Task Frames

Bei der neuen Implementierung von Unterprogrammen von Steinmacher-Burow [SB00a]


werden Unterprogramme mit Hilfe eines Frame Stack von Task Frames abgearbeitet.
Ein Task Frame enthält hierbei die Informationen, die zur Ausführung eines Unterpro-
gramms notwendig sind (z. B. Unterprogrammadresse, Parameter, . . . ), beschreibt also
eine auszuführende Aufgabe (englisch: task). Im Unterschied zu der herkömmlichen
Implementierung von Unterprogrammen mit Activation Frames wird hier bei einem
Unterprogrammaufruf das aktuelle Unterprogramm nicht unterbrochen, sondern der
Aufruf in Form eines neuen Task Frame gemerkt. Erst bei Beendigung des Unter-
programms wird sein Task Frame durch die gesammelten gemerkten Task Frames

11
2 C with Execution System (CES)

seiner aufzurufenden Unterprogramme ersetzt und mit deren Ausführung in deren


ursprünglicher Reihenfolge fortgesetzt. Da das aufrufende Unterprogramm bei einem
Unterprogrammaufruf nicht unterbrochen wird und das aufgerufene Unterprogramm
erst nach Ende der Ausführung des aufrufenden Unterprogramms ausgeführt wird,
kann ein Unterprogramm nicht selber auf die Ergebnisse seiner aufzurufenden Unterpro-
gramme zugreifen. Um die Ergebnisse dennoch weiter verarbeiten zu können, muss ein
Unterprogramm daher andere Unterprogramme mit dieser Aufgabe beauftragen, indem
es die Unterprogramme mit den Ein-/Ausgabe- und Ausgabeparametern seiner bereits
aufgerufenen Unterprogramme als Eingabe- und Ein-/Ausgabeparameter aufruft.

Task Frames stellen gemerkte Unterprogrammaufrufe in Form von Arbeitspaketen


dar, welche über den Datenfluss voneinander abhängen (siehe Abschnitt 3.3). Damit
können sie von anderen Prozessoren einfach gestohlen und somit parallel abgearbeitet
werden, wie es im Abschnitt 3.5 erklärt wird.

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

2.2 CES Syntax


CES erweitert C um die neue Implementierung von Unterprogrammen. Diese sogenann-
ten CES Unterprogramme können an den gleichen Stellen wie normale C Unterpro-
gramme deklariert werden. Sie enthalten ganz normalen C Quelltext und können den
vollständigen Umfang von C nutzen. Zusätzlich zu den normalen C Unterprogramm-
aufrufen können diese jedoch auch CES Unterprogrammaufrufe enthalten. Umgekehrt
ist es allerdings in der aktuellen Implementierung noch nicht einfach möglich, aus C
heraus CES Unterprogramme aufzurufen.
CES Unterprogramme können eine beliebige Anzahl2 an Ein-, Ein-/Aus-, und Aus-
gabeparametern übergeben bekommen. Die Bedeutung der unterschiedlichen CES Pa-
rametertypen ist in Tabelle 2.1 zusammengefasst.

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.

Zur Unterscheidung von C Unterprogrammen werden CES Unterprogramme jeweils


mit „$“ eingeleitet und abgeschlossen (siehe Listing 2.8). Ebenso werden CES Para-
metervariablen und CES Unterprogrammaufrufe in CES Unterprogrammen mit „$“
2
In der aktuellen Implementierung ist die maximale Anzahl der Ein- Ein-/Aus- und Ausgabeparameter
jedoch auf insgesamt 25 beschränkt.

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 - } - $ -
  

Abbildung 2.3: CES Syntax als Railroad-Diagramm

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 - $ -
 

Abbildung 2.3: CES Syntax als Railroad-Diagramm (Fortsetzung)

15
2 C with Execution System (CES)

$<unterprogrammbezeichner>([<eingabeparameterdefinitionsliste>];
[<ein-/ausgabeparameterdefinitionsliste>];
[<ausgabeparameterdefinitionsliste>]){
...
}$

Listing 2.4: CES Unterprogrammdefinition

$[parallel] <unterprogrammbezeichner>([<eingabeparameterliste>]; '


[<ein-/ausgabeparameterliste>]; [<ausgabeparameterliste>]);$

Listing 2.5: CES Unterprogrammaufruf

umschlossen (siehe Listing 2.9).


CES gehört wie auch C zu den typisierten Programmiersprachen. CES Unterpro-
grammparameter sind daher immer typbehaftet. Ihr Variablentyp wird analog zu
C bei der CES Unterprogrammdefinition vor dem Namen ihrer Parametervariablen
angegeben. Sowohl der Typbezeichner als auch der Namensbezeichner müssen dabei
dem regulären Ausdruck [a-zA-Z_]+ [a-zA-Z0-9_]* genügen.3 Daher können aus meh-
reren Bezeichnern bestehende Variablentypen (z. B. unsigned int) oder komplexere
Variablentypen (z. B. char * oder int (*)(int)) aus C nur über benutzerdefinierte
Variablentypen in CES Unterprogrammdefinitionen und -aufrufen verwendet werden
(siehe folgende Definition von argv_t).
Bei der CES Unterprogrammdefinition (siehe Listing 2.4) besteht eine Parameter-
definition immer aus einem Variablentypenbezeichner gefolgt von einem Variablen-
namensbezeichner. Mehrere Parameterdefinitionen eines Parametertyps (Eingabe-,
Ein-/Ausgabe- oder Ausgabeparameter) werden durch Kommata getrennt zu einer Pa-
rameterdefinitionsliste zusammengefasst. Diese Parameterdefinitionslisten werden durch
Semikola getrennt in folgender Reihenfolge angegeben: zuerst die Eingabeparameterliste,
anschließend die Ein-/Ausgabeparameterliste und zuletzt die Ausgabeparameterlis-
te. Auch eine leere Parameterliste muss immer mit angegeben werden, so dass die
Parameterdefinition eines CES Unterprogramms immer genau zwei Semikola enthält.
Beim CES Unterprogrammaufruf (siehe Listing 2.5) werden die Übergabepara-
meter entsprechend der Reihenfolge der CES Unterprogrammdefinition angegeben.
Eingabe- und Ein-/Ausgabeparameter müssen immer bestehende C Variablen oder
CES Parametervariablen sein. Ausgabeparameter können hingegen bestehende CES Pa-
rametervariablen sein oder sie erzeugen neue CES Parametervariablen. Die derzeitige

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 }$

Listing 2.6: Call-by-Value Parameterübergabe von C Variablen beim CES Unterpro-


grammaufruf

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 }$

Listing 2.7: Call-by-Reference Parameterübergabe von CES Parametervariablen beim


CES Unterprogrammaufruf

7 #include <stdio.h> /* printf() */


8
9 /**
10 * CES Subroutine PROGRAM
11 *
12 * @param[in] argc the argument count.
13 * @param[in] argv the argument vector.
14 */
15 $program(int argc, argv_t argv;;){
16 printf("Hello, world!\n");
17 }$

Listing 2.8: Hello World in CES – helloworld.ces

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.9: Mergesort in CES (CES Unterprogramm mergesort)

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 }$

Listing 2.10: Mergesort in CES (CES Unterprogramm merge)

1/8

1/4 1/8 1/8 1/8

1/2 1/4 1/4 1/4 1/4 1/8 1/8

1 1/2 1/2 1/2 1/2 1/2 1/2 1/2 ...

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.

2.3 CES Compiler (CESC)

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)

CES CES Compiler C Compiler ausführbare


C Quelltext
Quelltext Datei

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.

2.3.1 Erweiterung des CES Compiler um die Unterstützung alternativer


Laufzeitumgebungen für CES

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)

manuellen Arbeiten zu lösen.


Darüber hinaus wurde während der ersten Anpassungen der Codegenerierung des
CESC erkannt, dass mit Hilfe von Makros im generierten C Quelltext ein Interface zu
den verschiedenen Laufzeitumgebungen für CES geschaffen werden kann. Im Rahmen
dieser Arbeit wurden daher umfangreiche Veränderungen an der Codegenerierung des
CESC vorgenommen und die sequentielle Laufzeitumgebung für CES entsprechend
angepasst, um unterschiedliche Laufzeitumgebungen für CES durch ein sauberes In-
terface (siehe Anhang A) mit ein und demselben generierten Quelltext zu bedienen.
Hierbei wurde massiv vom Einsatz von Makros Gebrauch gemacht, die den ursprünglich
statisch generierten C Quelltext ersetzen und erst zur Übersetzungszeit durch den
C Präprozessor (CPP) expandiert werden. Jede Laufzeitumgebung für CES definiert
diese Makros entsprechend ihren eigenen Bedürfnissen. Damit wird der einfache Einsatz
unterschiedlicher Laufzeitumgebungen für CES unabhängig von dem durch den CESC
generierten C Quelltext möglich. Dieses Makro-Interface wurde im Laufe der Entwick-
lung der round-robin Laufzeitumgebung für CES und der parallelen Laufzeitumgebung
für CES nach und nach weiterentwickelt.
In Abbildung 2.6 wird daher zum Wechsel der Laufzeitumgebung für CES für ein
gegebenes CES Programm nur die Laufzeitumgebung ausgetauscht, die aus *.h und *.c
C Quelltextdateien besteht, die die C Makrodefinitionen und das C-Hauptprogramm
enthalten. Zur Auswahl stehen drei alternative Laufzeitumgebungen für CES, die
sequentielle Laufzeitumgebung für CES, die round-robin Laufzeitumgebung für CES
und die parallele Laufzeitumgebung für CES.
Die angepasste sequentielle Laufzeitumgebung für CES wird im Folgenden Ab-
schnitt 2.4 erläutert, die Entwicklung der round-robin Laufzeitumgebung für CES wird
im Abschnitt 3.6 erklärt und die Entwicklung der parallelen Laufzeitumgebung für
CES im Kapitel 3.
Im Folgenden wird, wenn nicht anders angegeben, ausschließlich auf den angepassten
CESC und die angepasste sequentielle Laufzeitumgebung für CES Bezug genommen.

2.3.2 Vom CES Compiler generierter C Quelltext für ein


CES Unterprogramm

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)

1 /* PUSH STORAGE FOR C VARIABLE ’nleft’ TO FRAME STACK */


2 RUNTIME_CREATE_STORAGE_CVAR(nleft, size_t, nleft);
3
4 /* PUSH STORAGE FOR C VARIABLE ’left’ TO FRAME STACK */
5 RUNTIME_CREATE_STORAGE_CVAR(left, ptr_t, left);
6
7 /* PUSH TASK ’mergesort’ TO CURRENT STACK */
8 RUNTIME_CREATE_TASK(mergesort, 0, 0, 3, 1, 0);
9 RUNTIME_NEWTASK_PARAMIN_REFERENCE(mergesort, 1) = RUNTIME_STORAGE_REFERENCE(nleft);
10 RUNTIME_NEWTASK_PARAMIN_REFERENCE(mergesort, 2) = RUNTIME_TASK_PARAMIN_REFERENCE(2);
11 RUNTIME_NEWTASK_PARAMIN_REFERENCE(mergesort, 3) = RUNTIME_TASK_PARAMIN_REFERENCE(3);
12 RUNTIME_NEWTASK_PARAMINOUT_REFERENCE(mergesort, 1) = RUNTIME_STORAGE_REFERENCE(left);

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.

2.3.3 Vom CES Compiler generierter C Quelltext für einen


CES Unterprogrammaufruf

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;);$

Bei dem Unterprogrammaufruf sollen dem CES Unterprogramm mergesort folgende


Parameter übergeben werden: Als Eingabeparameter die C Variable size_t nleft,
die CES Parametervariable size sowie die CES Parametervariable compare, als Ein/-
Ausgabeparameter die C Variable ptr_t left und keine Ausgabeparameter. Zuerst
müssen alle benötigten neuen CES Parametervariablen angelegt werden. Dies ge-
schieht mit Hilfe der beiden von der Laufzeitumgebung zu definierenden Makros
RUNTIME_CREATE_STORAGE_CVAR(...) für die Übergabe von C Variablen als Eingabe-
oder Ein-/Ausgabeparameter und RUNTIME_CREATE_STORAGE_OUTPUT(...) für die Er-
zeugung einer neuen CES Parametervariablen für einen Ausgabeparameter. Im Beispiel
müssen für die beiden C Variablen nleft und left neue CES Parametervariablen
mit Hilfe des Makros RUNTIME_CREATE_STORAGE_CVAR(...) angelegt werden (Zeilen
2 und 5), dem der Bezeichner der neuen CES Parametervariable, deren Variablen-
typ und der initiale Wert der C Variable übergeben wird. Anschließend wird ein
neuer Task Frame zum Merken des CES Unterprogrammaufrufs mit Hilfe des von
der Laufzeitumgebung zu definierenden Makros RUNTIME_CREATE_TASK angelegt, das
den Bezeichner des zu merkenden CES Unterprogramms, die Werte für den ance-
stor und parallel Schalter und die Anzahl an Eingabe-, Ein-/Ausgabe- und Ausga-
beparametern übergeben bekommt. Zuletzt werden die Parameter des zu merken-
den CES Unterprogrammaufrufs im neuen Task Frame mit Hilfe der von der Lauf-
zeitumgebung zu definierenden Makros RUNTIME_NEWTASK_PARAM*_REFERENCE(...),
RUNTIME_STORAGE_REFERENCE(...) und RUNTIME_TASK_PARAM*_REFERENCE vermerkt.
Die Makros bekommen je nach Typ entweder den CES Unterprogrammbezeichner und
die Parameternummer, den CES Parametervariablenbezeichner oder die Parameter-
nummer übergeben.
Tabelle 2.3 listet die Parameternummernzuordnung des CESC für die Parameter des
CES Unterprogramms mergesort aus Listing 2.9 auf.

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)

Tabelle 2.3: Übersicht über die Parameternummern des CES Unterprogramms


mergesort aus Listing 2.9
CES Parametertyp ParameterNummer
Parameter-
variable
n eingabe 1
size eingabe 2
compare eingabe 3
array ein-/ausgabe 1

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

Task Frame des laufenden CES Unterprogramms erfolgt.


Tabelle 2.3 listet die Parameternummernzuordnung des CESC für die Parameter des
CES Unterprogramms mergesort aus Listing 2.9 auf.

2.4 Sequentielle Laufzeitumgebung für CES (Sequential CES


Runtime)

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)

auch zu Veranschaulichungszwecken benutzt werden.


Listing 2.15 zeigt das C Unterprogramm exec_top_of_fs der sequentiellen Laufzeit-
umgebung für CES zur sequentiellen Abarbeitung der Task Frames des Frame Stack.
Das Unterprogramm ist eine einfache Schleife, die jeweils den obersten Task Frame des
Frame Stacks verarbeitet, bis der Frame Stack leer ist.

2.4.1 Aufbau der Task Frames der sequentiellen Laufzeitumgebung für


CES

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

Funktionszeiger auf Unterprogramm







Array von Zeigern auf Parameter des Unterprogramms




(Ein-, Ein/Aus- und Ausgabeparameter)






hhh 

hhhh
h hh h
hhhh h hh Task
hhhhhhhhhhhhh



hhhh h 

hhhhhhhhh 


hhhh h hh
hhhh


hhhhh
h





Funktionszeiger auf Pretty Printer Unterprogramm






Zeiger auf Namenszeichenkette



Pretty Printer
ancestor flag 



parallel flag

Abbildung 2.7: Aufbau der Struktur TASK_FRAME (Sequentielle Laufzeitumgebung


für CES)

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)

1 typedef struct TASK_FRAME {


2 void (*fnptr_task)(struct TASK_FRAME ** /* fsp */, struct TASK_FRAME * /* csp */);
3 void *parameter[ARG_SIZE];
4 #ifdef CES_PRETTYPRINTER
5 void (*fnptr_print)(void);
6 char *name;
7 int ancestor;
8 int parallel;
9 #endif /* CES_PRETTYPRINTER */
10 } TASK_FRAME;

Listing 2.16: Definition der Struktur TASK_FRAME (Sequentielle Laufzeitumgebung


für 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 }

Listing 2.17: Storage Unterprogramm aus run_time.c der sequentiellen Laufzeitumge-


bung für CES

2.4.2 Aufbau der Storage Frames der sequentiellen Laufzeitumgebung für


CES

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





Abbildung 2.8: Aufbau der Struktur STORAGE_FRAME (Sequentielle Laufzeitumge-


bung für CES)

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;

Listing 2.18: Definition der Struktur STORAGE_FRAME (Sequentielle Laufzeitumge-


bung für CES)

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.

2.4.3 Sequentielle Ausführung von CES Programmen mit der


sequentiellen Laufzeitumgebung für CES

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;)

(a) t0 (b) t1 (c) t2

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;)

(d) t3 (e) t4 (f) t5

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;)

(g) t6 (h) t7 (i) t8

merge(n/2, left0 , n/2, right0 ,


size, compare; array;)

(j) t9

Task Frame, der sich in Bearbeitung durch den Prozessor befindet

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

left1 right1 left2 right2

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)

Die neue Implementierung von Unterprogrammen von Steinmacher-Burow weist einen


entscheidenden Vorteil gegenüber der herkömmlichen Implementierung von Unterpro-
grammen auf: Unterprogrammaufrufe als Aufgaben stellen saubere Arbeitspakete dar,
welche durch klare Datenabhängigkeiten voneinander abhängig sind. Sie sind somit zur
parallelen Abarbeitung auf mehreren Prozessoren geeignet, sofern mehrere von ihnen
zeitgleich keine Abhängigkeiten mehr aufweisen.
Im Folgenden wird zunächst genauer auf die Datenabhängigkeiten zwischen den
Task Frames eingegangen und eine einfache Möglichkeit der statischen Markierung von
unabhängigen Task Frames zur Übersetzungszeit von Steinmacher-Burow sowie das
Konzept des Cop/Thief Work-Stealing von Steinmacher-Burow vorgestellt. Anschließend
wird die Konzeption und Entwicklung der parallelen Laufzeitumgebung für CES mit
dem Zwischenschritt der Entwicklung einer round-robin Laufzeitumgebung für CES im
Detail erläutert.

3.1 Problem der Identifizierung potentieller Parallelitäten


zwischen Unterprogrammaufrufen bei der herkömmlichen
Implementierung von Unterprogrammen

Bei der herkömmlichen Implementierung von Unterprogrammen ist es schwierig, po-


tentielle Parallelitäten zwischen Unterprogrammaufrufen eines Unterprogramms zu
erkennen, da die Unterprogrammaufrufe immer sofort bei ihrem Auftreten ausgeführt
werden. Folgende Unterprogrammaufrufe werden erst nach dem Ende vorhergehender
Unterprogrammaufrufe erreicht. Zur Feststellung von Paralellitäten zwischen den Un-
terprogrammaufrufen wäre es jedoch notwendig, die folgenden Unterprogrammaufrufe
bereits zu kennen, um diese gegebenenfalls parallel auszuführen. Da die Unterprogramm-
aufrufe sequentiell erreicht werden und immer sofort bei ihrem Auftreten ausgeführt
werden, werden die Unterprogramme sequentiell ausgeführt.

39
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)

3.2 Identifizierung potentieller Parallelitäten zwischen


Unterprogrammaufrufen bei der neuen Implementierung
von Unterprogrammen

Bei der neuen Implementierung von Unterprogrammen werden die Unterprogramm-


aufrufe eines Unterprogramms hingegen nicht sofort ausgeführt, sondern gesammelt
und nach dem Ende des aufrufenden Unterprogramms ausgeführt. Dadurch ist es
möglich, die gesammelten Unterprogrammaufrufe vor ihrer Ausführung auf mögliche
Parallelitäten zu analysieren und diese entsprechend zu nutzen.

3.3 Datenabhängigkeiten zwischen Task Frames

CES Unterprogrammaufrufe werden nicht sofort ausgeführt, sondern gesammelt und


erst nach Ende des laufenden CES Unterprogramms in ihrer ursprünglichen Reihenfolge
abgearbeitet. Dabei stehen die gemerkten CES Unterprogrammaufrufe, wie bereits
im Abschnitt 2.1 erläutert, über die Referenzen auf Speicherplätze für ihre Parame-
tervariablen untereinander in Verbindung und bilden einen gerichteten azyklischen
Graphen.
Ein-/Ausgabe- und Ausgabeparameter von CES Unterprogrammaufrufen erzeugen
Daten, welche von Eingabe- und Ein-/Ausgabeparametern anderer CES Unterpro-
grammaufrufe konsumiert werden, welche wiederum neue Daten in Form ihrer Ein-
/Ausgabe- und Ausgabeparameter erzeugen können. Ein CES Unterprogrammaufruf,
welcher Daten über seine Eingabe- oder Ein-/Ausgabeparameter konsumiert, ist folg-
lich erst ablaufbereit, sobald alle seine benötigten Daten von den vorhergehenden
CES Unterprogrammaufrufen produziert wurden.
Auf dem Task Frame Stack befindet sich somit zu einem jeden Zeitpunkt immer
eine Abfolge von Task Frames, welche über die CES Parametervariablen ihrer gemerk-
ten CES Unterprogrammaufrufe miteinander verbunden sind und einen gerichteten
azyklischen Graphen bilden. Die sequentielle Last In – First Out (LIFO) Abarbei-
tung der Task Frames stellt nicht nur die herkömmliche depth-first Ausführung (siehe
Abschnitt 2.1) sicher, sondern auch die Produktion der von einem jeden CES Unter-
programm zu konsumierenden Daten in der richtigen Reihenfolge. Daraus folgt, dass
immer mindestens die Datenflussabhängigkeiten des CES Unterprogrammaufrufs des
obersten Task Frame vollständig erfüllt sind und der oberste Task Frame somit immer
bereit zur Verarbeitung ist.
Betrachtet man den Algorithmus Mergesort aus Listing 2.9 noch einmal genauer,

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

merge(left, n/2, right,


n/2, compare; array;)

Abbildung 3.1: Datenflussgraph der CES Unterprogrammaufrufe des CES Unterpro-


gramms mergesort aus Listing 2.9

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.

3.4 Markierung parallel ausführbarer


CES Unterprogrammaufrufe zur Übersetzungszeit
In der aktuellen Implementierung von CES ist es möglich, CES Unterprogrammaufrufe
im Quelltext als parallel ausführbar zu markieren. Hierzu dient das im Abschnitt 2.2
erwähnte Schlüsselwort parallel vor dem Unterprogrammbezeichner beim CES Un-

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 }$

Listing 3.1: Zulässige Markierung parallel ausführbarer CES Unterprogrammaufrufe

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 }$

Listing 3.2: Unzulässige Markierung parallel ausführbarer CES Unterprogrammaufrufe

terprogrammaufruf. Der Programmierer darf nur solche Unterprogrammaufrufe als


parallel ausführbar kennzeichnen, deren Datenabhängigkeiten bereits erfüllt sind, die
also nach dem Ende des Unterprogramms sofort parallel zueinander ausgeführt werden
können.
In Listing 3.1 und Listing 3.2 werden die zulässige und unzulässige Anwendung
der Markierung parallel ausführbarer CES Unterprogrammaufrufe an einem einfachen
Beispiel demonstriert.
Listing 3.3 zeigt die Erweiterung der sequentiellen Implementierung von Merge-
sort in CES aus Listing 2.9 um die Markierung der parallel ausführbaren CES Un-
terprogrammaufrufe mit Hilfe des parallel Schlüsselworts. Die beiden mergesort
CES Unterprogrammaufrufe dürfen als parallel ausführbar markiert werden, da ihre
Datenflussabhängigkeiten beim Ende des Unterprogramms erfüllt sind. Der merge
CES Unterprogrammaufruf muss hingegen im Anschluss an die beiden parallel aus-
führbaren mergesort Unterprogrammaufrufe ausgeführt werden, da er von deren
Ergebnissen abhängig ist, und darf somit nicht als parallel ausführbar markiert werden.

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 Parallele Ausführung von CES Programmen mittels


Cop/Thief Work-Stealing
Die parallele Laufzeitumgebung für CES soll das Cop/Thief Work-Stealing Konzept
zur Verteilung der Arbeit auf mehrere Prozessoren verwenden.
In diesem Unterkapitel wird zunächst das Konzept des Work-Stealing erläutert und
anschließend mit anderen Konzepten der Verteilung von Arbeit auf mehrere Prozessoren
verglichen. Anschließend wird die Erweiterung des klassischen Work-Stealing zum
Cop/Thief Work-Stealing erklärt.

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 }$

Listing 3.3: Parallele Implementierung von Mergesort in CES (CES Unterprogramm


mergesort)

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;)

(a) t0 (b) t1 (c) t2

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/2 1/4 1/8 1/4 1/8 1/8 1/8 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

folgenden Zeitpunkt t2 stiehlt Prozessor C ebenfalls ein Arbeitspaket von Prozessor A,


welcher dadurch zum Zeitpunkt t4 selbst keine Arbeit mehr hat und daher im folgenden
Zeitpunkt t5 von Prozessor B ein Arbeitspaket stiehlt. Zum Zeitpunkt t6 stiehlt aus
gleichem Grund Prozessor B von Prozessor A ein Arbeitspaket.

3.5.2 Cop/Thief Work-Stealing


Das Konzept des Cop/Thief Work-Stealing von Steinmacher-Burow [SB00a, Kap. 10,
S. 30–39] ist eine Erweiterung des klassischen Konzepts des Work-Stealing.
Wie beim Work-Stealing besitzt auch beim Cop/Thief Work-Stealing ein jeder
Prozessor eine eigene Warteschlange für Arbeitspakete – den Frame Stack (LIFO War-
teschlange) –, den er wie gehabt in sequentieller Reihenfolge von oben nach unten
abarbeitet. Besitzt ein Prozessor keine Task Frames mehr auf seinem Frame Stack oder
sind diese durch Datenabhängigkeiten blockiert, so versucht er welche von anderen
Prozessoren zu stehlen. Beim klassischen Work-Stealing befinden sich ausschließlich
unabhängige Arbeitspakete in den Warteschlangen der Prozessoren. Bei der neuen
Implementierung von Unterprogrammen von Steinmacher-Burow befinden sich jedoch
sowohl unabhängige als auch durch Datenabhängigkeiten blockierte Task Frames (Ar-
beitspakete) auf dem Frame Stack eines Prozessors. Bei der sequentiellen Abarbeitung
der Task Frames werden diese Abhängigkeiten automatisch immer korrekt eingehalten
(siehe Abschnitt 2.1). Für die parallele Abarbeitung der Task Frames ist jedoch ein
Mechanismus notwendig, der die korrekte Abarbeitungsreihenfolge der Task Frames
sicherstellt.
Das Cop/Thief Work-Stealing Konzept ist eine saubere Synchronisationslösung, bei
der Synchronisationsaktionen in Form von speziellen Synchronization Frames in den
Frame Stack eingefügt werden. Diese Synchronization Frames blockieren gegebenenfalls
die weitere sequentielle Abarbeitung des Frame Stack eines Prozessors, bis die notwen-
digen Abhängigkeiten gelöst sind und ermöglichen es dem Prozessor derweil, als parallel
abarbeitbar markierte Task Frames von den Frame Stacks der anderen Prozessoren zu
stehlen und abzuarbeiten. Das Konzept nutzt hierzu in der aktuellen Implementierung
ausschließlich die statische Information, ob ein Task Frame als parallel abarbeitbar
markiert ist.
Beim Konzept des Cop/Thief Work-Stealing werden die beiden folgenden speziellen
Synchronization Frames zur Synchronisation verwendet:

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

...

COP0 T HIEF0 T HIEFn−2

P0 P1 ... Pn−1

Abbildung 3.4: Initialisierung des Cop/Thief Work-Stealing am Beispiel der parallelen


Ausführung eines CES Programms

(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.

Abbildung 3.5: Cop/Thief Work-Stealing


50
3.5 Parallele Ausführung von CES Programmen mittels Cop/Thief Work-Stealing

mergesort(n, size,
compare; array;)

COP1

COP0 T HIEF0 T HIEF1

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 ;)

merge(n/2, left0 , n/2, right0 ,


size, compare; array;)

COP1

COP0 T HIEF0 T HIEF1

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 ;)

merge(n/4, left1 , n/4, right1 ,


size, compare; left0 ;)

T HIEF2

merge(n/2, left0 , n/2, right0 , mergesort(n/2, size,


size, compare; array;) compare; right0 ;)

COP1 COP2

COP0 T HIEF0 T HIEF1

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

merge(n/4, left1 , n/4, right1 , mergesort(n/4, size,


size, compare; left0 ;) compare; left2 ;)

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 ;)

COP1 COP2 COP3

COP0 T HIEF0 T HIEF1

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

merge(n/4, left1 , n/4, right1 ,


size, compare; left0 ;)

mergesort(n/4, size,
T HIEF2
compare; right2 ;)

merge(n/2, left0 , n/2, right0 , merge(n/4, left2 , n/4, right2 ,


size, compare; array;) size, compare; right0 ;)

COP1 COP2 COP3

COP0 T HIEF0 T HIEF1

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.

merge(n/4, left1 , n/4, right1 ,


size, compare; left0 ;)

T HIEF2

merge(n/2, left0 , n/2, right0 , merge(n/4, left2 , n/4, right2 ,


size, compare; array;) size, compare; right0 ;)

COP1 COP2

COP0 T HIEF0 T HIEF1

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:

When a processor begins work stealing, it operates as follows. The processor


becomes a thief and attempts to steal work from a victim processor chosen
uniformly at random. The thief queries the ready deque of the victim, and
if it is nonempty, the thief removes and begins work on the top thread
[Anm.: Blumofes Stack arbeitet entgegengesetzt zu dem in dieser Arbeit
beschriebenen.]. If the victim’s ready deque is empty, however, the thief
tries again, picking another victim at random. ([Blu95, S. 38])

Ü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

1638 while (!t && !USE_SHARED(done)) {


1639 /* otherwise, steal */
1640 Cilk_enter_state(ws, STATE_STEALING);
1641 victim = rts_rand(ws) % USE_PARAMETER(active_size);
1642 if (victim != ws->self) {
1643 t = Closure_steal(ws, victim);
1644 if (!t && USE_PARAMETER(options->yieldslice) && !USE_SHARED(done)) {
1645 Cilk_lower_priority(ws);
1646 }
1647 }
1648 /* Cilk_fence(); */
1649 Cilk_exit_state(ws, STATE_STEALING);
1650 }

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:

IDvictim = random() mod N (3.2)

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.

3.6 Round-Robin Laufzeitumgebung für CES (Round-Robin


CES Runtime)
Die Entwicklung und Implementierung der round-robin Laufzeitumgebung für CES
stellt einen Zwischenschritt zur Entwicklung der parallelen Laufzeitumgebung für CES
dar. Die round-robin Laufzeitumgebung für CES arbeitet, ähnlich wie in Abbildung 3.6
gezeigt, ein CES Programm mit mehreren virtuellen Prozessoren Pi mit jeweils eigenem
Frame Stack und dem Cop/Thief Work-Stealing Konzept aus Unterabschnitt 3.5.2
pseudoparallel ab, indem sie jeweils reihum einen Frame eines jeden Frame Stack F Si
verarbeitet, als würde er auf dem virtuellen Prozessor Pi abgearbeitet. Die round-robin
Laufzeitumgebung für CES kommt daher aufgrund der fehlenden Nebenläufigkeit ohne
über die Synchronization Frames hinausgehende Synchronisation und die damit ver-
bundenen zu berücksichtigenden Komplikationen aus. Die notwendige Synchronisation
des Cop/Thief Work-Stealing beim Einsatz von Threads zur parallelen Ausführung
von CES Programmen wird im folgenden Abschnitt 3.9 eingeführt.
Für die round-robin Ausführung von CES Programmen werden sehr ähnliche Da-
tenstrukturen benötigt, wie sie auch für die parallele Ausführung benötigt werden.

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.

3.6.1 Änderungen gegenüber der sequentiellen Laufzeitumgebung für CES

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)

1 typedef struct THREAD_DATA {


2 struct {
3 TASK_FRAME frame_stack[FS_SIZE]; /**< frame stack */
4 TASK_FRAME current_stack[FS_SIZE]; /**< current frame stack */
5 } data;
6 TASK_FRAME * fsp; /**< frame stack pointer */
7 TASK_FRAME * csp; /**< current stack pointer */
8 /* debugging information */
9 unsigned int executes; /**< executed task counter */
10 } THREAD_DATA;

Listing 3.6: Definition der Struktur THREAD_DATA (Round-Robin Laufzeitumge-


bung für CES)

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.

3.6.1.1 Unterstützung mehrerer Frame und Current Stacks

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

Funktionszeiger auf Pretty Printer Unterprogramm






Zeiger auf Namenszeichenkette Pretty Printer

ancestor flag

Abbildung 3.7: Aufbau der Struktur TASK_FRAME (Round-Robin Laufzeitumgebung


für CES)

1 typedef struct TASK_FRAME {


2 void (*fnptr_task)(struct TASK_FRAME ** /* fsp */, struct TASK_FRAME * /* csp */);
3 void *parameter[ARG_SIZE];
4 int parallel;
5 #ifdef CES_PRETTYPRINTER
6 void (*fnptr_print)(void);
7 char *name;
8 int ancestor;
9 #endif /* CES_PRETTYPRINTER */
10 } TASK_FRAME;

Listing 3.7: Definition der Struktur TASK_FRAME (Round-Robin Laufzeitumgebung


für CES)

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

Das Unterprogramm der Frame Stack Abarbeitungsschleife exec_top_of_fs der se-


quentiellen Laufzeitumgebung für CES aus Listing 2.15 wurde um die round-robin
Abarbeitung der Frame Stacks erweitert. Listing 3.8 zeigt das erweiterte Unterprogramm

59
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)

169 void exec_top_of_fs() {


170 int running = TRUE;
171 int thread;
172 THREAD_DATA * td;
173
174 while (running) {
175 running = FALSE;
176
177 for (thread = 0; thread < threads; ++thread) {
178 td = &thread_data[thread];
179 #if CES_PRETTYPRINTER
180 printf("\t****************************** THREAD %d ******************************\n'
", thread);
181 #endif /* CES_PRETTYPRINTER */
182 if (td->fsp >= FS_BOTTOM(td)) {
183 #if CES_PRETTYPRINTER
184 if(td->fsp->ancestor == 0 && td->fsp->fnptr_task != func_storage) {
185 printf("\t*****************EXECUTE %d****************\n", td->executes);
186 show_fs(td->fsp, FS_BOTTOM(td));
187 td->fsp->fnptr_task(&(td->fsp), td->csp); /* execute task */
188 td->executes++;
189 } else {
190 td->fsp -= 1; /* set frame stack pointer to next task - skip ancestors and '
storages */
191 printf("\t STORAGE\n");
192 }
193 #else /* CES_PRETTYPRINTER */
194 td->fsp->fnptr_task(&(td->fsp), td->csp); /* execute task */
195 td->executes++;
196 #endif /* CES_PRETTYPRINTER */
197 running = running || (td->fsp >= FS_BOTTOM(td));
198 }
199 }
200 }
201 }

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 Frame Stack Abarbeitungsschleife exec_top_of_fs der round-robin Laufzeitumge-


bung für CES.
Die zwei ineinander verschachtelten Schleifen (Zeilen 174 und 177) bearbeiten jeweils
reihum einen Frame eines jeden Frame Stack F Si des virtuellen Prozessors Pi , bis alle
Frame Stacks leer sind.

3.6.1.4 Implementierung des Cop Unterprogramms

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 typedef struct COP_TASK_FRAME {


2 void (*fnptr_cop_task)(TASK_FRAME ** /* fsp */, TASK_FRAME * /* csp */);
3 /* parameters */
4 struct THIEF_TASK_FRAME * thief; /**< reference to the thief to kill */
5 } COP_TASK_FRAME;

Listing 3.9: Definition der Struktur COP_TASK_FRAME (Round-Robin Laufzeitum-


gebung für CES)

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 }

Listing 3.10: Cop Unterprogramm (Round-Robin Laufzeitumgebung für CES)

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.

3.6.1.5 Implementierung des Thief Unterprogramms

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)

1 typedef struct THIEF_TASK_FRAME {


2 void (*fnptr_thief_task)(TASK_FRAME ** /* fsp */, TASK_FRAME * /* csp */);
3 /* parameters */
4 int killed; /**< boolean to notify thief about beeing killed by its cop */
5 int threadIndex; /**< own thread index */
6 } THIEF_TASK_FRAME;

Listing 3.11: Definition der Struktur THIEF_TASK_FRAME (Round-Robin Laufzeit-


umgebung für CES)

ting 3.12 zeigt die Definition des speziellen Thief Unterprogramms in C.


Das spezielle Thief Unterprogramm muss in der Implemementierung der round-
robin Laufzeitumgebung für CES diese spezielle Abarbeitung berücksichtigen. Das
Unterprogramm darf beispielsweise nicht endlos in einer Schleife als parallel ausführbar
markierte Task Frames von fremden Frame Stacks stehlen, sondern muss sich nach
dem erfolglosen Scannen aller fremden Frame Stacks beenden, wobei es seinen T HIEF
Frame nicht von seinem Frame Stack entfernt. Dadurch wird gewährleistet, dass die
round-robin Abarbeitung mit dem nächsten virtuellen Prozessor fortschreiten kann.
Das Thief Unterprogramm scannt daher in einer Schleife (Zeile 8) maximal einmal
alle fremden Frame Stacks, sofern der Thief nicht das Signal zum Beenden von seinem
Cop erhalten hat. Stößt es beim Scannen auf einen stehlbaren Task Frame, so stiehlt
es ihn (Zeilen 15–33), wie in Unterabschnitt 3.5.2 beschrieben und beendet sich. Hat
der Thief hingegen von seinem zugehörigen Cop das Signal zum Beenden erhalten, so
entfernt er seinen T HIEF Frame von seinem Frame Stack (Zeilen 41–44).

3.6.2 Beispiel der Ausgabe des Pretty Printer der round-robin


Laufzeitumgebung für CES

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.

3.7 Die POSIX Threads (Pthreads) Bibliothek für Threads in


C
POSIX Threads (Pthreads) ist ein Portable Operating System Interface Standard
für Threads vom Institute of Electrical and Electronics Engineers (IEEE) Portable
Applications Standards Committee.
Die Pthreads Bibliothek stellt eine Application Programming Interface (API) zur
Programmierung von Threads in C und C++ zur Verfügung. Zur Nutzung von Pthreads

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 }

Listing 3.12: Reduziertes Thief Unterprogramm (Round-Robin Laufzeitumgebung für


CES)

63
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)

muss die Anwendung die Bibliothek mittels #include <pthread.h> einbinden.

3.7.1 Erzeugung von Threads

int pthread_create(pthread_t * thread, const pthread_attr_t * attributes,


void * (*thread_subroutine)(void *), void * argument);

Listing 3.13: Die Pthreads API zum Erzeugen von Threads

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.

3.7.2 Beendigung und Abbruch von Threads

void pthread_exit(void * status);


int pthread_cancel(pthread_t thread);

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

besser pthread_exit(...) beenden und Informationen mittels status zurückliefern.


Endet sein Unterprogramm thread_subroutine, so beendet sich der Thread ebenfalls
selbständig. pthread_exit(...) gibt alle threadspezifischen Daten und seinen Stack
frei, daher müssen vorher alle auf seinem Stack erzeugten Objekte zerstört werden. Im
Gegensatz zum C Unterprogramm exit gibt pthread_exit(...) allerdings nicht die
zwischen Threads geteilten Resourcen (z. B. Dateien) frei, da sie noch von anderen
Threads in Verwendung sein könnten.
Listing 3.17 zeigt die Verwendung von pthread_exit zur Übergabe von Informationen
an den zusammenführenden Thread.
Mittels pthread_cancel(...) kann ein laufender Thread von außerhalb kontrolliert
abgebrochen werden. Der abzubrechende Thread kann den Abbrechungswunsch ab-
weisen oder zurückhalten und Aufräumarbeiten durchführen. Beim Abbruch ruft der
Thread implizit pthread_exit((void *)-1) auf.

3.7.3 Zusammenführung von Threads

int pthread_join(pthread_t thread, void ** status);

Listing 3.15: Die Pthreads API zum Zusammenführen von Threads

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.

3.7.4 Unterbrechung von Threads

int pthread_yield();

Listing 3.16: Die Pthreads API zum Unterbrechen von Threads

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)

1 void * thread_subroutine(void * argument) {


2 /* cast void argument pointer to custom type pointer */
3 myargtype_t * myargument = (myargtype_t *)argument;
4 ...
5 // thread routine
6 ...
7 /* pass information to joining thread by returning a void pointer to a custom datastructure'
*/
8 myreturntype_t * returndata = malloc(sizeof(returndata)); /* joining thread has to free '
memory */
9 ...
10 // populate datastructure
11 ...
12 pthread_exit((void *)returndata);
13 /* or (above style is preferred) */
14 return (void *)returndata;
15 }

Listing 3.17: C Unterprogramm von Threads mit Pthreads

3.7.5 Das Thread-Unterprogramm

Listing 3.17 zeigt den grundlegenden Aufbau eines Pthreads Thread-Unterprogramms,


wie es von pthread_create(...) erwartet wird. Ein Thread-Unterprogramm stellt
den Programmcode dar, der von einem Thread zur Laufzeit ausgeführt wird. Endet
das Thread-Unterprogramm, so endet auch automatisch der Thread.

Das Thread-Unterprogramm erhält zu Beginn seiner Ausführung durch einen neu


erzeugten Thread zur Initialisierung von seinem Erzeuger den Parameter agument
übergeben (Zeile 1), welcher vom Datentyp eines unbestimmten Zeigers (void *) ist.
Dieser Parameter kann somit sowohl zur Übergabe eines Zeigers auf eine beliebige
Datenstruktur als auch zur Übergabe eines beliebigen Werts genutzt werden, dessen
Datentyp dieselbe Datenwortbreite wie ein Zeiger hat (z. B. int, unsigned int). Es
empfiehlt sich, zunächst eine Typkonvertierung des Parameters in den erwarteten Typ
vorzunehmen (Zeile 3), bevor auf den Parameter zugegiffen wird.

Das Thread-Unterprogramm kann beim Beenden einen Rückgabewert in Form


eines unbestimmten Zeigers (void *) mittels pthread_exit(...) an seinen Erzeuger
zurückliefern (Zeile 12). Hierzu muss es für den Rückgabewert Speicher außerhalb des
Thread reservieren (zB mit malloc; Zeile 8), der dann vom Erzeuger wieder freigegeben
werden muss. Alternativ kann es natürlich auch einen beliebigen Wert zurückgegeben,
dessen Datentyp dieselbe Datenwortbreite wie ein Zeiger hat.

66
3.7 Die POSIX Threads (Pthreads) Bibliothek für Threads in C

3.7.6 Beispiel der Verwendung von Pthreads zur Programmierung von


Threads in C
Das Beispielprogramm aus Listing 3.18 erzeugt NTHREADS Threads, welche jeweils den
Text „Hello, world. . . “ zusammen mit der Nummer des Thread auf dem Bildschirm
ausgeben. Bei der Erzeugung der Threads wird ihnen ihre jeweilige Nummer über
das optionale Argument argment übergeben und ihre Referenz in dem Array thread
gespeichert.
Die Bildschirmausgabe des Beispielprogramms ist in Listing 3.18 wiedergegeben.
Die Reihenfolge der Ausgaben ist abhängig von der jeweiligen Implementierung von
Pthreads und dem Scheduling der Threads und im Beispiel lediglich zufällig in der
Reihenfolge der Erzeugung.

67
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)

9 #include <stdio.h> /* printf() */


10 #include <stdlib.h> /* EXIT_SUCCESS */
11 #include <pthread.h> /* POSIX Threads Library */
12
13 /* Global Constants */
14 #define NTHREADS 5 /**< number of threads to create */
15
16 /**
17 * Thread Subroutine: Hello World
18 * Prints "Hello, world..." and the thread’s id to STDOUT.
19 *
20 * @param argument the thread’s id.
21 * @return the thread’s status (here: the return code). EXIT_SUCCESS if the
22 * thread terminated correctly, else EXIT_FAILURE.
23 */
24 void * thread_helloworld_subroutine(void * argument) {
25 int tid = (int)argument; /* cast ptr to integer */
26 printf("Hello, world from Pthreads working for thread number %d!\n", tid);
27
28 pthread_exit((void *)EXIT_SUCCESS);
29 /* or */
30 return (void *)EXIT_SUCCESS;
31 }
32
33 /**
34 * C Main Function
35 *
36 * @param argc the argument count.
37 * @param argv the argument vector.
38 * @return the return code. EXIT_SUCCESS if the program terminated correctly,
39 * else EXIT_FAILURE.
40 */
41 int main(int argc, char * argv[]) {
42 pthread_t thread[NTHREADS]; /**< thread handles */
43 int i;
44
45 /* create threads */
46 for (i = 1; i <= NTHREADS; ++i) {
47 if (pthread_create(&thread[i], NULL, thread_helloworld_subroutine, (void *)i))
48 printf("Error creating thread %d!\n", i);
49 }
50
51 /* join threads */
52 for (i = 1; i <= NTHREADS; ++i) {
53 int status; /* temporary variable to store the terminated thread’s status */
54
55 if (pthread_join(thread[i], (void **)&status))
56 printf("Error joining thread %d!\n", i);
57
58 if (status != EXIT_SUCCESS)
59 printf("Thread %d failed!\n", i);
60 else
61 printf("Thread %d succeeded!\n", i);
62 }
63
64 return EXIT_SUCCESS;
65 }

Listing 3.18: Hello World mit Pthreads in C

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)

3.8 Verwendung der POSIX Threads Bibliothek zur parallelen


Ausführung eines CES Programms
Anhand des reduzierten und aufbereiteten Ausschnitts Listing 3.20 des Quelltexts
des C Hauptprogramms main der parallelen Laufzeitumgebung für CES wird die
Verwendung von Pthreads zur parallelen Ausführung eines CES Programms mit Hilfe
des Cop/Thief Work Stealing aus Unterabschnitt 3.5.2 demonstriert. Die parallele
Ausführung läuft nach folgendem Schema ab:

1. Initialisierung der globalen threadspezifischen Datenstruktur thread_data (je-


weils ein Frame Stack fs, Current Stack cs und Frame Stack Pointer fsp pro
Thread). (Zeilen 5–7)

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)

3. Erzeugung jeweils eines Threads pro physikalischem Prozessorkern (CES_THREADS)


mit Hilfe von pthread_create (siehe Unterabschnitt 3.7.1).

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.

5. Zusammenführung der erzeugten Threads mit Hilfe von pthread_join (siehe


Unterabschnitt 3.7.3), um das Ende der Ausführung des CES Programms ab-
zuwarten. Dadurch gibt das C Hauptprogramm für die Zeit den Prozessor ab.
(Zeilen 23–28)

6. Gegebenenfalls Ausgabe von während der Laufzeit gesammelten statistischen


Daten. (Zeilen 30–31)

Die notwendige Synchronisation der Threads der parallelen Laufzeitumgebung wird


im folgenden Abschnitt 3.9 erläutert und ihre Implementierung mit Hilfe von Synchro-
nisierungsprimitiven von Pthreads bzw. atomaren Operationen in Abschnitt 3.11 und
Abschnitt 3.12 erklärt.

70
3.8 Verwendung von Pthreads zur parallelen Ausführung eines CES Programms

1 int main(int argc, char *argv[]) {


2 pthread_t tids[CES_THREADS]; /* thread IDs */
3 int i = 0;
4
5 /* initialize thread data */
6 for (i = 0; i < CES_THREADS; ++i)
7 init_thread_data(&thread_data[i]);
8
9 /* initialize thread 0 frame stack with initial cops and other thread’s stacks with initial'
thieves */
10 for (i = 1; i < CES_THREADS; ++i) {
11 ...
12 }
13
14 /* initialize thread 0 frame stack with initial task frame program(argc, argv;;) */
15 ...
16
17 /* create task execution threads and execute CES application */
18 for (i = 0; i < CES_THREADS; ++i) {
19 if (pthread_create(&tids[i], NULL, exec_top_of_fs, (void *)i) != 0)
20 error("Error in pthread_create: thread %d\n", i);
21 }
22
23 /* join task execution threads */
24 for (i = 0; i < CES_THREADS; ++i) {
25 int status;
26 if (pthread_join(tids[i], (void *)&status) != 0)
27 error("Error in pthread_join: thread %d\n", i);
28 }
29
30 /* print statistics if compiled with CES_STATISTICS defined */
31 ...
32
33 return EXIT_SUCCESS; /* defined in stdlib.h */
34 }

Listing 3.20: Verwendung von Pthreads zur parallelen Ausführung eines CES Pro-
gramms

71
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)

3.9 Notwendigkeit der Synchronisierung des Cop/Thief


Work-Stealing unter Verwendung von Threads

Im Gegensatz zu der im Abschnitt 3.6 untersuchten pseudoparallelen Abarbeitung


eines CES Programms mittels mehrerer Frame Stacks und nur einem Prozessor bei der
round-robin Laufzeitumgebung für CES, ist es bei der parallelen Abarbeitung mittels
mehrerer Threads bei der zu entwickelnden parallelen Laufzeitumgebung für CES
notwendig, die Kommunikation zwischen den einzelnen Threads zu synchronisieren.
Bei der parallelen Laufzeitumgebung für CES besitzt jeder Thread einen eigenen
Frame Stack, dessen Task Frames er mittels des im Unterabschnitt 3.5.2 beschriebenen
Cop/Thief Work-Stealing abarbeitet. Dabei arbeitet jeder Thread in einer Schleife
sequentiell jeweils den obersten Task Frame seines eigenen Frame Stack ab, bis dieser
leer ist. Jedoch befinden sich neben den Task Frames auch noch die beiden speziellen
Synchronization Frames, die COP Frames und die T HIEF Frames, auf den Frame
Stacks der Threads.
Bei der Abarbeitung eines COP Frame muss der Cop seinem zugehörigen Thief,
dessen T HIEF Frame sich auf einem fremden Frame Stack befindet, signalisieren, dass
der gestohlene Task Frame vollständig abgearbeitet wurde, damit der Thief die weitere
sequentielle Abarbeitung seines Frame Stack über seinen T HIEF Frame hinaus für
seinen Thread freigibt. Bei der Abarbeitung eines T HIEF Frame versucht der Thief
einen als parallel abarbeitbar markierten Task Frame von einem fremden Frame Stack
zu stehlen. Hierbei scannt er, wie im Unterabschnitt 3.5.2 beschrieben, den fremden
Frame Stack eines anderen Thread. Findet er einen stehlbaren Task Frame, so kopiert
er ihn auf seinen eigenen Frame Stack und ersetzt den ursprünglichen Task Frame durch
einen neuen T HIEF Frame. In beiden Fällen findet eine Kommunikation zwischen
mehreren Threads bzw. ein Zugriff auf Daten eines fremden Thread statt, die in jedem
Fall synchronisiert werden muss.

3.9.1 Thief: Synchronisation des Scannens eines fremden Frame Stack


eines anderen Thread

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.

3.9.2 Thief: Synchronisation des Stehlens eines als parallel abarbeitbar


markierten Task Frame von einem fremden Frame Stack eines
anderen Thread

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.

3.9.3 Cop: Synchronisation der Signalisierung der vollständigen


Abarbeitung des gestohlenen Task Frame an den zugehörigen Thief
auf einem fremden Frame Stack eines anderen Thread

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.

3.10 Primitiven zur Synchronisation


3.10.1 Synchronisationsprimitiven der POSIX Threads Bibliothek

Pthreads stellt vier unterschiedliche Synchronisationsmechanismen zur Verfügung:


Mutexes, Condition Variables, Read-Write Locks und Joins. [IBM, Abschnitt “Synchro-
nization Overview”] Die ersten drei Mechanismen sind universelle Synchronisationspri-
mitiven zwischen Threads, der letzte Mechanismus, die Joins, ist jedoch auf das Warten
des Endens eines anderen Threads beschränkt und bereits im Unterabschnitt 3.7.3 im
Detail erläutert.

73
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)

3.10.1.1 Mutexes

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER


int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t * attributes);
int pthread_mutex_destroy(pthread_mutex_t * mutex);
int pthread_mutex_lock(pthread_mutex_t * mutex);
int pthread_mutex_trylock(pthread_mutex_t * mutex);
int pthread_mutex_unlock(pthread_mutex_t * mutex);

Listing 3.21: Die Pthreads API für 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.

3.10.1.2 Condition Variables

pthread_cond_t cvar = PTHREAD_COND_INITIALIZER;


int pthread_cond_init(pthread_cond_t * cvar, const pthread_condattr_t * attributes);
int pthread_cond_destroy(pthread_cond_t * cvar);

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.23: Warten auf das Eintreten eines Ereignisses

int pthread_cond_wait(pthread_cond_t * cvar, pthread_mutex_t * mutex);


int pthread_cond_timedwait(pthread_cond_t * cvar, pthread_mutex_t * mutex, const struct '
timespec * timeout);
int pthread_cond_signal(pthread_cond_t * cvar);
int pthread_cond_broadcast(pthread_cond_t * cvar);

Listing 3.22: Die Pthreads API für Condition Variables

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)

Mittels pthread_cond_signal(...) oder pthread_cond_broadcast(...) signali-


siert ein Thread den auf die Condition Variable cvar wartenden Threads, dass das Ereig-
nis eingetreten ist, auf das sie warten. Bei der Verwendung von pthread_cond_signal(...)
wird das Ereignis mindestens3 einem wartenden Thread signalisiert. Bei der Verwen-
dung von pthread_cond_broadcast(...) wird das Ereignis hingegen allen wartenden
Threads signalisiert.
Im Anhang B im Abschnitt B.1 wird die Verwendung von Condition Variables an
einem einfachen Producer/Consumer Beispiel mit einem First In – First Out (FIFO)
Ringpuffer zum Datenaustausch demonstriert. Das Beispielprogramm ist allerdings nicht
vollständig Threadsicher. Beim Producer ist nicht sichergestellt, dass der Schreibvorgang
zum Einfügen seines produzierten Elements in den Ringpuffer vollständig abgeschlossen
und für alle anderen Prozessoren sichtbar ist, bevor die Änderung des des Producer-
Index erfolgt. Beim Consumer ist entsprechend nicht sichergestellt, dass der Lesevorgang
zum Entnehmen eines Elements aus dem Ringpuffer abgeschlossen und für alle anderen
Prozessoren sichtbar ist, bevor die Änderung des Consumer-Index erfolgt. Dadurch
kann einerseits bei einem vormals leeren Ringpuffer die Situation entstehen, dass
ein Consumer ein unvollständig eingefügtes Element aus dem Ringpuffer entnimmt,
da er den bereits geänderten Producer-Index vor dem Ende des Schreibvorgangs
des Einfügens sieht. Andererseits kann bei vormals vollem Ringpuffer die Situation
entstehen, dass ein Producer ein unvollständig entnommenes Element überschreibt,
da er den bereits geänderten Consumer-Index vor dem Ende des Lesevorgangs des
Entnehmens sieht. Die zur Herstellung der vollständigen Threadsicherheit notwendigen
Memory Barriers/Fences werden im Unterunterabschnitt 3.10.3.1 erläutert und sind
im Quelltext des Beispielprogramms in den Kommentaren erwähnt.

3.10.1.3 Read-Write Locks (RW Locks)

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;


int pthread_rwlock_init(pthread_rlock_t * rwlock, const pthread_rwlockattr_t * attributes);
int pthread_rwlock_destroy(pthread_rwlock_t * rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t * rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t * rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t * rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t * rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t * rwlock);

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.

3.10.2 Verwendung atomarer Operationen zur Entwicklung neuer


performanter Synchronisationsprimitiven oder Datenstrukturen

Die im folgenden Unterabschnitt 3.10.3 beschriebenen vom Prozessor bereitgestell-


ten atomaren Operationen können zur Entwicklung einfacher sowie auch komplexer
Synchronisationsprimitiven und Datenstrukturen benutzt werden (siehe Listing 3.26).
Letztenendes sind auch die von der POSIX Threads Bibliothek bereitgestellten Syn-
chronisierungsprimitiven aus Unterabschnitt 3.10.1 mit Hilfe der atomaren Operationen
realisiert. Meist können mit Hilfe der atomaren Operationen sehr viel performantere
synchronisierte Datenstrukturen implementiert werden als mit den Synchronisierungs-
primitiven des Betriebssystems oder der Thread-Bibliothek. [Jon06; DMS; Her; SH97;
HLMS05] Atomare Operationen haben mit Java 1.5 daher auch in Java Einzug gehalten:
java.util.concurrent.atomic.

77
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)

Schlagwörter, die häufig im Zusammenhang mit mittels atomarer Operationen


synchronisierten Synchronisierungsprimitiven und Datenstrukturen auftreten, sind:
lock-free, wait-free und non-blocking.

3.10.3 Atomare Operationen als Synchronisationsprimitiven

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.

3.10.3.1 Einfache atomare Lese- und Schreiboperationen sowie Memory


Barriers/Fences

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.

3.10.3.2 Komplexe atomare Speicheroperationen

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

1 typedef int lock_t;


2 enum {unlocked = 0, locked = 1};
3
4 void lock_init(lock_t * lock) {
5 *lock = unlocked;
6 }
7
8 void lock_lock(lock_t * lock) {
9 lock_t oldLockState;
10 do {
11 /* wait until lock is unlocked */
12 while (*lock == locked);
13 /* try to get lock */
14 oldLockState = Swap(lock, locked);
15 } while (oldLockState == locked);
16 }
17
18 void lock_unlock(lock_t * lock) {
19 *lock = unlocked;
20 }

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.25: Swap

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)

Compare and Swap (CAS)


Die atomare Operation Compare and Swap (CAS) erlaubt es, ein Datenwort im Speicher
atomar mit einem Wert zu vergleichen und gegen einen anderen Wert auszutauschen,
sofern das Datenwort den angegebenen Wert besaß. Listing 3.27 und Listing 3.28 zeigen
die beiden möglichen formalen Definitionen von Compare and Swap. Die IA-32 stellt
die Operation mit datenwortabhängigem Rückgabewert in Form der Maschinenbefehle
cmpxchg (8-Bit, 16-Bit und 32-Bit), cmpxchg8b (64-Bit) und cmpxchg16b (128-Bit)
bereit, die zur Erlangung der Atomarität explizit mit dem lock Prefix verwendet werden
müssen. Die POWER Architektur stellt ebenfalls entsprechende Maschinenbefehle für
32-Bit im 32-Bit Modus und 64-Bit im 64-Bit Modus bereit.
1 boolean CompareAndSwap(<type> * pointer, <type> oldValue, <type> newValue) {
2 if (*pointer = oldValue) {
3 *pointer ← newValue;
4 return true;
5 }
6 return false;
7 }

Listing 3.27: Compare and Swap mit boolischem Rückgabewert

1 <type> CompareAndSwap(<type> * pointer, <type> expectedValue, <type> newValue) {


2 <type> oldValue ← *pointer;
3 if (*pointer = expectedValue) {
4 *pointer ← newValue;
5 }
6 return oldValue;
7 }

Listing 3.28: Compare and Swap mit datentypabhängigem Rückgabewert

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)

int __compare_and_swaplp(volatile long * addr, long * old_val_addr, long new_val);

Fetch and Add/Sub


Die atomare Operation Fetch and Add erlaubt es, ein Datenwort im Speicher ato-
mar zunächst zu lesen und ihm anschließend einen beliebigen Wert hinzuzuaddieren.
Listing 3.29 zeigt die formale Definitionen von Fetch and Add. Die IA-32 stellt die
Operation in Form des Maschinenbefehls xadd (Exchange and Add) bereit, der zur
Erlangung der Atomarität explizit mit dem lock Prefix verwendet werden muss.
1 <type> FetchAndAdd(<type> * pointer, <type> increment) {
2 <type> oldValue ← *pointer;
3 *pointer ← *pointer + increment;
4 return oldValue;
5 }

Listing 3.29: Fetch and Add

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.

3.11 Synchronisation der parallelen Laufzeitumgebung für


CES mittels der Synchronisierungsprimitiven von POSIX
Threads (Pthreads)
In einem ersten Anlauf soll die parallele Laufzeitumgebung für CES ausschließlich
mit Hilfe der Synchronsisationsprimitiven von Pthreads aus Unterabschnitt 3.10.1
synchronisiert werden. Hierzu müssen die im Abschnitt 3.9 genannten Situationen
derart abgesichert werden, dass es zu keinen unerlaubten Zuständen kommt. In einem
im folgenden Abschnitt 3.12 beschriebenen zweiten Anlauf soll die Synchronisation
der parallelen Laufzeitumgebung für CES mit Hilfe der atomaren Operationen aus
Unterabschnitt 3.10.3 optimiert werden.
Diese erste mittels der Synchronisierungsprimitiven von Pthreads synchronisierte Ver-
sion der parallelen Laufzeitumgebung für CES dient als weiterer Zwischenschritt nach
der round-robin Laufzeitumgebung für CES. Durch den Einsatz der von Pthreads be-
reitgestellten Mittel soll in relativ kurzer Zeit eine lauffähige rudimentär synchronisierte

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.

3.11.1 Änderungen gegenüber der round-robin Laufzeitumgebung für CES

Neben den bereits im Abschnitt 3.8 beschriebenen Erweiterung um Threads zur


parallelen Ausführung des CES Programms wurden das Unterprogramm der Abar-
beitungsschleife exec_top_of_fs sowie das spezielle Cop und Thief Unterprogramm
angepasst und mussten synchronisiert werden. Der Aufbau der Task Frames, Storage
Frames und COP Frames blieb gegenüber dem der round-robin Laufzeitumgebung für
CES gleich. Lediglich der Aufbau der T HIEF Frames wurde verändert.

3.11.1.1 Aufbau der Struktur THREAD_DATA

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)

1 typedef struct THREAD_DATA {


2 struct {
3 TASK_FRAME frame_stack[FS_SIZE]; /**< frame stack */
4 TASK_FRAME current_stack[FS_SIZE]; /**< current frame stack */
5 } data;
6 TASK_FRAME * fsp; /**< frame stack pointer */
7 TASK_FRAME * csp; /**< current frame stack pointer */
8 /* runtime information */
9 pthread_mutex_t thief_mutex; /**< mutex to lock access to one thief at a time */
10 TASK_FRAME * thief_fsp; /**< frame stack pointer for thieves */
11 TASK_FRAME * thief_fsp_top;
12 pthread_mutex_t thief_fsp_top_mutex;
13 /* debugging information */
14 unsigned int executes; /**< executed task counter */
15 } THREAD_DATA;

Listing 3.30: Definition der Struktur THREAD_DATA (Parallele 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)

1 void * exec_top_of_fs(void * tid) {


2 int thread = (int)tid;
3 THREAD_DATA * td = &thread_data[thread];
4
5 CES_TRACE(("Thread %d: Beginning to work ...\n", thread));
6
7 while (td->fsp >= FS_BOTTOM(td)) {
8 /* synchronize with thief */
9 pthread_mutex_lock(&td->thief_fsp_top_mutex);
10 td->thief_fsp_top = td->fsp;
11 pthread_mutex_unlock(&td->thief_fsp_top_mutex);
12
13 /* execute frame stack’s topmost task */
14 if (!IS_STORAGE_FRAME(td->fsp)) {
15 td->fsp->fnptr_task(&(td->fsp), td->csp); /* execute task */
16 CES_GATHER_STATISTIC(td->executes++);
17 } else {
18 td->fsp -= 1; /* skip storages: set frame stack pointer to next task */
19 }
20 }
21
22 CES_TRACE(("Thread %d: My job is done ...\n", thread));
23 pthread_exit(EXIT_SUCCESS);
24 }

Listing 3.31: Definition des Unterprogramms der Frame Stack Abarbeitungsschleife


(Parallele Laufzeitumgebung für CES; Pthreads)

3.11.1.3 Implementierung des Cop Unterprogramms

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.

3.11.1.4 Implementierung des Thief Unterprogramms

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 }

Listing 3.32: Definition des Cop Unterprogramms (Parallele Laufzeitumgebung für


CES)

1 typedef struct THIEF_TASK_FRAME {


2 void (*fnptr_thief_task)(TASK_FRAME ** /* fsp */, TASK_FRAME * /* csp */); /**< reference '
to the task’s C procedure */
3 } THIEF_TASK_FRAME;

Listing 3.33: Definition der Struktur THIEF_TASK_FRAME (Parallele Laufzeitum-


gebung für CES)

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.

3.11.2 Thief: Synchronisation des Scannens eines fremden Frame Stack


eines anderen Thread

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.

3.11.3 Thief: Synchronisation des Stehlens eines als parallel abarbeitbar


markierten Task Frame von einem fremden Frame Stack eines
anderen Thread

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.

3.11.4 Cop: Synchronisation der Signalisierung der vollständigen


Abarbeitung des gestohlenen Task Frame an den zugehörigen
Thief auf einem fremden Frame Stack eines anderen Thread

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).

3.12 Synchronisation der parallelen Laufzeitumgebung für


CES mittels der atomaren Operationen (CAS64)
In diesem zweiten Anlauf soll die im vorhergehenden Abschnitt 3.11 mittels der Syn-
chronisierungsprimitiven von Pthreads synchronisierte parallele Laufzeitumgebung
für CES mit Hilfe der atomaren Operationen aus Unterabschnitt 3.10.3 optimiert
werden. Hauptziel der Optimierung ist eine asymmetrische Synchronisation zwischen
der Frame Stack Abarbeitungsschleife und den Thieves, wobei der Synchronisations-
aufwand für die Abarbeitungsschleife minimiert werden soll, auch wenn sich dabei
der Synchronisationsaufwand für die Thieves erhöht. Dies minimiert den gesamten
durch die Parallelisierung entstehenden Overhead, da die meisten Task Frames von der
Abarbeitungsschleife sequentiell abgearbeitet werden, ohne gestohlen zu werden (siehe
Unterabschnitt 3.5.1). Die Minimierung des Aufwandes der Abarbeitungsschleife wird
im Unterabschnitt 3.12.3 beschrieben. Die Steigerung der Performance der mit den
atomaren Operationen optimierten Synchronisation wird in Kapitel 4 nachgewiesen.
Es muss daher versucht werden, die teure Synchronisation mittels der Synchronisie-
rungsprimitiven von Pthreads aus der Frame Stack Abarbeitungsschleife exec_top_of_fs
in den Thief zu verlagern und wenn möglich mit Hilfe der atomaren Operationen zu
beschleunigen.

89
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)

1 typedef struct THREAD_DATA {


2 struct {
3 TASK_FRAME frame_stack[FS_SIZE]; /**< frame stack */
4 TASK_FRAME current_stack[FS_SIZE]; /**< current frame stack */
5 } data;
6 TASK_FRAME * fsp; /**< frame stack pointer */
7 TASK_FRAME * csp; /**< current frame stack pointer */
8 /* runtime information */
9 pthread_mutex_t thief_mutex; /**< mutex to lock access to one thief at a time */
10 uint32_t parallel_counter; /**< ABA counter for parallel flagged task frames; note: must be'
>= 2 */
11 /* debugging information */
12 #ifdef CES_STATISTICS
13 struct {
14 unsigned int task_execute_count; /**< executed task counter */
15 unsigned int task_didsteal_count; /**< stealed victim task counter */
16 unsigned int task_gotstolen_count; /**< own stealed task counter */
17 } statistics;
18 #endif /* CES_STATISTICS */
19 } THREAD_DATA;

Listing 3.34: Definition der Struktur THREAD_DATA (Parallele Laufzeitumgebung


für CES; CAS)

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.

3.12.1 Änderungen gegenüber der mittels der Synchronisierungsprimitiven


von Pthreads synchronisierten parallelen Laufzeitumgebung für
CES

Neben den bereits im Unterabschnitt 3.11.1 beschriebenen Erweiterungen gegenüber


der round-robin Laufzeitumgebung für CES wurden einige weitere Änderungen an
der parallelen Laufzeitumgebung für CES vorgenommen. So wurden das Unterpro-
gramm der Abarbeitungsschleife exec_top_of_fs sowie das spezielle Cop und Thief
Unterprogramm überarbeitet und mit Hilfe der atomaren Operationen synchronisiert.
Der Aufbau der Task Frames, Storage Frames und COP Frames wurde noch einmal
geringfügig verändert. Lediglich der Aufbau der T HIEF Frames blieb gleich.

3.12.1.1 Aufbau der Struktur THREAD_DATA

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

Abbildung 3.8: Aufbau der Struktur TASK_FRAME (Parallele Laufzeitumgebung für


CES; CAS)

1 typedef struct TASK_FRAME {


2 volatile void (*fnptr_task)(struct TASK_FRAME ** /* fsp */, struct TASK_FRAME * /* csp */);
3 volatile int parallel;
4 void *parameter[ARG_SIZE];
5 /* debugging information */
6 char *name;
7 } TASK_FRAME;

Listing 3.35: Definition der Struktur TASK_FRAME (Parallele Laufzeitumgebung für


CES; CAS)

Stack sicherstellt, wurde behalten (Zeile 9). Alle weiteren Synchronisationsdatenstruk-


turen wurden allerdings wieder entfernt. An deren Stelle ist ein threadspezifischer
Zähler parallel_counter hinzugekommen (Zeile 10), der zur Lösung des sogenannten
„ABA“-Problems benötigt wird, welches im Unterabschnitt 3.12.3 näher erläutert wird.
Darüber hinaus sind eine Reihe an Variablen zum Sammeln von statistischen Daten
hinzugekommen (Zeilen 12–18).

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;

Listing 3.36: Definition der Struktur STORAGE_FRAME (Parallele Laufzeitumge-


bung für CES; CAS)

1 typedef struct COP_TASK_FRAME {


2 void (*fnptr_cop_task)(TASK_FRAME ** /* fsp */, TASK_FRAME * /* csp */); /**< reference to '
the task’s C procedure */
3 int reserved_parallel; /**< padding for parallel flag in TASK_FRAME */
4 /* parameters */
5 struct THIEF_TASK_FRAME * thief; /**< reference to the thief to kill */
6 } COP_TASK_FRAME;

Listing 3.37: Definition der Struktur COP_TASK_FRAME (Parallele Laufzeitumge-


bung für CES; CAS)

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.

3.12.1.3 Implementierung des Thread-Unterprogramms der Frame Stack


Abarbeitungsschleife

Das Thread-Unterprogramm der Frame Stack Abarbeitungsschleife exec_top_of_fs


wurde ebenfalls nur leicht verändert. Die Synchronisation mit den Thieves wurde auf
die Verwendung der atomaren Operationen umgestellt.
Listing 3.38 zeigt das mittels der atomaren Operation MFENCE (siehe Unterunter-
abschnitt 3.10.3.1) synchronisierte Thread-Unterprogramm der Frame Stack Abar-
beitungsschleife. Die Synchronisation mit den Thieves wird in Unterabschnitt 3.12.3
erläutert.

92
3.12 Synchronisation der parallelen Laufzeitumgebung für CES (CAS64)

158 void * exec_top_of_fs(void * tid) {


159 int thread = (int)tid;
160 THREAD_DATA * td = &thread_data[thread];
161
162 CES_TRACE(("Thread %d: Beginning to work ...\n", thread));
163
164 while (td->fsp >= FS_BOTTOM(td)) {
165 /* synchronize with thief */
166 if (td->fsp->parallel) {
167 td->fsp->parallel = 0;
168 MFENCE;
169 }
170
171 /* execute frame stack’s topmost task */
172 if (!IS_STORAGE_FRAME(td->fsp)) {
173 td->fsp->fnptr_task(&(td->fsp), td->csp); /* execute task */
174 CES_GATHER_STATISTIC(td->statistics.task_execute_count++);
175 } else {
176 td->fsp -= 1; /* skip storages: set frame stack pointer to next task */
177 }
178 }
179
180 CES_TRACE(("Thread %d: My job is done ...\n", thread));
181 pthread_exit(EXIT_SUCCESS);
182 }

Listing 3.38: Definition des Unterprogramms der Task Frame Abarbeitungsschleife


(Parallele Laufzeitumgebung für CES; CAS) – run_time.c

3.12.1.4 Implementierung des Thief Unterprogramms

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.

3.12.2 Thief: Synchronisation des Scannens eines fremden Frame Stack


eines anderen Thread

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)

Ausführng der Frame Stack Abarbeitungsschleife als nicht zeitkritisch zu bewerten.


Die in Unterabschnitt 3.11.2 vorgestellte Lösung mit Hilfe eines Mutex hat keine
Performanceauswirkungen auf die Frame Stack Abarbeitungsschleife exec_top_of_fs,
da ausschließlich eine Synchronisation unter den Thieves erfolgt. Daher wurde die
Synchronisation zwischen den Thieves mittels des Mutex thief_mutex beibehalten.
Wie auch bei der Implementierung mit Hilfe der Synchronisierungsprimitiven von
Ptherads kann es vorkommen, dass der Thief über den obersten Task Frame hinaus
den fremden Frame Stack scannt, wenn es zu einer Änderung des Frame Stack Pointers
fsp nach der Überprüfung durch den Thief kommt (Zeile 34). Es ist auch hier wieder
im Rahmen der Synchronisation des Stehlens sicherzustellen, dass dies erkannt und
entsprechend behandelt wird.

3.12.3 Thief: Synchronisation des Stehlens eines als parallel abarbeitbar


markierten Task Frame von einem fremden Frame Stack eines
anderen Thread

Im Gegensatz zu der Synchronisation mittels der Synchronisationsprimitiven von


Pthreads blockiert der Thief nicht mehr die Frame Stack Abarbeitungsschleife exec_top_of_fs
seines Ziels während des Stehlens. Stattdessen tauscht der Thief den als parallel ab-
arbeitbar markierten Task Frame mit Hilfe der atomaren Compare and Swap (CAS)
Operation während der Ausführung der Abarbeitungschleife des Ziels aus. Die Abar-
beitungsschleife seines Ziels kann daher immer ohne Unterbrechung ihren Frame Stack
weiter sequentiell abarbeiten. Auch greift der Thief nun direkt auf den Frame Stack Poin-
ter fsp seines Ziels zu, was den Synchronisationsaufwand für die Abarbeitungsschleife
seines Ziels weiter reduziert.
Die im Unterunterabschnitt 3.10.3.2 beschriebene atomare Compare and Swap (CAS)
Operation erlaubt das Austauschen eines Datenworts im Speicher nach vorherigem
Vergleich seines aktuellen Werts mit einem angegebenen Wert. Der Austausch findet nur
statt, wenn beide Werte gleich sind. Die aktuellen Prozessoren der IA-32 stellen die Ope-
ration im 32-Bit-Modus für 32-Bit und 64-Bit breite Datenwörter als Maschinenbefehl
bereit.
Zur Realisierung des atomaren Stehlens wurde der Aufbau der Task Frames angepasst.
Aus dem Funktionszeiger und dem Schalter parallel wurde eine insgesamt 64-Bit
breite Einheit gebildet. Entsprechend wurden auch die Storage Frames sowie Cop und
Thief Frames angeglichen.
Wichtigste Voraussetzung für das atomare Stehlen eines Task Frame mit Hilfe der
atomaren CAS Operation ist die Möglichkeit, die neugebildete Einheit aus dem Funkti-

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

abzuarbeiten“, „parallel abarbeitbar aber nicht stehlbar“, „parallel abarbeitbar und


stehlbar“.
Beim Erzeugen neuer als parallel abarbeitbar markierter Task Frames auf dem
Current Stack haben sie zunächst den Status „parallel abarbeitbar aber nicht stehlbar“.
Im Rahmen der Unterprogrammfinalisierung werden nun die gesammelten Task Frames
vom Current Stack zuoberst auf den Frame Stack kopiert (Listing 3.39, Zeile 300).
Anschließend wird der Status der als parallel ausführbar markierten Task Frames
von „parallel abarbeitbar aber nicht stehlbar“ auf „parallel abarbeitbar und stehlbar“
geändert (Zeilen 302–313), wobei eine vorangehende ausgeführte Store Fence sicherstellt
(Zeile 301), dass das Kopieren vorher vollständig abgeschlossen ist.
Neben der eben beschriebenen Verhinderung, einen unvollständigen in der Erzeugung
befindlichen Task Frame zu stehlen, muss auch unbedingt sichergestellt werden, dass
nicht der in der Abarbeitung befindliche Task Frame gestohlen wird. Dies würde zu
einer Korruption des Frame Stack und einer illegalen mehrfachen Ausführung eines
Unterprogrammaufrufs führen. Verhindert wird dies durch das atomare Löschen des
Schalters parallel durch die Frame Stack Abarbeitungsschleife vor Beginn der Abar-
beitung (Listing 3.38, Zeile 167). Durch eine anschließende Memory Fence (Zeile 168)

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.

Mit dem sogenannten „ABA“-Problem bezeichnet man das Problem, zu erkennen,


ob sich ein Datum zwischen zwei Zeitpunkten geändert hat, wenn Ausgangswert und
Endwert gleich sind. Der Name erklärt das Problem: ein Datum hat zum Zeitpunkt
t0 den Wert A. Zwischenzeitlich nimmt es zum Zeitpunkt t1 den Wert B an. Zum
Zeitpunkt t2 nimmt es wieder den Wert A an. Sieht ein Außenstehender nur die
Zeitpunkte t0 und t2 , kann er nicht entscheiden, ob sich das Datum zwischenzeitlich
geändert hat. [Mic04]

Die Codierung des Schalters parallel wurde daher folgendermaßen gewählt:






 =0 „sequentiell abzuarbeiten“

parallel

= 1 „parallel abarbeitbar aber nicht stehlbar“

≥ 2 „parallel abarbeitbar und stehlbar“

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

3.12.4 Cop: Synchronisation der Signalisierung der vollständigen


Abarbeitung des gestohlenen Task Frame an den zugehörigen
Thief auf einem fremden Frame Stack eines anderen Thread

Die Synchronisation der Signalisierung der vollständigen Abarbeitung des gestohlenen


Task Frame findet ausschließlich zwischen einem Cop und seinem zugehörigen Thief statt.
Da bereits bei der Implementierung der parallelen Laufzeitumgebung für CES mittels
der Synchronisierungsprimitiven von Pthreads keine gesonderten Vorkehrungen zur
Synchronisation der Signalisierung getroffen werden mussten, wurde keine Veränderung
vorgenommen.

3.13 Überprüfung der Korrektheit der parallelen


Laufzeitumgebung für CES
Die Korrektheit der parallelen Laufzeitumgebung für CES wurde mit Hilfe der im
folgenden Kapitel 4 untersuchten Anwendungen und einiger spezieller Testprogramme
überprüft.
Der vollständige Quelltext der Testprogramme 1 und 2 ist im Abschnitt B.6 abge-
druckt. An dieser Stelle soll kurz auf die Funktionsweise der beiden Testprogramme
eingegangen werden. Beide Testprogramme überprüfen, ob es bei der Ausführung als
parallel ausführbar markierter CES Unterprogrammaufrufe zu unerlaubten Situationen
durch das Stehlen kommt. Ein solcher CES Unterprogrammaufruf darf beim Stehlen
nicht verloren gehen oder mehrfach ausgeführt werden.
Das Testprogramm 1 (Listing B.10) führt eine definierbare Anzahl von parallel
ausführbaren CES Unterprogrammaufrufen durch und zählt deren jeweilige Ausfüh-
rungshäufigkeit mit Hilfe der atomaren Operation atomic_inc32(...). Anschließend
überprüft das Testprogramm, ob jeder parallele CES Unterprogrammaufruf exakt ein-
mal durchgeführt wurde. Es wäre ein Fehler, wenn einer der CES Unterprogrammaufrufe
keinmal oder mehrfach ausgeführt worden wäre.
Das Testprogramm 2 (Listing B.11) führt einen erweiterten Test der parallelen
CES Unterprogrammausführung durch. Es führt in einer Endlosschleife eine definier-
bare Anzahl von parallel ausführbaren CES Unterprogrammaufrufen durch, wobei
es für jeden durchgeführten Aufruf einen Zähler atomar inkrementiert. Das CES Un-
terprogramm dekrementiert diesen Zähler entsprechend wieder atomar. Nach jedem
Schleifendurchlauf überprüft die Endlosschleife, dass alle Zähler immer noch den Wert
null aufweisen. Andernfalls wäre ein CES Unterprogrammaufruf nicht oder mehrfach
ausgeführt worden, was einen Fehler darstellt. Die Endlosschleife des Testprogramms 2

99
3 Parallele Laufzeitumgebung für CES (Parallel CES Runtime)

bricht bei einem solchen Fehler mit einer Fehlermeldung ab.


Auf dem Testserver konnte das Testprogramm 2 erfolgreich über mehrere Tage
hinweg ohne Fehler ausgeführt werden, bis es jeweils manuell beendet wurde. Hierbei
wurde das Testprogramm 2 jeweils mit der parallelen Laufzeitumgebung für CES unter
der Verwendung von acht Threads und mit 64 bzw. 128 durchzuführenden parallelen
CES Unterprogrammaufrufen übersetzt und mittels nohup im Hintergrund gestartet:
make CES_SRCS="test_parallel_runtime_task_execution2.ces" CFLAGS="-D CES_THREADS=8 -D NTASKS'
=128"
nohup ./ces_test_parallel_runtime_task_execution2.exe &

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

Die reinen Ergebnisse der Laufzeitmessungen der beiden Laufzeitumgebungen für


CES sind für sich genommen wenig aussagekräftig, wenn man die Leistungsfähigkeit
der parallelen Laufzeitumgebung für CES beurteilen möchte oder CES mit anderen
Systemen vergleichen möchte. Man muss die Ergebnisse zuerst zueinander in Bezug
setzen, um verwertbare Daten zu erhalten. Hierzu ist es zunächst notwendig, einige
Grundlagen der Bewertung paralleler Softwaresysteme zu kennen und zu verstehen.

4.1.1 Amdahlsches Gesetz

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

Beschleunigung unter der Anwendung des Amdahlschen Gesetzes


2500

2000
Beschleunigung

1500

1000
800x

500 400x
200x
100x
50x 33x 25x
0
0% 1% 2% 3% 4%
Serieller Anteil

Abbildung 4.1: Maximale Beschleunigung unter Anwendung des Amdahlschen Gesetzes


aus Gleichung 4.1

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.

4.1.2 Gustafsonsches Gesetz

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

Abbildung 4.2: Parallele Skalierbarkeit [Sut08b]

Abbildung 4.3: Illustration des Amdahlschen Gesetzes für s = p [Sut08a]

104
4.1 Grundlagen

Abbildung 4.4: Illustration des Gustafsonschen Gesetzes [Sut08a]

105
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES

Abbildung 4.4 illustriert die Anwendung des Gustafsonschen Gesetzes anhand eines
einfachen Beispiels.

4.1.3 Beziehung zwischen dem Amdahlschen Gesetz und dem


Gustafsonschen Gesetz

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.

4.2 Mess- und Auswertungsverfahren


Im Folgenden werden das Messverfahren, das Auswertungsverfahren und die graphische
Darstellung der Messergebnisse kurz erläutert.

106
4.2 Mess- und Auswertungsverfahren

1 /* Initialisierung und Eingabe */


2 ...
3
4 /* Verarbeitung */
5 $stopwatch_start(;;stopwatch_marker marker);$
6 // CES Unterprogrammaufruf des Kernalgorithmus des CES~Programms
7 $stopwatch_stop(marker;;);$
8
9 /* Ausgabe und Finalisierung */
10 ...

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

Die relative Beschleunigung der parallelen Ausführung einer Anwendung im Vergleich


zu ihrer sequentiellen Ausführung berechnet sich entsprechend:

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

4.2.3 Graphische Darstellung der Messergebnisse

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.

Benchmark (CAS64 vs. Pthreads)


9
parallele Laufzeitumgebung für CES (CAS64)
parallele Laufzeitumgebung für CES (Pthreads)
8 maximale lineare Beschleunigung

7 theoretisches Maximum ohne Synchronisationsoverhead


theoretisches Maximum der Laufzeitumgebung
6 reales Ergebnis der Laufzeitumgebung

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: Beispiel der Auswertung der Messergebnisse eines Benchmark

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

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

Die mathematische Definition der Fibonacci Zahlenfolge lautet wie folgt:






 0 falls n = 0

f ib(n) = 1 falls n = 1 (4.5)


f ib(n − 1) + f ib(n − 2) sonst

Die rekursive Implementierung1 der Berechnung der Fibonacci Zahlenfolge in CES


entsprechend der Gleichung 4.5 ist in Listing 4.2 und Listing 4.3 wiedergegeben.
Eine weitaus effizientere Variante zur Berechnung der Fibonacci Zahlenfolge besteht
in der Potenzierung der quadratischen Matrix:
!n+1
0 1 f ib(n + 1)
!
f ib(n)
= (4.6)
1 1 f ib(n + 1) f ib(n + 2)

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

performant durch sukzessives Halbieren des Problems in O(log(n)) umgesetzt werden:



M k · M k für gerade n mit n = 2 · k
Mn = (4.7)
M k · M k · M für ungerade n mit n = 2 · k + 1

Fibonacci-Zahlen (CAS64 vs. Pthreads)


9
fibonacci(40) (CAS64)
fibonacci(40) (Pthreads)
8 maximum linear speedup

5
Speedup

0
1 2 3 4 5 6 7 8
Threads

Abbildung 4.6: Skalierung der rekursiven Implementierung der Berechnung der


Fibonacci-Zahlen in CES

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 }$

Listing 4.2: Fibonacci Zahlen in CES (CES Unterprogramm fibonacci) – fibonacci.


ces

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 }$

Listing 4.3: Fibonacci Zahlen in CES (CES Unterprogramm add_uint64) – fibonacci.


ces

113
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES

4.3.2 Aufsummierung der natürlichen Zahlen (Gaußscher


Summenalgorithmus)

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

Aufsummierung der natuerlichen Zahlen (CAS64 vs. Pthreads)


9
sumn(100000000 1) (CAS64)
sumn(100000000 1) (Pthreads)
8 maximum linear speedup

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

Aufsummierung der natuerlichen Zahlen (Variation der Granularitaet; CAS64)


9
sumn(100000000 1)
sumn(100000000 10)
8 sumn(100000000 50)
sumn(100000000 100)
sumn(100000000 500)
sumn(100000000 1000)
7 maximum linear speedup

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)

Aufsummierung der natuerlichen Zahlen (Variation der Granularitaet; Pthreads)


9
sumn(100000000 1)
sumn(100000000 10)
8 sumn(100000000 50)
sumn(100000000 100)
sumn(100000000 500)
sumn(100000000 1000)
7 maximum linear speedup

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

4.4 N -Damen Problem


Das N -Damen Problem ist ein sehr bekanntes mathematisches Problem. Es geht darum,
N -Damen auf einem N × N Schachbrett so zu positionieren, dass sich die Damen
untereinander nicht bedrohen. Das CES Programm bestimmt für ein gegebenes N die
Anzahl der Lösungen für das Problem.
Der CES Quelltext der Implementierung des N -Damen Problems ist in Listing 4.7,
Listing 4.8, Listing 4.9 und Listing 4.10 wiedergegeben. Die mit Hilfe des Programms
berechneten Lösungen für N von 1–15 wurden mit den diversen im Internet veröffent-
lichten Lösungen verifiziert.
Aufgrund des relativ hohen Arbeitsaufwands und der großen Menge an paralleler
Arbeit im Vergleich zu den vorangegangenen Beispielen ist zunächst eine sehr steile
und nahezu perfekte Skalierung anzunehmen. Doch aufgrund der Verwendung von
malloc(...) und free(...) bei jedem Unterprogrammaufruf ist nur eine reduzierte
Performance zu erwarten.

N-Damen Problem (CAS64 vs. Pthreads)


9
nqueens(15) (CAS64)
nqueens(15) (Pthreads)
8 maximum linear speedup

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 }$

Listing 4.7: N -Damen Problem in CES (CES Unterprogramm nqueens) – nqueens.ces

steigener Anzahl an Threads wesentlich abnehmende Skalierung der CAS64 Synchro-


nisation kann eventuell mit der häufigen Verwendung von malloc(...), free(...)
und memcpy(...) begründet werden, welche die Beschleunigung begrenzen. Durch die
höhere Performance der CAS64 Synchronisation wird diese Grenze bei acht Threads
sichtbar, wohingegen die Pthreads Synchronisation diese Grenze noch nicht erreicht
hat.

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 }$

Listing 4.8: N -Damen Problem in CES (CES Unterprogramm nqueens_recursive) –


nqueens.ces

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 }$

Listing 4.9: N -Damen Problem in CES (CES Unterprogramm sum_uint32) – nqueens.


ces

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 }

Listing 4.10: N -Damen Problem in CES (C Unterprogramm test_board) – nqueens.


ces

4.5 Sortieralgorithmen

4.5.1 Mergesort

Die gegenüber der im Abschnitt 2.2 präsentierten verbesserte Implementierung von


Mergesort in CES ist in Listing 4.11, Listing 4.12 und Listing 4.13 wiedergegeben.
Im Gegensatz zu der bereits präsentierten Implementierung wird bei dieser nur ein
einziges temporäres Hilfsarray verwendet, welches zu Beginn mit dem unsortierten
Inhalt des zu sortierenden Array initialisiert wird (siehe Listing 4.11). Anschließend
werden die zu sortierenden Arrayelemente rekursiv zwischen den beiden Arrays hin-
und hersortiert, wobei beide Arrays jeweils bei der Rekursion ihre Rolle als Quelle oder
Ziel tauschen. Der Algorithmus stellt sicher, dass im letzten Schritt immer die zwei
sortierten Hälften des ursprünglichen Quellarray in das ursprüngliche Zielarray sortiert
zusammengeführt werden.
Die hier gezeigte Implementierung von Mergesort ist trotzdem noch nicht optimal,
da sich die merge Operation ebenfalls parallelisieren lässt (siehe [LP98, Kap. 2.2, S. 9ff]
und das Cilk Beispielprogramm cilksort.cilk).
Abbildung 4.11 zeigt das Ergebnis der Leistungsmessung des CES Programms

122
4.5 Sortieralgorithmen

Mergesort mit Zufallszahlen (CAS64 vs. Pthreads)


9
mergesort(10000000 rand) (CAS64)
mergesort(10000000 rand) (Pthreads)
8 maximum linear speedup

5
Speedup

0
1 2 3 4 5 6 7 8
Threads

Abbildung 4.11: Skalierung der Implementierung von Mergesort in CES

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 }$

Listing 4.11: Mergesort in CES (CES Unterprogramm sort_mergesort) – sort.ces

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 }$

Listing 4.12: Mergesort in CES (CES Unterprogramm mergesort_internal) – sort.


ces

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 }$

Listing 4.13: Mergesort in CES (CES Unterprogramm merge) – sort.ces

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.

Quicksort mit Zufallszahlen (CAS64 vs. Pthreads)


9
quicksort(10000000 rand) (CAS64)
quicksort(10000000 rand) (Pthreads)
8 maximum linear speedup

5
Speedup

0
1 2 3 4 5 6 7 8
Threads

Abbildung 4.12: Skalierung der Implementierung von Quicksort in CES

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 }$

Listing 4.14: Quicksort in CES (CES Unterprogramm sort_quicksort) – sort.ces

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 }$

Listing 4.15: Quicksort in CES (CES Unterprogramm quicksort_internal) – sort.


ces

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 }

Listing 4.16: Quicksort in CES (C Unterprogramm partition) – sort.ces

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)

Listing 4.17: Quicksort in CES (C Makro SWAP) – sort.ces

129
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES

4.6 Suchalgorithmen

4.6.1 Unvollständige Suchalgorithmen

Unvollständige Suchalgorithmen, die ihre parallele Verarbeitung abbrechen möchten,


weil sie nicht den vollständigen Suchraum analysieren müssen, lassen sich in der
aktuellen Implementierung von CES nicht gut implementieren. Es ist möglich den
Abbruch durch Kommunikation über globale Variablen zu realisieren, wie es auch bei
Cilk und FJTask notwendig ist. Dies stellt jedoch keine saubere Lösung des Problems
dar. In die Zukunft von CES wird daher eine andere Lösung angestrebt.
Aus dem oben genannten Grund werden in dieser Arbeit keine Suchalgorithmen mit
unvollständiger Auswertung des Suchraums untersucht.

4.6.2 Suche nach dem Minimum und oder Maximum eines unsortierten
Array

Der Quelltext der Implementierung der Minimum/Maximum Suche in CES ist in


Listing 4.18 und Listing 4.19 wiedergegeben.

Search Minimum (CAS64 vs. Pthreads)


9
searchmin(10000000 rand) (CAS64)
searchmin(10000000 rand) (Pthreads)
8 maximum linear speedup

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

Search Maximum (CAS64 vs. Pthreads)


9
searchmax(10000000 rand) (CAS64)
searchmax(10000000 rand) (Pthreads)
8 maximum linear speedup

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

1 $search_minmax(ptr_t array, size_t n, size_t size, compare_t compare;; ptr_t result){


2 if ($n$ == 1) {
3 /* array has one element and is min/max by default */
4 $result$ = $array$;
5 } else {
6 /* divide and conquer */
7 size_t nleft = $n$ / 2;
8 size_t nright = $n$ - nleft;
9 // ptr_t leftarray = $array$;
10 ptr_t rightarray = $array$ + nleft * $size$;
11
12 $parallel search_minmax(array, size_t nleft, size, compare;; ptr_t leftresult);$
13 $parallel search_minmax(ptr_t rightarray, size_t nright, size, compare;; ptr_t '
rightresult);$
14 $minmax(leftresult, rightresult, compare;; result);$
15 }
16 }$

Listing 4.18: Minimum-/Maximumsuche in CES (CES Unterprogramm


search_minmax) – search.ces

1 $minmax(ptr_t element1, ptr_t element2, compare_t compare;; ptr_t result){


2 if ($compare$($element1$, $element2$) >= 0) { /* prefer left element if equal */
3 $result$ = $element1$;
4 } else {
5 $result$ = $element2$;
6 }
7 }$

Listing 4.19: Minimum-/Maximumsuche in CES (CES Unterprogramm minmax) –


search.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.

MemCopy (CAS64 vs. Pthreads)


9
memcopy(1024) (CAS64)
memcopy(1024) (Pthreads)
8 maximum linear speedup

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 }$

Listing 4.20: MemCopy in CES (CES Unterprogramm pmemcopy) – memcopy.ces

4.8 Numerische Integration


Listing 4.21, Listing 4.22 und Listing 4.23 zeigen die Implementierung eines einfachen
numerischen Integrationsalgorithmus des FJTask Beispielprogramms integrate.java
[Lea00] in CES, welcher das Divide and Conquer Konzept verwendet.
Abbildung 4.16 zeigt das Ergebnis der Leistungsmessung des CES Programms zur
numerischen Integration (integrate.ces) für das Intervall [−10; 20] und dem Expo-
nenten 8 der zu integrierenden Beispielfunktion. Die Beschleunigung für einen Thread
beträgt für die Synchronisation mittels CAS64 S¯1 = 0,81 und für die Synchronisation
mittels Pthreads S¯1 = 0,35. Ist dieser parallele Synchronisationsoverhead einmal be-
zahlt, skaliert die Anwendung nahezu linear. Bei der Synchronisation mittels CAS64
flacht die Skalierung leicht ab. Duch den seriellen Anteil des Algorithmus kommt das
Amdahlsche Gesetz ins Spiel, das die maximal erzielbare Beschleunigung beschränkt.

134
4.8 Numerische Integration

Nummerische Integration (CAS64 vs. Pthreads)


9
integrate(8 -10 20) (CAS64)
integrate(8 -10 20) (Pthreads)
8 maximum linear speedup

5
Speedup

0
1 2 3 4 5 6 7 8
Threads

Abbildung 4.16: Skalierung der Implementierung des numerischen Integrierens in CES

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 }$

Listing 4.21: Numerische Integration in CES (CES Unterprogramm integrate) –


integrate.ces

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 }$

Listing 4.22: Numerische Integration in CES (CES Unterprogramm quadrature) –


integrate.ces

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 }$

Listing 4.23: Numerische Integration in CES (CES Unterprogramm add_double) –


integrate.ces

137
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES

4.9 Mandelbrot Menge – Apfelmännchen


Listing 4.24 und Listing 4.25 zeigen eine Implementierung der Berechnung und Visuali-
sierung der Mandelbrot Menge, eines auch als „Apfelmännchen“ bekannten Fraktals, als
CES Programm. Zur Visualisierung wurde eine einfache Bitmap-Bibliothek [RS04] einer
früheren Studienarbeit verwendet, welche die Erstellung einer schwarz-weißen Bitmap-
graphik aus C heraus ermöglicht. Abbildung 4.17 zeigt eine mit dem CES Programm
erzeugte Bitmapgraphik der Mandelbrot Menge.

Abbildung 4.17: Graphische Darstellung der Mandelbot Menge

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

zn+1 = zn2 + c (4.9)

und der Anfangsbedingung z0 = 0 beschränkt bleibt.

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.

Mandelbrot-Menge (CAS64 vs. Pthreads)


9
mandelbrot(-2.25 0.75 -1.25 1.25 800 2000) (CAS64)
mandelbrot(-2.25 0.75 -1.25 1.25 800 2000) (Pthreads)
8 maximum linear speedup

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

dennoch den selben Arbeitsanteil geleistet hat.

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 }$

Listing 4.24: Mandelbrot Menge in CES (CES Unterprogramm mandelbrot) –


mandelbrot.ces

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 }

Listing 4.25: Mandelbrot Menge in CES (C Unterprogramm mandelbrot_iterations)


– mandelbrot.ces

143
4 Leistungsbewertung der parallelen Laufzeitumgebung für CES

4.10 Zusammenfassung der Ergebnisse der untersuchten


Anwendungen der parallelen Laufzeitumgebung für CES
Es konnte gezeigt werden, dass die implementierte Synchronisation der parallelen
Laufzeitumgebung für CES mittels der Synchronisierungsprimitiven der POSIX Threads
Bibliothek kostspieliger ist als die Synchronisation mittels der atomaren Operationen.
Dies ist besonders bei Anwendungen mit relativ gringer Arbeit pro Unterprogramm
sichtbar. Eine zu geringe Granularität der parallelen Unterprogramme führt zu einer
flachen Steigung.
Einige Anwendungen skalieren linear bis zu acht Hardware-Threads, andere An-
wendungen jedoch nicht. Ihre Beschleunigungskurve flacht mit zunehmender Anzahl
an Threads ab. Bei einigen liegt dies mutmaßlich an der Speicherbandbreite (z. B.
MemCopy, N -Damen Problem), bei anderen am eventuell zu geringen Anteil paralleler
Arbeit.

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

der Implementierung unter Verwendung der Synchronisationsprimitiven der POSIX


Threads Bibliothek 0,29 und unter Verwendung der atomaren Operationen 0,67. Im
besten Fall, bei Anwendungen mit großer Granularität, wie beispielsweise der Mandel-
brot Menge aus Abschnitt 4.9, ist der parallele Synchronisationsoverhead im Vergleich
verschwindend gering.

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:

• Untersuchung der sequentiellen und parallelen Performance von CES im Vergleich


zur besten sequentiellen Implementierung in C.

• Untersuchung der parallelen Performance, Benutzerfreundlichkeit und Produktivi-


tät von CES im Vergleich zu anderen Systemen zur transparenten nebenläufigen
Programmierung wie beispielsweise Cilk, TBB, FJTask, Open Multi-Processing
(OpenMP), . . . .

• Erweiterung des Cop/Thief Work-Stealing um einen Mechanismus zur Beschleu-


nigung des Scannens des fremden Frame Stack. Eventuell könnte der Thief Frame
Stack Pointer zwischen einzelnen Stehlversuchen gecached werden, so dass bereits
untersuchte Frames nicht bei jedem Stehlvorgang erneut analysiert werden müs-
sen. Zur Überprüfung, ob sich der Frame Stack unterhalb des Thief Frame Stack
Pointer zwischen zwei Stehlversuchen geändert hat, könnte möglicherweise der
im Schalter parallel eines als parallel abarbeitbar markierten Task Frame ge-
speicherte „ABA“-Zähler verwendet werden. Der Thief würde nach erfolgreichem
Stehlen den Wert des „ABA“-Zählers des gestohlenen Task Frame zusammen
mit dem aktuellen Thief Frame Stack Pointer in der Datenstruktur des fremden
Frame Stack sichern. Wäre beim folgenden Versuch, einen Task Frame zu steh-
len, der Frame Stack Pointer niedriger als der Thief Frame Stack Pointer oder
würde der Wert des „ABA“-Zählers des zu stehlenden Task Frame eine Differenz
größer als eins zum gesicherten vorherigen „ABA“-Zähler aufweisen, so müsste
der Thief den fremden Frame Stack erneut von ganz unten scannen und nicht

147
6 Ausblick

ab der gesicherten letzten Position. Zu berücksichtigen wäre dabei der Überlauf


des „ABA“-Zählers und die richtige Reihenfolge der Markierung der als parallel
abarbeitbar markierten Task Frames beim Kopieren vom Current Stack.

• Untersuchung, ob der Thief den eigenen Frame Stack für andere Thieves sperren
sollte.

• Untersuchung der Erweiterungs- und Verbesserungsmöglichkeit des Cop/Thief


Work-Stealing um Parallelitäts-Feedback nach [AHL06].

Der Prototyp C with Execution System (CES) bietet darüber hinaus noch viele
weitere interessante zu untersuchende und zu implementierende Bereiche, wie beispiels-
weise:

• Untersuchung möglicher Konzepte zur Optimierung der Ausführung der neuen


Implementierung von Unterprogrammen.

• Entwicklung und Implementierung eines Konzepts zur einfachen Integration von


CES in bestehende C Anwendungen.

• Erweiterung von CES um Task Frames variabler Größe zur speichereffizienten


Übergabe einer beliebigen Anzahl an Argumenten beim Unterprogrammaufruf.

• Erweiterung des CESC um eine intelligentere Erzeugung von Parametervariablen


(Storage Frames) auf dem Frame Stack.

• Untersuchung der Machbarkeit dynamischer Erkennung von parallel ausführbaren


Unterprogrammaufrufen zur Programmlaufzeit als Erweiterung zur statischen
Markierung zur Übersetzungszeit.

• Erweiterung des CESC um eine Codeanalyse zur automatischen Erkennung


und Markierung von parallel ausführbaren Unterprogrammaufrufen und zur
Generierung von anonymen Unterprogrammen beim Zugriff auf Unterprogramm-
ergebnisse.

• Erweiterung des CESC um eine eigenständige Syntax und Semantikanalyse zur


Steigerung der Benutzerfreundlichkeit.

Nicht nur aufgrund der derzeitigen unaufhaltsamen Entwicklung hin zu Mehrkern-


prozessoren und vielleicht bald sogar Manycoreprozessoren im Büro- und Heimbereich
ist die aktuelle Entwicklung diverser neuer Konzepte zur transparenten nebenläufigen
Programmierung hochinteressant. Abschließend bleibt nur noch die Frage, ob sich ein

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

B.1 Producer/Consumer Demonstrationsprogramm mit


Pthreads in C
B.1.1 Programmquelltext

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 }

Listing B.1: Producer/Consumer Demonstrationsprogramm unter Verwendung eines


FIFO Ringpuffers zum Datenaustausch mit Pthreads in C

B.1.2 Bildschirmausgabe

1 Producer 0: Ready ...


2 Producer 1: Ready ...
3 Consumer 0: Ready ...
4 Consumer 0: Queue is empty, going to sleep ...
5 Consumer 1: Ready ...
6 Consumer 1: Queue is empty, going to sleep ...
7 Producer 0: Produced item 1 ...
8 Producer 0: Appended item 1 to the queue [1] ...
9 Consumer 0: Awoke, checking queue ...
10 Consumer 0: Took item 1 from the queue [0] ...
11 Consumer 0: Consumed item 1 ...
12 Consumer 0: Queue is empty, going to sleep ...
13 Producer 1: Produced item 6 ...
14 Producer 1: Appended item 6 to the queue [1] ...
15 Consumer 1: Awoke, checking queue ...
16 Consumer 1: Took item 6 from the queue [0] ...
17 Producer 0: Produced item 2 ...
18 Producer 0: Appended item 2 to the queue [1] ...
19 Consumer 0: Awoke, checking queue ...
20 Consumer 0: Took item 2 from the queue [0] ...
21 Producer 1: Produced item 7 ...

158
B.1 Producer/Consumer Demonstrationsprogramm mit Pthreads in C

22 Producer 1: Appended item 7 to the queue [1] ...


23 Producer 0: Produced item 3 ...
24 Producer 0: Queue is full, going to sleep ...
25 Consumer 1: Consumed item 6 ...
26 Consumer 1: Took item 7 from the queue [0] ...
27 Producer 0: Awoke, checking queue ...
28 Producer 0: Appended item 3 to the queue [1] ...
29 Producer 1: Produced item 8 ...
30 Producer 1: Queue is full, going to sleep ...
31 Consumer 0: Consumed item 2 ...
32 Consumer 0: Took item 3 from the queue [0] ...
33 Producer 1: Awoke, checking queue ...
34 Producer 1: Appended item 8 to the queue [1] ...
35 Consumer 0: Consumed item 3 ...
36 Consumer 0: Took item 8 from the queue [0] ...
37 Consumer 0: Consumed item 8 ...
38 Consumer 0: Queue is empty, going to sleep ...
39 Producer 1: Produced item 9 ...
40 Producer 1: Appended item 9 to the queue [1] ...
41 Consumer 0: Awoke, checking queue ...
42 Consumer 0: Took item 9 from the queue [0] ...
43 Producer 0: Produced item 4 ...
44 Producer 0: Appended item 4 to the queue [1] ...
45 Consumer 1: Consumed item 7 ...
46 Consumer 1: Took item 4 from the queue [0] ...
47 Producer 0: Produced item 5 ...
48 Producer 0: Appended item 5 to the queue [1] ...
49 Producer 0: Terminating ...
50 Producer 1: Produced item 10 ...
51 Producer 1: Queue is full, going to sleep ...
52 Consumer 0: Consumed item 9 ...
53 Consumer 0: Terminating ...
54 Consumer 1: Consumed item 4 ...
55 Consumer 1: Took item 5 from the queue [0] ...
56 Producer 1: Awoke, checking queue ...
57 Producer 1: Appended item 10 to the queue [1] ...
58 Producer 1: Terminating ...
59 Consumer 1: Consumed item 5 ...
60 Consumer 1: Took item 10 from the queue [0] ...
61 Consumer 1: Consumed item 10 ...
62 Consumer 1: Terminating ...

Listing B.2: Bildschirmausgabe des Producer/Consumer Demonstrationsprogramms


mit Pthreads in C aus Listing B.1

159
B Listings

B.2 Bildschirmausgabe des Pretty Printer der round-robin


Laufzeitumgebung für CES

1 ****************************** INITIAL STATE THREAD 0 ******************************


2 2 program(int argc, char** argv;;)
3 1 cop(;;)
4 ****************************** INITIAL STATE THREAD 1 ******************************
5 1 thief(;;)
6 ****************************** THREAD 0 ******************************
7 *****************EXECUTE 1****************
8 2 program(int argc, char** argv;;)
9 1 cop(;;)
10 ****************************** THREAD 1 ******************************
11 *****************EXECUTE 1****************
12 1 thief(;;)
13 thief: Looking for something to steal from thread 0...
14 thief: There was nothing to steal from thread 0...
15 thief: Giving up (looked at all other threads)...
16 ****************************** THREAD 0 ******************************
17 *****************EXECUTE 2****************
18 6 mergesort(n=4, ...; array;)
19 1 cop(;;)
20 ****************************** THREAD 1 ******************************
21 *****************EXECUTE 2****************
22 1 thief(;;)
23 thief: Looking for something to steal from thread 0...
24 thief: Stealing task ’mergesort’ from thread 0...
25 ****************************** THREAD 0 ******************************
26 *****************EXECUTE 3****************
27 12 mergesort(nleft=2, ...; left;) PARALLEL
28 11 thief(;;)
29 10 merge(left, nleft=2, right, nright=2, ...; array;)
30 1 cop(;;)
31 ****************************** THREAD 1 ******************************
32 *****************EXECUTE 3****************
33 3 mergesort(nright=2, ...; right;) PARALLEL
34 2 cop(;;)
35 1 thief(;;)
36 ****************************** THREAD 0 ******************************
37 *****************EXECUTE 4****************
38 18 mergesort(nleft=1, ...; left;) PARALLEL
39 17 mergesort(nright=1, ...; right;) PARALLEL
40 16 merge(left, nleft=1, right, nright=1, ...; left;)
41 11 thief(;;)
42 10 merge(left, nleft=2, right, nright=2, ...; array;)
43 1 cop(;;)
44 ****************************** THREAD 1 ******************************
45 *****************EXECUTE 4****************
46 9 mergesort(nleft=1, ...; left;) PARALLEL
47 8 mergesort(nright=1, ...; right;) PARALLEL
48 7 merge(left, nleft=1, right, nright=1, ...; right;)

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

153 ****************************** THREAD 1 ******************************


154 *****************EXECUTE 13****************
155 1 thief(;;)
156 thief: Looking for something to steal from thread 0...
157 thief: There was nothing to steal from thread 0...
158 thief: Giving up (looked at all other threads)...
159 ****************************** THREAD 0 ******************************
160 STORAGE
161 ****************************** THREAD 1 ******************************
162 *****************EXECUTE 14****************
163 1 thief(;;)
164 thief: Looking for something to steal from thread 0...
165 thief: There was nothing to steal from thread 0...
166 thief: Giving up (looked at all other threads)...
167 ****************************** THREAD 0 ******************************
168 STORAGE
169 ****************************** THREAD 1 ******************************
170 *****************EXECUTE 15****************
171 1 thief(;;)
172 thief: Looking for something to steal from thread 0...
173 thief: There was nothing to steal from thread 0...
174 thief: Giving up (looked at all other threads)...
175 ****************************** THREAD 0 ******************************
176 STORAGE
177 ****************************** THREAD 1 ******************************
178 *****************EXECUTE 16****************
179 1 thief(;;)
180 thief: Looking for something to steal from thread 0...
181 thief: There was nothing to steal from thread 0...
182 thief: Giving up (looked at all other threads)...
183 ****************************** THREAD 0 ******************************
184 STORAGE
185 ****************************** THREAD 1 ******************************
186 *****************EXECUTE 17****************
187 1 thief(;;)
188 thief: Looking for something to steal from thread 0...
189 thief: There was nothing to steal from thread 0...
190 thief: Giving up (looked at all other threads)...
191 ****************************** THREAD 0 ******************************
192 *****************EXECUTE 10****************
193 1 cop(;;)
194 cop: Killing thief...
195 ****************************** THREAD 1 ******************************
196 *****************EXECUTE 18****************
197 1 thief(;;)
198 thief: Got killed by cop...

Listing B.3: Reduzierte Bildschirmausgabe des Pretty Printer während der round-robin
Ausführung von Mergesort aus Listing 3.3

163
B Listings

B.3 Atomare Operationen in C mit Inline-Assembler für IA-32


B.3.1 32-Bit Compare and Swap mit booleschem Rückgabewert

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

B.3.2 32-Bit Compare and Swap mit datentypabhängigem Rückgabewert

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

B.3.3 64-Bit Compare and Swap mit booleschem Rückgabewert

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

B.3.4 64-Bit Compare and Swap mit datentypabhängigem Rückgabewert

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

B.4 Definition des Thief Unterprogramms der parallelen


Laufzeitumgebung für CES mit Pthreads Synchronisation

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)

45 /* steal task frame */


46 COP_TASK_FRAME * cop;
47 THIEF_TASK_FRAME * thief;
48
49 /* 1. create cop task frame on top of own task frame stack */
50 (*fsp)++;
51 cop = (COP_TASK_FRAME *)*fsp;
52 cop->fnptr_cop_task = func_cop;
53 (*fsp)->parallel = 0;
54
55 /* 2. copy foreign task frame on top of own frame stack */
56 (*fsp)++;
57 **fsp = *victim_fsp;
58
59 /* 3. overwrite foreign task frame with theif task frame */
60 thief = (THIEF_TASK_FRAME *)victim_fsp;
61 thief->fnptr_thief_task = func_thief;
62 victim_fsp->parallel = 0;
63
64 /* 4. initialize cop parameters */
65 cop->thief = thief;
66
67 pthread_mutex_unlock(&victim_td->thief_fsp_top_mutex); /* unlock exec_top */
68 pthread_mutex_unlock(&victim_td->thief_mutex); /* unlock victim’s frame stack */
69 return; /* thief’s work is done for now */
70 }
71
72 pthread_mutex_unlock(&victim_td->thief_mutex); /* unlock victim’s frame stack */
73 }
74
75 /* the thief can only be removed from the thread’s task frame stack if it has been killed '
but has not stolen anything */
76 /* thief has been killed by its cop */
77 --(*fsp);
78 }

Listing B.8: Definition des Thief Unterprogramms (Parallele Laufzeitumgebung für


CES; Pthreads)

169
B Listings

B.5 Definition des Thief Unterprogramms der parallelen


Laufzeitumgebung für CES mit CAS64 Synchronisation

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)

46 /* try to steal frame_fnptr using atomic compare and exchange (CAS) */


47 if (!atomic_bool_cas64(&((frame_fnptr *)victim_fsp)->uint64, victim_fnptr.uint64, '
thief_fnptr.uint64)) {
48 /* CAS failed: exec_top has modified the frame (i.e. reset the parallel flag) '
*/
49 CES_TRACE(("thief (%d): Got into conflict with exec_top; exec_top is probably '
executing the frame...\n", own_id));
50 break; /* back-off */
51 }
52
53 CES_TRACE(("thief (%d): Successfully stealed task frame ’%s’ (%u) pointer from '
thread %d...\n", own_id, victim_fsp->name, ((ptrdiff_t)victim_fsp - (ptrdiff_t)'
FS_BOTTOM(victim_td)) / sizeof(TASK_FRAME), victim_id));
54
55 /* steal the rest of the task frame */
56 COP_TASK_FRAME * cop = (COP_TASK_FRAME *)(*fsp + 1); /* cop frame at fsp + 1 */
57 THIEF_TASK_FRAME * thief = (THIEF_TASK_FRAME *)victim_fsp;
58 TASK_FRAME * stolen_task = *fsp + 2; /* stolen task frame at fsp + 2 */
59
60 /* create cop task frame on top of own task frame stack */
61 *(frame_fnptr *)(&cop->fnptr_cop_task) = cop_fnptr;
62
63 /* copy foreign task frame on top of own frame stack */
64 *stolen_task = *victim_fsp; /* copy task frame with thief task fnptr; parallel is '
zero */
65 assert(!stolen_task->parallel);
66 stolen_task->fnptr_task = victim_fnptr.fnptr.fnptr_task; /* reset old task fnptr */
67
68 /* initialize cop parameters and increment frame pointer */
69 cop->thief = thief;
70 (*fsp) += 2; /* cop & stolen task frames */
71
72 CES_GATHER_STATISTIC(own_td->statistics.task_didsteal_count++);
73 CES_GATHER_STATISTIC(victim_td->statistics.task_gotstolen_count++);
74
75 pthread_mutex_unlock(&victim_td->thief_mutex); /* unlock victim’s frame stack */
76 return; /* thief’s work is done for now */
77 }
78
79 pthread_mutex_unlock(&victim_td->thief_mutex); /* unlock victim’s frame stack */
80 }
81
82 /* the thief can only be removed from the thread’s task frame stack if it has been killed '
but has not stolen anything */
83 /* thief has been killed by its cop */
84 --(*fsp);
85 }

Listing B.9: Definition des Thief Unterprogramms (Parallele Laufzeitumgebung für


CES; CAS)

171
B Listings

B.6 Testprogramme zur Verifikation der parallelen


Laufzeitumgebung für CES
B.6.1 Testprogramm 1

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 }$

Listing B.10: Testprogramm 1 – test_parallel_runtime_task_execution1.ces

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

50 /* hack: keep own task frame forever on the frame stack */


51 (*fsp)++;
52
53 /* check counter(s) for errors */
54 int i;
55 for (i = 0; i < NTASKS; ++i) {
56 if (counter[i]) {
57 printf("Error: Counter %d has value %"PRId32" != zero!\n", i, counter[i]);
58 exit(1);
59 }
60 }
61
62 /* create parallel child task(s) */
63 for (i = NTASKS-1; i >= 0 ; --i) {
64 $parallel task(int i;;);$
65 atomic_inc32(&counter[i]);
66 }
67
68 /* this task runs forever in a "recursive" loop */
69 /* hack: calling forever here does not work as previous calls to task created
70 NTASK storage frames on frame stack which will then grow to infinity
71 DOLLARforever(;;);DOLLAR
72 */
73 }$
74
75 /**
76 * CES Subroutine TASK
77 */
78 $task(int i;;){
79 assert((0 <= $i$) && ($i$ < NTASKS));
80 atomic_dec32(&counter[$i$]);
81 }$

Listing B.11: Testprogramm 2 – test_parallel_runtime_task_execution2.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

D.1 Systemkonfiguration hdclb063


processor : 0
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 : 0
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 : 4670.13

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

Listing D.1: Ausgabe von cat /proc/cpuinfo

180
D.2 GNU Compiler Collection (GCC)

D.2 GNU Compiler Collection (GCC)


D.2.1 GCC 4.2.2

Using built-in specs.


Target: i686-pc-linux-gnu
Configured with: /usr/src/gcc-4.2.2/configure
Thread model: posix
gcc version 4.2.2

Listing D.2: Ausgabe von gcc -v

D.2.2 GCC 4.1.0

Using built-in specs.


Target: i386-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info '
--enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --'
enable-__cxa_atexit --disable-libunwind-exceptions --with-gxx-include-dir=/usr/include/c'
++/3.4.3 --enable-libgcj-multifile --enable-languages=c,c++,java,f95 --enable-java-awt=gtk '
--disable-dssi --with-java-home=/usr/lib/jvm/java-1.4.2-gcj-1.4.2.0/jre --with-cpu=generic '
--host=i386-redhat-linux
Thread model: posix
gcc version 4.1.0 20060515 (Red Hat 4.1.0-18)

Listing D.3: Ausgabe von gcc -v

D.2.3 GCC 3.4.6

Reading specs from /usr/lib/gcc/i386-redhat-linux/3.4.6/specs


Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info '
--enable-shared --enable-threads=posix --disable-checking --with-system-zlib --enable-'
__cxa_atexit --disable-libunwind-exceptions --enable-java-awt=gtk --host=i386-redhat-linux
Thread model: posix
gcc version 3.4.6 20060404 (Red Hat 3.4.6-3)

Listing D.4: Ausgabe von gcc -v

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.

Deadlock Ein Deadlock (deutsch: Verklemmung) bezeichnet in der Informatik einen


Zustand, bei dem ein oder mehrere Prozesse auf Betriebsmittel warten, die dem
Prozess selbst oder einem anderen beteiligten Prozess zugeteilt sind.

Hyper-Threading Der Beriff Hyper-Threading oder auch Mehrfädigkeit beschreibt


die Fähigkeit von Prozessoren, mit nur einem vollständigen Hauptprozessor
mehrere Programme quasi gleichzeitig zu bearbeiten. Der Begriff Hyper-Threading
Technology (HTT) bezeichnet Intels Implementierung von Hyper-Threading.
http://www.intel.com/info/hyperthreading/.

Laufzeitumgebung Eine Laufzeitumgebung (engl. Runtime Environment) verwaltet


ein Programm während der Laufzeit und stellt diesem Dienste zur Verfügung.
Ein bekanntes Beispiel ist die Java Runtime Environment (JRE).
Livelock Ein Livelock bezeichnet in der Informatik eine Art des Deadlocks von zwei oder
mehr Prozessen, wobei diese nicht in einem Zustand verharren, sondern ständig
zwischen mehreren Zuständen wechseln, aus denen sie nicht mehr entkommen
können.
Lock Unter Locking (englisch für Sperren) versteht man in der Informatik das Sperren
des Zugriffs auf eine Ressource, um den exklusiven Zugriff durch einen Prozess
oder Thread sicherzustellen.

Manycoreprozessor Als Manycoreprozessor (englisch: many-core processing unit) be-


zeichnet man einen Mehrkernprozessor mit mehr als acht Hauptprozessoren.

183
Glossar

Mehrkernprozessor Als Mehrkernprozessor (englisch: multi-core processing unit) be-


zeichnet man einen Mikroprozessor mit mehr als einem vollständigem Hauptpro-
zessor auf einem einzigen Chip.

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.

Race Condition Eine Race Condition (deutsch: Wettlaufsituation) bezeichnet in der


Informatik eine Konstellation, in der das Ergebnis einer Operation vom zeitlichen
Verhalten bestimmter Einzeloperationen abhängt.

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

[Alt06] Alt, Helmut: Schnelle Sortieralgorithmen – Sortieren großer Datenmen-


gen. In: Algorithmus der Woche (2006), 21. März, Nr. 3. http://www-i1.
informatik.rwth-aachen.de/~algorithmus/algo3.php, Abruf: 2008-01-
16

[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.

[BL99] Blumofe, Robert D. ; Leiserson, Charles E.: Scheduling Multithrea-


ded Computations by Work Stealing. In: Journal of the ACM 46 (1999),
September, Nr. 5, 720–748. http://supertech.csail.mit.edu/papers/
steal.pdf, Abruf: 2007-12-18

[Blu95] Blumofe, Robert D.: Executing Multithreaded Programs Efficiently. Cam-


bridge, Massachusetts, Department of Electrical Engineering and Computer
Science, Massachusetts Institute of Technology, Diss., September 1995.
http://supertech.csail.mit.edu/papers/rdb-phdthesis.pdf, Abruf:
2007-12-18. – Available as MIT Laboratory for Computer Science Technical
Report MIT/LCS/TR-677.

[BOI] BOINC – Berkeley Open Infrastructure for Network Computing. http:


//boinc.berkeley.edu/, Abruf: 2007-12-18

[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

[Cil] The Cilk Project. http://supertech.csail.mit.edu/cilk/, Abruf: 2007-


12-19

185
Literaturverzeichnis

[DG04] Dean, Jeffrey ; Ghemawat, Sanjay: MapReduce: Simplified Data Proces-


sing on Large Clusters. In: Proceedings of the Sixth Symposium on Operating
System Design and Implementation (OSDI’04). San Francisco, CA, USA,
Dezember 2004, 137–150

[DMS] Dice, Dave ; Moir, Mark ; Scherer III, William: Quickly Reacquirable
Locks

[DTM07] Du Toit, Stefanus ; McCool, Michael: RapidMind: C++ Meets Multicore.


In: Dr. Dobb’s Portal (2007), 8. Juni. http://www.ddj.com/architect/
199902702, Abruf: 2007-12-04

[Gil07] Gillespie, Matt: Transitioning Software to Future Generations of Multi-


Core. Version: 8. Mai 2007. http://softwarecommunity.intel.com/
articles/eng/1273.htm, Abruf: 2008-01-12. Intel Software Network

[Gus88] Gustafson, John L.: Reevaluating Amdahl’s Law. In: Communications


of the ACM 31 (1988), Nr. 5, 532–533. http://www.scl.ameslab.gov/
Publications/Gus/AmdahlsLaw/Amdahls.html, Abruf: 2008-01-18

[Her] Herlihy, Maurice: Wait-Free Synchronization

[HLMS05] Hendler, Danny ; Lev, Yossi ; Moir, Mark ; Shavit, Nir: A dynamic-sized
nonblocking work stealing deque. 2005

[IBM] IBM (Hrsg.): Programming for AIX – Multi-Threaded Program-


ming. http://publib.boulder.ibm.com/infocenter/systems/index.
jsp?topic=/com.ibm.aix.genprogc/doc/genprogc/threads_prg.htm,
Abruf: 2008-01-02. IBM Systems Information Center

[Inta] Intel (Hrsg.): Intel Executive Biography – Gordon E. Moore. http:


//www.intel.com/pressroom/kits/bios/moore.htm, Abruf: 2008-01-11

[Intb] Intel (Hrsg.): Intel® Threading Building Blocks 2.0 for Open Source.
http://threadingbuildingblocks.org/, Abruf: 2007-12-10

[Intc] Intel (Hrsg.): Moore’s Law. http://www.intel.com/technology/


mooreslaw/, Abruf: 2007-12-05

[Int05] International Business Machines Corporation (Hrsg.): IBM XL C


Enterprise Edition V8.0 for AIX Compiler Reference. 1. Ausgabe. Interna-
tional Business Machines Corporation, August 2005

[Int07a] Intel (Hrsg.): Intel® 64 and IA-32 Architectures Optimization Refe-


rence Manual. Intel, November 2007. http://www.intel.com/design/
processor/manuals/248966.pdf, Abruf: 2008-01-10. – Order Number:
248966-016

186
Literaturverzeichnis

[Int07b] Intel (Hrsg.): Intel® 64 and IA-32 Architectures Software Developer’s


Manual – Volume 2A: Instruction Set Reference, A-M. Intel, November
2007. http://www.intel.com/design/processor/manuals/253666.pdf,
Abruf: 2008-01-10. – Order Number: 253666-025US

[Int07c] Intel (Hrsg.): Intel® 64 and IA-32 Architectures Software Developer’s


Manual – Volume 2B: Instruction Set Reference, N-Z. Intel, November
2007. http://www.intel.com/design/processor/manuals/253667.pdf,
Abruf: 2008-01-10. – Order Number: 253667-025US

[Int07d] Intel (Hrsg.): Intel® 64 and IA-32 Architectures Software Developer’s


Manual – Volume 3A: System Programming Guide. Intel, November 2007.
http://www.intel.com/design/processor/manuals/253668.pdf, Abruf:
2008-01-10. – Order Number: 253668-025US

[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

[Lan07] Lang, H. W.: Mergesort. Version: 4. November 2007. http://www.inf.


fh-flensburg.de/lang/algorithmen/sortieren/merge/merge.htm, Ab-
ruf: 2008-01-16

[Lea00] Lea, Doug: A Java Fork/Join Framework. http://gee.cs.oswego.edu/


dl/papers/fj.pdf. Version: 2000, Abruf: 2007-11-13

[LP98] Leiserson, Charles E. ; Prokop, Harald: A Minicourse on Multithreaded


Programming. http://supertech.csail.mit.edu/papers/minicourse.
pdf. Version: 1998, Abruf: 2007-12-18

[Mic04] Michael, Maged M.: ABA Prevention Using Single-Word Instructions /


IBM Research Division. Version: 29. Januar 2004. http://www.research.
ibm.com/people/m/michael/RC23089.pdf, Abruf: 2008-02-20. Thomas J.
Watson Research Center, P.O. Box 218, Yorktown Heights, NY 10598, USA,
29. Januar 2004. – IBM Research Report

[Ope07] OpenMP Architecture Review Board (Hrsg.): OpenMP Applica-


tion Program Interface. Draft 3.0 Public Comment. OpenMP Architec-
ture Review Board, 21. Oktober 2007. http://www.openmp.org/drupal/
mp-documents/spec30_draft.pdf, Abruf: 2007-12-11

[RK07] Reinhardt, Steve ; Karypis, George: A Multi-Level Parallel Implementa-


tion of a Program for Finding Frequent Patterns in a Large Sparse Graph.
2007

187
Literaturverzeichnis

[RS04] Remus, Jens ; Schilling, Robert: C Bonusaufgabe: Funktionsplotter.


9. August 2004. – Abschlussaufgabe der C Übung an der FH Wedel

[SB00a] Steinmacher-Burow, Burkhard D.: Task Frames. http://arxiv.org/


abs/cs.PL/0004011. Version: 2000, Abruf: 2007-11-07

[SB00b] Steinmacher-Burow, Burkhard D.: TSIA: A Dataflow Model. http:


//arxiv.org/abs/cs.PL/0003010. Version: 2000, Abruf: 2007-11-07

[SH97] Schmidt, Douglas C. ; Harrison, Tim: Double-Checked Locking – An


Optimization Pattern for Efficiently Initializing and Accessing Thread-safe
Objects. In: Martin, Robert (Hrsg.) ; Buschmann, Frank (Hrsg.) ; Riehle,
Dirke (Hrsg.): Pattern Languages of Program Design 3. Addison-Wesley,
1997

[Shi06] Shiveley, Robert: Performance Scaling in the Multi-Core Era.


Version: 2006. http://www.intel.com/cd/ids/developer/asmo-na/eng/
dc/threading/290740.htm, Abruf: 2007-12-11. Intel Software Network

[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

[Son07] Sony Computer Entertainment: PLAYSTATION® 3 Enables


Folding@home to be Recognized by Guinness World Records™ as World’s

Most Powerful Distributed Computing Network. Version: 11. Januar


2007. http://www.scei.co.jp/corporate/release/071101e.html, Ab-
ruf: 2008-01-11. 2007. – Press Release

[ST08] Stallman, Richard M. ; The GCC Developer Community ; Free


Software Foundation (Hrsg.): Using the GNU Compiler Collecti-
on. GCC 4.2.3. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA: Free Software Foundation, 2. Februar 2008. http://gcc.gnu.org/
onlinedocs/gcc-4.2.3/gcc.pdf, Abruf: 2008-02-13

[Sta] Stanford University (Hrsg.): Folding@home. http://folding.


stanford.edu/, Abruf: 2008-01-11

[Sup07] Supercomputing Technologies Group, MIT Laboratory for Com-


puter Science (Hrsg.): Cilk 5.4.6 Reference Manual. Supercompu-
ting Technologies Group, MIT Laboratory for Computer Science, 2007.
http://supertech.csail.mit.edu/cilk/, Abruf: 2007-12-18

[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

[Sut05b] Sutter, Herb: A Fundamental Turn Toward Concurrency in Software.


In: Dr. Dobb’s Portal (2005), 1. März. http://www.ddj.com/architect/
184405990, Abruf: 2007-12-04

[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

C pthread_join . . . . . . . . . . 65, 68, 71, 158


pthread_mutex_destroy . . . . . . . . . . . 74
CES Schlüsselwörter pthread_mutex_init . . . . . . . . . . . . . . . 74
parallel . . . . . . . . . . . . . . . 16, 17, 41, pthread_mutex_lock . . . . . . 74, 75, 157
42, 44, 113, 117, 121, 125, 128, pthread_mutex_t . . . . . . . . . . 74, 75, 156
132, 134, 136, 142, 172, 175 pthread_mutex_trylock . . . . . . . . . . . 74
pthread_mutex_unlock . . . . 74, 75, 157
I
pthread_rwlock_destroy . . . . . . 76, 77
IA-32 Schlüsselwörter pthread_rwlock_init . . . . . . . . . . 76, 77
lfence . . . . . . . . . . . . . . . . . . . . . 79, 80 pthread_rwlock_rdlock . . . . . . . .76, 77
lock . . . . . . . . . . . . . . . . 80, 81, 82, 83 pthread_rwlock_t . . . . . . . . . . . . . 76, 77
mfence . . . . . . . . . . . . . . . . . . . . . 79, 80 pthread_rwlock_tryrdlock . . . . 76, 77
sfence . . . . . . . . . . . . . . . . . . . . . 79, 80 pthread_rwlock_trywrlock . . . . 76, 77
pthread_rwlock_unlock . . . . . . . .76, 77
L pthread_rwlock_wrlock . . . . . . . .76, 77
pthread_t . . . . . . . . . .64, 65, 68, 71, 158
lfence . . . . . . . . . . . . . . . . . . . . . . . . . 79, 80
pthread_yield . . . . . . . . . . . . . . . . . . . . . 65
lock . . . . . . . . . . . . . . . . . . . . 80, 81, 82, 83
Pthreads Schlüsselwörter
M pthread_cancel . . . . . . . . . . . . 64, 65
pthread_cond_broadcast . . 75, 76
mfence . . . . . . . . . . . . . . . . . . . . . . . . . 79, 80 pthread_cond_destroy . . . . 74, 75
pthread_cond_init . . . . . . . . 74, 75
P
pthread_cond_signal . 75, 76, 157
parallel . . . . . . . . . . . . . . . . . . . 16, 17, 41, pthread_cond_t . . . . . . . 74, 75, 156
42, 44, 113, 117, 121, 125, 128, pthread_cond_timedwait . . . . . . 75
132, 134, 136, 142, 172, 175 pthread_cond_wait . . . . . . . 75, 157
pthread_cancel . . . . . . . . . . . . . . . . 64, 65 pthread_create64, 66, 68, 71, 158
pthread_cond_broadcast . . . . . . 75, 76 pthread_exit . . 64, 65, 66, 68, 157
pthread_cond_destroy . . . . . . . . . 74, 75 pthread_join . . . . . .65, 68, 71, 158
pthread_cond_init . . . . . . . . . . . . 74, 75 pthread_mutex_destroy . . . . . . . 74
pthread_cond_signal . . . . . 75, 76, 157 pthread_mutex_init . . . . . . . . . . .74
pthread_cond_t . . . . . . . . . . . 74, 75, 156 pthread_mutex_lock . . 74, 75, 157
pthread_cond_timedwait . . . . . . . . . . 75 pthread_mutex_t . . . . . . 74, 75, 156
pthread_cond_wait . . . . . . . . . . . 75, 157 pthread_mutex_trylock . . . . . . . 74
pthread_create . . . . 64, 66, 68, 71, 158 pthread_mutex_unlock 74, 75, 157
pthread_exit . . . . . . 64, 65, 66, 68, 157 pthread_rwlock_destroy . . 76, 77

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

También podría gustarte