Nachträgliche Parallelisierung von Computerspielen mit OpenMP

Transcription

Nachträgliche Parallelisierung von Computerspielen mit OpenMP
Nachträgliche Parallelisierung
von Computerspielen
mit OpenMP
Monika Gepperth
DIPLOMARBEIT
eingereicht am
Fachhochschul-Masterstudiengang
Digitale Medien
in Hagenberg
im Juni 2007
Copyright 2007 Monika Gepperth
Alle Rechte vorbehalten
ii
Erklärung
Hiermit erkläre ich an Eides statt, dass ich die vorliegende Arbeit selbstständig und ohne fremde Hilfe verfasst, andere als die angegebenen Quellen
und Hilfsmittel nicht benutzt und die aus anderen Quellen entnommenen
Stellen als solche gekennzeichnet habe.
Hagenberg, am 17. Juni 2007
Monika Gepperth
iii
Inhaltsverzeichnis
Erklärung
iii
Kurzfassung
vi
Abstract
vii
1 Einleitung
1.1 Begriffserklärungen . . . . . . . . . . . . . . . . . . . . . . . .
1.2 Zieldefinition . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.3 Kurzbeschreibung der Kapitel . . . . . . . . . . . . . . . . . .
2 Das
2.1
2.2
2.3
Potenzial von Multicore-Systemen
Aktueller Entwicklungsstand . . . . . . . . . . . . . . . . . .
Einsatzgebiete in Computerspielen . . . . . . . . . . . . . . .
Bedeutung für zukünftige Spiele-Entwicklung . . . . . . . . .
3 Vorstellung der Konzepte
3.1 OpenMP . . . . . . . . .
3.2 Projekt Techdemo . . .
3.3 Projekt Cube . . . . . .
3.4 Projekt ODE-Demo . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
1
3
4
5
5
16
17
20
20
21
27
28
4 Arbeitsumgebung und Implementierung
36
4.1 Einrichtung der Test– und Arbeitsumgebung . . . . . . . . . 36
4.2 Implementierung . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.3 Effizienztest . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
5 Diskussion der Ergebnisse
53
5.1 Projekt Techdemo . . . . . . . . . . . . . . . . . . . . . . . . 54
5.2 Projekt Cube . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
5.3 Projekt ODE-Demo . . . . . . . . . . . . . . . . . . . . . . . 60
6 Conclusio
65
iv
INHALTSVERZEICHNIS
v
A Parallel programming in OpenMP
A.1 Einleitung . . . . . . . . . . . . . . . . . . . . .
A.2 Erste Schritte mit OpenMP . . . . . . . . . . .
A.3 Ausnutzung der Parallelität auf Schleifenebene
A.4 Parallele Regionen . . . . . . . . . . . . . . . .
A.5 Synchronisation . . . . . . . . . . . . . . . . . .
A.6 Performance . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
67
67
68
71
72
77
81
B Screenshots Cube
B.1 templemount .
B.2 island pre . . .
B.3 fox . . . . . . .
B.4 metl3 . . . . .
B.5 douze . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
84
84
86
88
90
91
C Inhalt der CD-ROM
C.1 Diplomarbeit . . .
C.2 Quellcode . . . . .
C.3 Online–Quellen . .
C.4 Sonstiges . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
96
96
96
96
96
Literaturverzeichnis
.
.
.
.
.
97
Kurzfassung
Die vorliegende Diplomarbeit beschäftigt sich mit der Problematik der nachträglichen Optimierung von Spiele-Anwendungen durch Parallelverarbeitung.
Computerspiele skalierbar zu gestalten, war in der bisherigen SpieleEntwicklung nicht von Bedeutung, weil durch eine Erhöhung der Prozessortaktfrequenz und Verbesserung der Grafikkartenleistung auch eine direkte
Erhöhung der Framerate möglich war.
Multicore-Prozessoren sollen das Spielerlebnis neu definieren können.
Man verspricht sich eine verbesserte Spielqualität durch beispielsweise asynchrones Laden von Spielinformationen im Hintergrund, sodass ein fließender
Übergang von einem Level zum nächsten ohne Ladebildschirm möglich ist.
Die Entwicklung der Multicore-Technologie wirft die grundsätzliche Problematik auf, dass paralleles Denken um ein Vielfaches schwieriger ist, als
sich auf eine Sache zu konzentrieren. In Kombination mit einer nicht deterministischen Ausführungsreihenfolge, ergibt sich eine Komplexität, die eine
heutige Anwendungsentwicklung großteils noch überfordert. Ebenso besteht
Bedarf an aussagekräftigen Benchmark-Applikationen zur Identifikation von
potenziellen Engpässen bei der Portierung bestehender Implementierungen
auf Multicore-Systeme.
Unter Zuhilfenahme von OpenMP, einer Sammlung von Compiler-Anweisungen, Bibliotheksfunktionen und Umgebungsvariablen, werden drei sequentielle Spiel-Applikationen schrittweise parallel umgesetzt. Am Beispiel
der Cube-Engine wird demonstriert, dass eine nachträgliche Parallelisierung
der Game-Loop mit einer Leistungssteigerung verbunden sein kann.
Das Gesamtprojekt zeigt, dass sowohl die Struktur der eingesetzten Algorithmen, als auch die zugrunde liegende Systemarchitektur die erzielbare
Leistungssteigerung durch Parallelverarbeitung beeinflussen.
vi
Abstract
This thesis analyzes the limits of subsequent optimization of computer games
using concurrent processing techniques.
In the past, designing scalable computer games was of little relevance.
Due to increasing processor clock speeds and enhanced performance of graphics cards, games were continually able to achieve higher framerates.
Multi-core processors are expected to improve the experience for gamers.
Asynchronous background loading enables seamless transitions between levels without the need for load delays.
However, multi-core technology raises the issue that parallel thinking
is much more difficult than focusing on one task. In combination with a
non-deterministic order of execution, the evolving complexity places high
demands on current application development. Furthermore, programmers
need to be supported by significant benchmarking tools for multi-core systems in order to locate bottlenecks in existing single-threaded applications.
With the aid of OpenMP, an application programming interface for shared memory systems, three sequential game applications are incrementally
parallelized. Using the game engine Cube as an example, it is shown that
subsequent parallelization of game-loops can lead to increased performance
of computer games.
The overall project illustrates that both the structure of algorithms and
the underlying system architecture may influence the potential performance
gain though concurrent processing techniques. In conclusion, results are
presented and a discussion of further work is provided.
vii
Kapitel 1
Einleitung
Der PC-Anwender ist ein anpassungsfähiges Wesen. War er es vor beispielsweise fünf Jahren noch gewohnt zu warten, bis eine aufwändige Kalkulation
eines Programms beendet war, so wird er heute dank schnellerer Prozessoren
schon bei einem Bruchteil dieser Zeit ungeduldig.
Ready or not, multi-core technology is here.
Intel Corporation, 2007 [41].
1.1
Begriffserklärungen
In den folgenden Kapiteln werden einige, in der Fachsprache übliche Formulierungen verwendet, die an dieser Stelle erläutert werden sollen.
Benchmark
Benchmarking ist ein Leistungstest, um die Performance einer Anwendung
zu messen (vergleiche Performance). Übliche Kriterien bei Computerspielen sind die Auslastung des Prozessors und der Grafikkarte sowie die Framerate in Relation zur gewählten Darstellungsqualität (zur Messung der
Ausführungsgeschwindigkeit) [45].
Framerate
Abhängig von der Zeit, um alle Berechnungen des Systems durchzuführen,
wird die Anzeige (der aktuelle Frame) des Computerspiels aktualisiert. Die
Framerate ergibt sich aus der Anzahl dieser Aktualisierungen pro Sekunde
(fps) und wirkt sich auf das Spielerlebnis aus1 .
1
http://en.wikipedia.org/wiki/Framerate
1
KAPITEL 1. EINLEITUNG
2
Multitasking
Multitasking entsteht, wenn sich zwei oder mehr eigenständige Prozesse eine
Prozessor teilen“. Auf einem Singlecore-Prozessor ist ein schneller Kontext”
wechsel notwendig, damit die nach wie vor sequentiell abgearbeiteten Prozesse dem Benutzer als parallel ausgeführt erscheinen [35, S. 93]. Durch die
Multicore-Technologie ist eine echte Parallelität der Tasks möglich, indem
Prozesse auf unterschiedlichen Kernen des Prozessors ausgeführt werden.
Multithreading
Multithreading ist der Fall, wenn ein einzelner Prozess von zwei oder mehr
Threads ausgeführt wird. Diese Threads arbeiten auf einem SinglecoreProzessor oder werden auf mehreren Prozessorkernen parallel ausgeführt
[10, S. 655]. Durch die unbestimmbare zeitliche Abfolge der Threads in
der parallelen Abarbeitung, müssen Zugriffe auf gemeinsame Datenbereiche
abgesichert werden [51, S. 18].
Overhead
Overhead bezeichnet in der Parallelverarbeitung den für die parallele Umsetzung notwendigen zeitlichen Mehraufwand. Maßnahmen zu Thread-Management und Inter-Thread-Kommunikation gewährleisten korrekte Ergebnisse eines parallelen Programmes auf Kosten der Ausführungszeit [16, Kapitel 6].
Performance
Performance ist die Leistung einer Applikation im Sinne der verarbeiteten
Datenmenge pro Zeiteinheit und wird über bestimmte Testanwendungen
gemessen (vergleiche Benchmark ) [45].
Singlecore- / Multicore-Prozessor
Unter einem Singlecore-Prozessor ist ein Prozessor bestehend aus einem Prozessorkern zu verstehen, während Multicore-Prozessoren aus zwei oder mehr
Prozessorkernen bestehen.
Skalierbarkeit
Skalierbarkeit beschreibt die Anpassungsfähigkeit der Implementierung im
Sinne der effizienten Nutzung einer höheren Prozessorkernanzahl (vergleiche
hierzu Definition der Effizienz in 1.2) [51, S. 21–23].
KAPITEL 1. EINLEITUNG
3
Speedup
Der Speedup ist das Verhältnis der Ausführungszeit eines sequentiellen Programmes auf einem Prozessorkern zur parallelen Ausführung auf mehreren
Prozessorkernen. Entsteht durch die parallele Ausführung ein Geschwindigkeitsvorteil gegenüber der sequentiellen Implementierung, so spricht man
von einem Speedup der Applikation (vergleiche Abschnitt 1.2) [10, S. 654].
1.2
Zieldefinition
Ausgehend vom Vorprojekt dieser Diplomarbeit (beschrieben in Kapitel 4)
soll analysiert werden, inwieweit eine nachträgliche Parallelisierung einer
Spiele-Applikation mit OpenMP möglich ist. Darüber hinaus sollen allgemeine Schwierigkeiten bei der Unterstützung von Multicore-Systemen festgehalten werden. Ein weiteres Ziel ist es, den für eine korrekte Umsetzung
entstehenden zeitlichen Mehraufwand sowie Grenzen der Parallelisierung mit
OpenMP bewusst zu machen.
Nicht jeder Algorithmus eignet sich zur parallelen Ausführung. So wäre
es nicht zielführend, intensive Berechnungen zur Grafikdarstellung parallel
ausführen zu lassen, wenn nur ein Grafikprozessor vorhanden ist. Falls die
Reihenfolge der Berechnungen das Ergebnis bedingt oder ein Großteil der
Applikation sequentiell verbleibt, kann der Quellcode ebenfalls nicht effizient
parallel umgesetzt werden.
Welche Bedeutung der Umfang der Parallelisierung auf den zu erwartenden Speedup hat, wird im so genannten Gesetz von Amdahl festgehalten [51, S. 21]:
T1
1
(1.1)
=
Sp =
Tp
(1 − f ) + f /p
Demnach wird der maximal erreichbare Leistungszuwachs S (gleichzusetzen mit dem Unterschied der Laufzeit T ) bei p Prozessoren durch den
parallel ausführbaren Programmanteil f (repräsentiert durch Abstufung zwischen 0 und 1) begrenzt. Je näher f bei 1 liegt, desto eher lohnt ein Einsatz
von mehreren Prozessorkernen. Angenommen, die Parallelisierbarkeit einer
Spiele-Applikation beträgt 40% (≡ f = 0.4) ergäbe sich nach Amdahl auf
einem Dualcore-System eine maximale Leistungssteigerung von S = 1.25,
mit anderen Worten 25%. Die Effizienz
E=
Sp
p
(1.2)
definiert, ab wie vielen Prozessoren p keine weitere Leistungssteigerung
auftritt und wird als Maß für die Parallelisierbarkeit angesehen [51, S. 22].
Ausgehend davon, dass die Problemgröße von der Anzahl der Prozessoren
KAPITEL 1. EINLEITUNG
4
unabhängig ist, erweist sich das Gesetz von Amdahl in der Praxis nur bedingt aussagekräftig. Tatsächlich skaliert die Problematik mit der Prozessorenanzahl, was im Gesetz von Gustafson berücksichtigt wird [34]:
Sn = ts + tp · p = p + (1 − p) · (1 − f )
(1.3)
Der maximal erreichbare Speedup S ist im Vergleich zur These von Amdahl größer, weil durch die Aufteilung der Berechnungszeit tp auf p Prozessoren auch die Problemstellung, und in diesem Sinne der sequentielle Teil
des Programms ts , ebenfalls zunimmt [34].
Neben variablem Laufzeitverhalten durch zugrunde liegende Hardware,
beeinflusst vor allem die Struktur der Applikation und Komplexität der
eingesetzten Algorithmen die tatsächliche Leistungssteigerung durch Parallelverarbeitung [44, S. 214–215].
1.3
Kurzbeschreibung der Kapitel
In Kapitel 2, Das Potenzial von Multicore-Systemen“, wird der Stand der
”
und Intel
präsenTechnologie von Multicore-Prozessoren von AMD
tiert. Den Hauptteil der Diplomarbeit stellen Kapitel 3, Vorstellung der
”
Konzepte“ und Kapitel 4, Arbeitsumgebung und Implementierung“ dar,
”
die sich nach einer Vorstellung der zugrunde liegenden Projektarbeit, mit
der Umsetzung der gewählten Parallelisierungen beschäftigen. Kapitel 5,
Diskussion der Ergebnisse“ bietet eine Interpretation der Testergebnisse
”
und evaluiert, unter welchen Voraussetzungen der Einsatz von OpenMP in
Computerspielen mit einer Leistungssteigerung verbunden ist. Eine Zusammenfassung mit Zukunftsausblick in Kapitel 5, Conclusio“, dient zur Ab”
rundung der Arbeit. Zum besseren Verständnis der eingesetzten OpenMPAnweisungen, findet sich im Anhang A dieser Diplomarbeit eine Kurzinformation zum Inhalt des Buches Parallel Programming in OpenMP“ (siehe
”
[16]). Anhang B dokumentiert die in der Cube-Engine getesteten Levelpositionen.
Kapitel 2
Das Potenzial von
Multicore-Systemen
Ziel dieses Kapitel ist es, eine Einführung zu aktuellen1 Multicore-Prozessoren von AMD und Intel zu bieten. Vorgestellt werden die Prozessoren AMD
”
64 FX“, AMD Quad FX“, Intel Core 2 Duo Extreme X6800“ und Intel
”
”
”
Core 2 Extreme QX6700“. Zum Abschluss werden mögliche Einsatzgebiete
in der Spiele-Entwicklung zum gegenwärtigen Zeitpunkt und in Zukunft
präsentiert.
Wir stehen immer noch am Anfang der
Entwicklung des Computers.
Olaf Neuendorf, 2003 [51].
2.1
Aktueller Entwicklungsstand
Die Leistung von Prozessoren hat nicht proportional zum Takt zugenommen [21]. Leistungssteigerungen waren hauptsächlich auf drastischen Zuwachs der Taktfrequenz2 sowie Durchsatz der Befehle pro CPU-Takt zurückzuführen [54, S. 3]. Anforderungen von Kunden betreffend Baugröße, Energieverbrauch und Lärmpegel des Kühlsystems schränken die Möglichkeiten
der Architekturentwicklung maßgeblich ein [22].
Aufgrund dynamischer Leistung, Energieleitverlust, Drahtlaufzeit und
der Speicherhierarchie sehen sich CPU-Entwickler gezwungen neue Wege
zu gehen, um die Leistungsfähigkeit von Prozessoren zu erhöhen [19]. Die
Singlecore-Architektur erweist sich als nicht geeignet, die angestrebte höhere
Leistung bei effizientem Energieverbrauch zu realisieren [24]. MulticoreProzessoren können bei verringerter Taktrate mit geringerer Temperatur
betrieben werden und dennoch höhere Leistung erzielen [55, S. 7].
1
2
Stand März 2007
Vergleiche 1983: 5MHz – 2002: 3 GHz
5
KAPITEL 2. DAS POTENZIAL VON MULTICORE-SYSTEMEN
6
Bereits im ersten Halbjahr 2005 waren die Visionen von Multicore-Systemen für das Desktopsegment ein beliebtes Gesprächsthema in Fachzeitschriften [57]. Die Stärken der Parallelverarbeitung an sich waren bereits Mitte
der 1980er Jahre bekannt [60]. Im Vergleich zu Singlecore-Prozessoren, skaliert die Leistung von Multicore-CPUs in Relation zum Energieverbrauch
entscheidend besser [26, S. 1].
Es sollten deutliche Leistungszuwachse durch Threading, anstatt wie zuvor durch Verbesserung der zugrunde liegenden Hardware, erreicht werden
können [14]. Dualcore-Prozessoren, bisher auf den Einsatz im Serverbetrieb
beschränkt, sollten in den kommenden Jahren im Desktopbereich Einzug
halten und das Zeitalter der parallelen Datenverarbeitung einleiten [23].
Multi-core computing is
a question of when“, not if“.
”
”
Jonathan Erickson, 2005 [23].
Multicore“ ist der neue Trend im CPU-Design und hat die Philosophie
”
der immer höher angestrebten Taktfrequenzen abgelöst [20]. Die Produktion
von Dualcore-Prozessoren ist zwar teurer, doch fällt das Design der Kerne
entscheidend einfacher als vergleichbare Singlecore-Lösungen. Ein DualcoreProzessor besteht aus zwei Prozessorkernen auf einem einzelnen Sockel (vergleiche Abbildung 2.1, (a)–(c)). Die nächste Entwicklungsstufe, Prozessoren mit vier Kernen, kann auf zwei Arten umgesetzt werden. Während
Quadcore-Prozessoren von AMD zwei Sockel beanspruchen, positioniert Intel alle vier Kerne auf insgesamt nur einem Sockel (vergleiche Abbildung 2.1,
(d) und (d’)).
Im Hinblick auf die Kosten der CPU-Entwicklung und -Produktion ist
nachvollziehbar, dass bekannte Hersteller wie AMD und Intel auf MulticoreProzessoren umgestellt haben [21]. Durch die höhere Arbeitsfrequenz und
Transistorendichte werden Energieleitverlust sowie Wärmeentwicklung als
Hauptproblembereiche zukünftiger Prozessoren prognostiziert [11].
In weiterer Folge sollen die Schlüsseltechnologien der beiden Konkurrenzunternehmen AMD und Intel (in alphabetischer Reihenfolge) vorgestellt werden. Es ist nicht Ziel dieser Arbeit, die bessere“ Technologie
”
festzustellen, sondern es soll ein grober Überblick über die neuen MulticoreProzessoren der beiden Hersteller geboten werden.
AMD
Bereits 2004 präsentierte AMD den ersten x86 Dualcore-Prozessor [1, S. 3].
Erste Quadcore-Prozessoren waren für Anfang 2007 geplant, während Prozessoren mit acht Kernen für 2008 prognostiziert wurden [13] [6]. Die heutige
AMD 64“-Technologie ist speziell für Multicore-Unterstützung konzipiert
”
KAPITEL 2. DAS POTENZIAL VON MULTICORE-SYSTEMEN
(a)
(b)
(c)
Dual Processor
Hyper Threading
Dual Core
CPU State
Interrupt Logic
CPU State
Interrupt Logic
Execution
Units (ALUs)
Execution
Units (ALUs)
CS
IL
CS
IL
CS
IL
Processor Side Bus
(d)
(d’)
Quad Core (Intel)
Quad Core (AMD)
CPU
CS
CS State
Interrupt
Logic
IL
IL
CS
CS
CPU State
IL
IL
Interrupt
Logic
EU
EU
Execution
(ALUs)
(ALUs)
Units (ALU)
EU
EU
Execution
(ALUs)
(ALUs)
Units (ALU)
Processor Side Bus
CS
IL
CS
IL
EU
EU
(ALUs) (ALUs)
Execution
Units (ALUs)
Processor Side Bus
Processor Side Bus
7
CS
IL
EU
EU
(ALUs) (ALUs)
CS
IL
CS
IL
EU
EU
(ALUs) (ALUs)
Processor Side Bus
Abbildung 2.1: Prozessor-Architekturen im Vergleich: (a) eine DualCPU, (b) ein Singlecore-Prozessor mit Intels Hyperthreading Technologie,
(c) ein Dualcore-Prozessor, (d) das Quadcore-Design von Intel und (d’) ein
Quadcore-Prozessor von AMD [57].
und ermöglicht für Systeme mit sowohl 32 Bit als auch 64 Bit höchstmögliche
Performance [4]. Durch das besondere Direct Connect“-Architekturdesign
”
werden die Hauptelemente des Rechners (Prozessorkerne, Speichercontroller
und Ein-/Ausgabegeräte) direkt miteinander verbunden [1].
Athlon 64 FX
Der aktuellste3 Dualcore-Prozessor dieser Bauart trägt die Kennung Athlon
”
64 FX-62“ und ähnelt der Architektur des Athlon 64 X2“. Die FX-Variante
”
verfügt jedoch über einen DDR2-Speicher-Controller4 statt DDR (vergleiche
Abbildung 2.2).
Eine Kommunikation zwischen den einzelnen Kernen in CPU-Geschwindigkeit ist nun möglich und es gewinnt die Athlon 64 FX“-Technologie im
”
Vergleich zum Schwestermodell Athlon 64 X2“ deutlich an Leistung (siehe
”
Benchmark-Ergebnisse in Abbildung 2.3) [3].
3
4
Stand März 2007
http://de.wikipedia.org/wiki/DDR2
KAPITEL 2. DAS POTENZIAL VON MULTICORE-SYSTEMEN
Abbildung 2.2: Architektur des AMD Athlon 64. Der grundsätzliche
Aufbau des Athlon 64 X2 (links, Quelle: [5]) und des Athlon 64 FX (rechts,
Quelle: [9]) ist ähnlich.
Socket AM2 - Overall Performance
(Geometric Mean of: Office Productivity, Digital Media and Games)
AMD Athlon 64 Processor Performance Benchmark
Desktop Performance - Overall Performance
AMD Athlon 64 FX-62
131%
FX-60
124%
AMD Athlon 64 X2 5000+
120%
4800+
117%
4600+
115%
4400+
108%
4200+
106%
4000+
102%
3800+
100%
socket 939
Abbildung 2.3: Performance-Benchmark im Bereich digitale Medien
und Computerspiele der Athlon 64 X2- und FX-Serie [2].
8
KAPITEL 2. DAS POTENZIAL VON MULTICORE-SYSTEMEN
9
Folgende Eigenschaften werden seitens des Herstellers als Hauptmerkmale dieser Prozessortechnologie genannt [3]:
Die ”Hyper Transport“-Bus-Technologie beeinflusst die Gesamtlei-
stung durch optimierte I/O-Kommunikation und ermöglicht dadurch
eine Systembandbreite bis zu 8 GB/s.
Ein integrierter DDR2-Speichercontroller reduziert Speicherla-
tenzen durch direkte Verbindung von Prozessor und Arbeitsspeicher.
Hochgeschwindigkeitskommunikation bis zu 12.8 GB/s Speicherbandbreite ist erreichbar.
Ein großer ”On-Chip“-Hochleistungscache optimiert den Befehls-
durchsatz und kann vor allem bei Multithreading-Applikationen zu
Leistungssteigerungen führen.
Multimedia-Applikationen können von speziell erweiterten Befehlssätzen wie SSE, SSE2, SSE3, MMX und der 3DNow!“-Technologie pro”
fitieren.
Die ”Cool’n’Quiet“-Technologie regelt Taktfrequenz und Spannung
dynamisch, sodass Wärmeabgabe und Lärmpegel minimal gehalten
werden.
Athlon Quad FX Plattform
Bei der im November 2006 angekündigten Quad FX 4x4“-Technologie (auch
”
bekannt unter dual socket, direct connect“ (DSDC)) handelt es sich um eine
”
eigens entwickelte Plattform, bestehend aus einem Zwei-Sockel-Motherboard
und einem neuem Chipsatz von NVIDIA [31].
Jedem der beiden Sockel ist ein Dualcore-Prozessor der Serie FX-70,
FX-72 oder FX-74 zugedacht [27]. Die Vorgängermodelle FX-60 und FX62 sind zur restlichen Hardwarekonfiguration nicht kompatibel. In Tabelle
2.1 werden die Hauptmerkmale der Athlon 64 FX“- und Athlon Quad
”
”
FX“-Technologie gegenübergestellt.
Kritik an der Zwei-Sockel-Variante, die aufgrund fehlender Unterstützung5 nur einer beschränkten Zielgruppe von Nutzen sei, dementiert AMD
[31]. Dieses Architekturdesign ermögliche ein einfaches Erweitern auf bis
zu acht Kerne, sobald Quadcore-Prozessoren verfügbar sind [7]. Während
Intel das DRAM-Interface am Northbridge-Chip belässt, soll der Speichercontroller bei AMDs modular konzipierten Quadcore-Prozessoren bereits in
der CPU selbst integriert werden [13].
5
Beispielsweise Microsoft Windows Vista Home und Premium unterstützen nur einen
Sockel. Lediglich die Vista Business, Enterprise und Ultimate Varianten ermöglichen den
Einsatz eines Doppelsockel. Die Lizenzen der Vollversionen kosten jedoch etwa 140 USDollar mehr [31].
KAPITEL 2. DAS POTENZIAL VON MULTICORE-SYSTEMEN
Athlon 64 FX
Merkmale
Taktfrequenz
Anzahl der Kerne
Sockel
Prozesstechnologie
Anzahl Transistoren
64 Bit BefehlssatzUnterstützung
32 Bit BefehlssatzUnterstützung
Hyper
Transport
Support
Integrierter
DDR
Speicher-Controller
Speichertyp
FX-62
2.8 GHz
2
Sockel AM2
227 MB
Quad FX Plattform
mit Dual Sockel Direct Connect
FX-70
FX-72
FX-74
2.6 GHz
2.8 GHz
3.0 GHz
4 (Dual-Sockel, Dual-Core)
Sockel F (1207 FX)
90nm, DSL SOI
227 MB per Prozessor,
454 MB per Plattform
Ja, AMD64 Technologie
Ja, x86 ISA
1 HT Technologie Link
3 HT Technologie Links per Prozessor
Ja, Dual-channel
Ungebufferter DDR2 Speicher
PC2 6400, PC2 5300, PC2 4200 or PC2 3200
HT: bis zu 8.0 GB/s @ 2.0 GHz
Prozessor-zuSystem-Bandbreite
Integrierte
Northbridge
On-Chip
Hochleistungs-Cache
3D- und Multimedia
Befehle
10
Speicher: max. 12.8 GB/s @ 800MHz per Dualcore
Insgesamt:
Insgesamt: 33.6 GB/s per Plattform
20.8 GB/s
Ja, 128 Bit Datenpfad @ CPU Kernfrequenz
L1: 256 KB
L1: 512 KB (256 KB per Prozessor)
L2: 2 MB
L2: 4 MB (2 MB per Prozessor)
3DNow! Professional Technologie, SSE/SSE2/SSE3
Tabelle 2.1: Übersicht aktueller Multicore Prozessoren von AMD [3].
Der geplante Quadcore-Prozessor Barcelona“ soll ein ähnliches Grund”
design aufweisen, verfüge je Kern allerdings über 512KB L2 Cache und
2MB gemeinsamen L3 Cache [33]. Ein neuer native Quadcore-Prozessor
sei für das dritte Quartal 2007 zu erwarten, wurde jedoch seitens AMD
nicht bestätigt [31].
AMD selbst gibt an, dass der Quad FX“-Prozessor nicht nur für reines
”
Spielen gedacht sei (und empfiehlt hierfür sogar das Intel Konkurrenzprodukt QX6700“), sondern sieht die Stärke des Prozessors vor allem im Multi”
tasking6 von intensiven Anwendungen wie beispielsweise Videokodieren und
-dekodieren zur selben Zeit [31].
6
so genanntes Megatasking“
”
KAPITEL 2. DAS POTENZIAL VON MULTICORE-SYSTEMEN
11
Intel
I think multi-core processors bring an opportunity
to compute in different ways that we’ve never had
before.
Vernon Turner, 2005 [55, S. 6].
Die Verbreitung von Multicore-Systemen im Desktopbereich wurde von
Intel Mitte 2005 auf mehr als 70% Dualcore im Jahr 2006 respektive über
90% Multicore 2007 geschätzt [55, S. 3].
Die den Multicore-Prozessoren zugrunde liegende Core 2 “-Architektur
”
vereint zwei oder mehr Kerne auf einem Sockel, die vom Betriebssystem als
eigenständige Prozessoren erkannt werden [40].
Intel selbst fördert nach eigenen Angaben die Verbreitung von MulticoreProzessoren durch Einschulungen und online Trainingsmaterial. Softwareentwickler profitieren von Profiling-Applikationen, die eine Anpassung bestehender oder neuer Applikationen an Multicore-Systeme unterstützen (z.B.
Thread Checker“ und Thread Profiler“) [55, S. 4–5].
”
”
Intel Core 2 Duo Extreme X6800
Folgende Eigenschaften werden vom Hersteller hervorgehoben [36]:
”Dual-Core Processing“: zwei eigenständige Prozessorkerne der selben Taktfrequenz sind auf einem Sockel platziert und verfügen gemeinsam über 4 MB L2 Cache und 1066 MHz FSB.
”Wide Dynamic Execution“: verbesserte Ausführungsgeschwindigkeit und -effizienz. Jeder Prozessorkern kann bis zu vier gleichzeitige
Anweisungen ausführen.
”Advanced Smart Cache“: dynamische Zuordnung von benötigtem
L2-Cache abhängig von der Arbeitslast (vergleiche Abbildung 2.4).
”Virtualization Technology (VT)“: eine Hardwareplattform kann
in mehrere virtuelle Plattformen unterteilt werden. Die Hardwareunterstützte Isolation von Computeraktivitäten auf verschiedene Bereiche erhöht die Produktivität.
”Smart Memory Access“: Anweisungen werden mit einem höheren
Durchsatz verarbeitet und somit die Pipeline besser ausgenutzt.
”Advanced Digital Media Boost“: Verdopplung des Durchsatzes
durch Verarbeitung eines 128 Bit SSE-Blocks per CPU-Takt statt Zerteilung in 64 Bit Blöcke (vergleiche Abbildung 2.5).
KAPITEL 2. DAS POTENZIAL VON MULTICORE-SYSTEMEN
12
Abbildung 2.4: Der Advanced Smart Cache“ stellt den beiden Kernen
”
je nach Notwendigkeit vorhandene Cache-Kapazität zur Verfügung. Quelle:
[38].
.
Abbildung 2.5: Der Advanced Digital Media Boost“ soll höhere
”
Durchsatzratzen bei Multimedia-Applikationen ermöglichen. Quelle: [38].
”Extended Memory 64 Technology (EMT64)“: erweiterter Zugriff auf virtuellen und physikalischen Speicher.
”Intelligent Power Capability“: Nicht benutzte Bereiche des Prozessors werden aus Gründen des effizienten Energieverbrauchs abgeschaltet (vergleiche Abbildung 2.6).
”Execute Disable Bit“:
Schadcode-Ausführungsprävention durch
Markierung von Speicherbereichen als ausführbar“ oder nicht aus”
”
führbar“.
KAPITEL 2. DAS POTENZIAL VON MULTICORE-SYSTEMEN
13
Abbildung 2.6: Die Intelligent Power Capability“-Technologie ver”
sucht den Energieverbrauch des Prozessors minimal zu halten. Quelle: [38].
”Thermal Solution for Boxed Processors“: dynamische Regelung
der Ventilatorgeschwindigkeit je nach CPU-Temperatur zur Minimierung des Lärmpegels.
Intel Core 2 Extreme QX6700
This quad-core desktop processor will be the ultimate
gaming machine and multimedia processing engine
for today’s growing list of threaded applications.
Intel Corporation, 2006 [54, S. 6].
Der 2005 erstmals vorgestellte Quadcore-Prozessor QX6700“ wurde auf
”
dem Intel Developer Forum im Frühling 2006 demonstriert und Ende 2006
ausgeliefert [32].
Er besteht aus zwei Core 2 Duo“-Einheiten derselben Taktfrequenz auf
”
einem Sockel ( quad-core processing“), die gemeinsam mit 8 MB L2-Cache
”
und 1066 MHz FSB ausgestattet sind (siehe Abbildung 2.7) [36]. Ein spezieller Digital Thermal Sensor“ (DTS) kümmert sich um effiziente Wärme”
kontrolle von Prozessor und Plattform, weshalb Systemventilatoren auf minimaler Geschwindigkeit gehalten werden können und der Prozessor selbst
unter starker Last nicht besonders heiß läuft [37] [15].
Tabelle 2.2 liefert einen groben Überblick über aktuelle7 Prozessoren der
Intel Core 2“-Familie. Abhängig von der laufenden Konfiguration über”
7
Stand März 2007
KAPITEL 2. DAS POTENZIAL VON MULTICORE-SYSTEMEN
14
Abbildung 2.7: Architektur des Intel Core 2 Extreme. Quelle: [58].
zeugt der QX6700-Prozessor mit einem Leistungsvorsprung von 10–15 Prozent, doch ist er im Moment vergleichsweise teuer [27]. In bestimmten Applikationen soll eine Leistungssteigerung von bis zu 80% möglich sein [32].
Processor No.
Architecture
L2 Cache
Clock Speed
FSB Speed
Other
Technologies
Core 2 Extreme
Quadcore
QX6700
Core 2 Quad
Quadcore
Q6600
Core 2 Extreme
Dualcore
X6800
65 nm
8M
2.66 GHz
1066 MHz
VT +/-,
Execute Disable Bit,
EIST
65 nm
8M
2.40 GHz
1066 MHz
VT +/-,
Execute Disable Bit,
EIST
65 nm
4M
2.93 GHz
1066 MHz
VT +/-,
Execute Disable Bit,
EIST
Tabelle 2.2: Überblick aktueller Intel Multicore-Prozessoren [39].
Zahlreiche Benchmarks versuchen zu beweisen, dass Spiele von Multicore-Prozessoren profitieren können. Wurde die Multicore-Technologie bei
der Implementierung nicht berücksichtigt, kann nicht die komplette zur
Verfügung stehende Rechenleistung genutzt werden (vergleiche Testergebnisse in den Abbildung 2.8). Hinzu kommt, dass bei hoher Auflösung die
Leistung von der Grafikkarte abhängig ist. Bei geringerer Auflösung kann
der Einfluss des Grafikprozessors minimiert werden [15].
KAPITEL 2. DAS POTENZIAL VON MULTICORE-SYSTEMEN
Doom 3 (1204 x 768)
Doom 3 (1600 x 1200)
106,3
Pentium D 820
71,8
130,8
Athlon 64 FX-62
72,9
Core 2 Duo E6700
72,8
180,4
Core 2 Extreme
X6800
Core 2 Extreme
QX6700
184,3
188,7
0,0
50,0
100,0
fps
200,0
72,8
75,0
0,0 10,0 20,0 30,0 40,0 50,0 60,0 70,0 80,0
fps
HL 2 Lost Coast (1204 x 768)
HL 2 Lost Coast (1600 x 1200)
76,6
Pentium D 820
138,5
Athlon 64 FX-62
100,8
Core 2 Duo E6700
106,9
184,8
183,5
50,0
100,0
150,0
fps
76,2
Core 2 Extreme
X6800
Core 2 Extreme
QX6700
192,2
0,0
15
200,0
250.0
107,4
112,9
0,0
20,0
40,0
60,0
fps
80,0
100,0
120,0
Abbildung 2.8: Aktuelle Spiele im Test: Was Intels Quadcore-Prozessor
(roter Balken) tatsächlich an Leistung mit sich bringt. Quelle: [45].
Zusammenfassung
Single-Core ist out und Multi-Core ist in [...].
Arnt Kugler, 2007 [43].
Vergleicht man die Multicore-Strategien von AMD und Intel, so wird
deutlich, dass die jeweilige Herangehensweise8 , zusätzliche CPU-Kerne zur
Steigerung der Leistung zu nutzen, auf das grundsätzlich unterschiedliche
Ausgangsmaterial der zugrunde liegenden Prozessoren (AMD Opteron beziehungsweise Intel Pentium 4) zurückzuführen ist. Während AMD mit
schnellen Bussen und direkter Speicheranbindung arbeitet, punktet Intel
mit einem großen Cache direkt am Chip [46].
Das Zeitalter der Singlecore-Prozessoren geht dem Ende zu, doch ist
noch nicht absehbar, welche der vorgestellten Technologien sich in Zukunft
durchsetzen wird [43]. Die bisherige Preis-Leistungs-Entwicklung kann Abbildung 2.9 entnommen werden. Während sich die aktuell neuesten9 Prozessoren im Preisbereich von über 1000 US-Dollar bewegen, ist der Preis von
8
Während AMD einen modularen Lego Approach“ versucht, setzt Intel auf FSB”
Design [46].
9
Stand März 2007.
KAPITEL 2. DAS POTENZIAL VON MULTICORE-SYSTEMEN
16
Price-Performance Curve - Mar 23 2007
$ 1200.00
EE955
FX-74
EE965
FX-72
FX-60
$1000.00
EX6800
QX6700
$800.00
FX-70
FX-62
$600.00
D840
D960
$400.00
D830
4400+
D950
5600+ E6600
4800+
D940
$200.00
D805
D820
Intel
AMD
Expon. (Intel)
Expon. (AMD)
5000+
E6400
D930
3800+ 4200+
$
1. 00000
E6700
6000+
1.20000
1.40000
4600+
1.60000
1.80000
2.00000
2.20000
2.40000
Abbildung 2.9: Entwicklung von Preis und Leistung aktueller AMD
und Intel Multicore-Prozessoren. Bild: [53].
älteren Modellen stark gesunken. Eine ähnliche Marktwertentwicklung ist
auch in Zukunft zu erwarten.
2.2
Einsatzgebiete in Computerspielen
Multi-core processors will dramatically
improve the performance of games.
Martin Reynolds, 2005 [55, S. 7].
Spiele-Konsolen der nächsten Generation sind Multicore-Systeme [15].
Zusätzliche Rechenleistung durch Multicore-Prozessoren ist auch bei Computerspielern willkommen. Mit mehr als einem Kern sind genauere Berechnungen von Grafik, Physik, Künstlicher Intelligenz sowie realistischere
Soundeffekte zur gleichen Zeit möglich [21].
Auch die in Computerspielen eingesetzte, rechenintensive 3D-Animation
kann durch parallele Rechenanweisungen signifikant verbessert werden [18].
Es ist daher nicht verwunderlich, dass unter den Desktop-Computern so
genannte Gaming PCs“ zu möglichen Einsatzgebieten von Multicore-Pro”
zessoren gezählt werden [28].
Intel selbst sieht einen möglichen Einsatzbereich der vier HardwareThreads des QX6700“ (für Details siehe Abschnitt 2.1) ebenfalls in der
”
KAPITEL 2. DAS POTENZIAL VON MULTICORE-SYSTEMEN
17
Verbesserung von Computerspielen. Ein rechenintensiver Grafik-Thread
rendert übergangslos die nächste Spielsequenz, während andere Threads auf
Benutzereingaben reagieren können [54, S. 6–7].
Mark Rein, Vizepräsident von Epic Games, fasst die Auswirkung von
Multicore-Computern auf die Spiele-Entwicklung mit den Worten better,
”
faster, smoother games“ zusammen [55, S. 8].
Quadcore-Lösungen sind nicht nur für Spieler im herkömmlichen Sinne
gedacht, sondern haben durch die gebotenen Ressourcen vor allem eine neue
Generation von Power-Usern“ im Visier, die mehrere Hochleistungsanwen”
dungen (Spiele und Multimedia) parallel verwenden [27]. Eine prognostizierte Aufgabe von Multicore-Systemen sei es außerdem, den aktuellen
Overhead, der seitens des Betriebssystems (z.B. Windows) und Grafikdarstellungstreibern (z.B. Direct3D) entsteht, zu verbergen [21].
Um ein Multicore-System auszunutzen, muss die betriebene Software auf
mögliche Parallelisierungsstellen untersucht werden, um mehreren Threads
jeweils unterschiedliche Tasks (bei ausgeglichener Menge an Arbeit) zuweisen zu können [57]. Vorausgesetzt die Applikation bietet Ansatzpunkte zur
funktionalen Parallelität10 oder Zerlegung der Daten11 , ist prinzipiell ein
Leistungszuwachs durch Multithreading möglich [14].
Multithreading-Applikationen zu entwerfen verlangt zweierlei. Es ist
nicht nur wichtig, Threads zu erstellen und ihnen Daten zu liefern, sondern auch die komplette Applikation thread-safe zu gestalten. Zu Ersterem
liefert OpenMP (vergleiche Kapitel 3.1) gute Hilfestellung, sicheren Code zu
schreiben bleibt jedoch dem Programmierer in diesem Fall selbst überlassen [21].
Zu Computerspielen, die Quadcore-Prozessoren bereits oder zukünftig
unterstützen, zählen Supreme Commander (Gas Powered Games / THQ),
UT 2007 sowie Unreal Engine 3 basierte Spiele (Epic Games), Half-Life 2:
Episode 2 (Valve), Splinter Cell: Double Agent (Ubisoft) sowie die CryEngine 2 (Crytek) [15].
2.3
Bedeutung für zukünftige Spiele-Entwicklung
The critical element in multi-core
computing is the software.
Intel Corporation, 2006 [54, S. 7].
Multicore-Systeme können das Spielerlebnis neu definieren. Spiele müssten in Zukunft nicht mehr zentral auf Spiele-Servern gelagert werden, son10
Aufteilung der Rechenarbeit in unabhängige Teilaufgaben möglich
Teilaufgaben mit Datenabhängigkeiten in Datenbereiche unterteilbar, bewirkt Aufteilung des zu bearbeitenden Datensatzes
11
KAPITEL 2. DAS POTENZIAL VON MULTICORE-SYSTEMEN
18
dern könnten über das Internet verteilt werden, weil die Computer der
nächsten Generation leistungsstark genug sind, diese selbst zu hosten [40].
Die prognostizierte Tera Era“ 12 , wo Teraflops und Terabytes im Compu”
ter-Alltag Gang und Gebe sind, wirft durch die Entwicklung der MulticoreTechnologie die grundsätzliche Problematik auf, dass paralleles Denken um
ein Vielfaches schwieriger ist, als sich auf eine Sache zu konzentrieren [54, S.
6–7]. In Kombination mit einer nicht deterministischen Ausführungsreihenfolge, ergibt sich eine Komplexität, welche die heutige Anwendungsentwicklung zum Teil noch überfordert [60].
Bereits im Sommer 2006 war klar, dass Standards und Hilfe-Applikationen notwendig sind, um die Anwendungsentwicklung für Multicore-Prozessoren zu unterstützen [12]. Im Herbst desselben Jahres wurde wiederholt darauf hingewiesen, dass die Entwicklung von entsprechender Hardware allein
nicht ausreiche, um von der Multicore-Technologie Gebrauch machen zu
können [13].
Immer noch ist der Bedarf an aussagekräftigen Benchmark-Applikationen zur Messung der tatsächlichen Performance-Gewinne sowie zur Identifikation von potenziellen Engpässen ( bottlenecks“) bei der Portierung be”
stehender Implementierungen auf Multicore-Systeme gegeben [17]. Voraussichtlich Mitte 2007 soll das Embedded Microprocessor Benchmark Consortium (EMBC) eine Standardsuite veröffentlichen, um die Leistung von
Multicore-Prozessoren effektiv messen zu können [48].
Nachdem neue Prozessoren für Desktop-Computer und Spielekonsolen
bereits Multithreading-Technologien mit einbeziehen werden, beginnen Engine-Entwickler zum Teil dem Multicore-Trend zu folgen. So soll die Cry”
Engine 2“ die Module Animation, Grafik, Physik et cetera entsprechend der
zugrunde liegenden Threadanzahl skalieren können [59].
Von der Unreal 3 Engine“ werden ebenfalls signifikante Leistungszu”
wachse auf Multicore-Plattformen erwartet [21]. Epic Games“ will den
”
Einsatz von Threading auf Funktionen wie Audio und Physik konzentrieren (je ein Thread pro Bereich). Die Game-Loop soll weiterhin sequentiell
ablaufen, weil hierfür nicht mehr Rechenleistung notwendig sei, als ein einzelner Kern zur Verfügung stellen kann. Man verspricht sich asynchrones
Laden von Levelinformationen im Hintergrund, wodurch sich die Wartezeit
bei Ladebildschirmen verringert. In Zukunft könnten Multicore-Systeme
darüber hinaus einen peer-to-peer Videochat ermöglichen, der in einem Teilbereich der Spielanzeige gleichzeitig mit der verbleibenden Spieldarstellung
angezeigt wird. Spieler sollen auf diese Weise verschiedene Dinge parallel
erleben können [55, S. 8].
Entwicklungen wie Intels experimenteller 80-core processor“ könnten,
”
bei vergleichsweise geringem Energieverbrauch13 des Prozessors, Videospiele
12
Mehr Informationen unter http://www.intel.com/technology/techresearch/terascale
Um nur so viele Kerne einzusetzen, wie es die aktuelle Arbeitslast erfordert, sucht
Intel aktuell nach Methoden, die Kerne unabhängig voneinander in Betrieb zu nehmen
13
KAPITEL 2. DAS POTENZIAL VON MULTICORE-SYSTEMEN
19
in Filmqualität ermöglichen. Die zugrunde liegende Architektur müsste adaptiert werden, um eine solche Anzahl an Prozessoren beispielsweise durch
Zuteilung parallel auszuführender Aufgaben mit Rechenarbeit versorgen zu
können [25]. Das sodann notwendige Softwaredesign wäre von hoher Komplexität und die Programmierer müssten durch entsprechende Standards
und neuartige Entwicklungsumgebungen bei der Umsetzung unterstützt werden [29]. Darüber hinaus fehlt zum jetzigen Zeitpunkt die notwendige Unterstützung seitens des Betriebssystems, um die theoretisch bereitgestellte
Rechenleistung von 80 Kernen auch tatsächlich nutzbar14 zu machen [42].
We lack algorithms, languages,
compilers and expertise.
Jim Larus, 2007 [49].
oder abzuschalten [50].
14
Es handelt sich um ein Forschungsprojekt, welches nicht auf dem Markt verfügbar
sein wird [50].
Kapitel 3
Vorstellung der Konzepte
Nach einer Einführung in OpenMP, werden drei Spiele-Projekte vorgestellt
und auf Parallelisierungsmöglichkeiten überprüft. Es handelt sich hierbei
um eine Component Based Game Engine (CBGE) sowie ein für das SpieleGenre repräsentatives 1st person shooter game“, namentlich Cube, und zwei
”
Physikdemo-Applikationen der ODE-Engine, Crash Test und Space Stress
Test.
Users are no longer satisfied with applications that
can perform only one task at a time.
Cameron Hughes, 1997 [35].
3.1
OpenMP
Es handelt sich bei OpenMP um keine eigene Programmiersprache, sondern
ein Modell, serielle Algorithmen parallel umzusetzen [16, S. xii]. Bestehend
aus einer Reihe von Compiler-Anweisungen ( Pragmas“), Bibliotheksfunk”
tionen und Umgebungsvariablen in C++ und Fortran, können die Konstrukte nachträglich, inkrementell in bestehende Applikationen eingefügt
werden [10, S. 595, 598].
OpenMP stellt Mechanismen zu Thread-Management, -Synchronisation
sowie -Kommunikation bereit und unterstützt damit bei der Implementierung des allgemeinen Ablaufes eines parallelen Programms [47, S. 217]:
Erstellung und Beendigung von Threads
Ein Master-Thread erstellt eine gewisse Anzahl an Worker-Threads,
die vorhandene Tasks parallel abarbeiten [16, S. 25]. Weil die Anzahl der arbeitenden Threads über die Programmlaufzeit variabel sein
kann, ist es notwendig, Worker-Threads am Ende der parallelen Abarbeitung bis zur nächsten Beanspruchung zu einem einzelnen MasterThread zu vereinen (vergleiche Fork&Join-Pattern“ in [10, S. 167]).
”
20
KAPITEL 3. VORSTELLUNG DER KONZEPTE
21
Kommunikation zwischen den Threads und
Zugriff auf gemeinsame Daten
Werden Datenstrukturen von mehreren Tasks gleichzeitig verwendet
und von zumindest einem Task verändert, ist es notwendig, den Datenzugriff explizit zu steuern (vergleiche Shared Data Pattern“ in [10, S.
”
173]). Der entstehende Overhead kann die Skalierbarkeit des Quellcodes maßgeblich einschränken [16, S. 171].
Ein möglicher Ansatzpunkt für eine schrittweise Parallelisierung stellen
umfangreiche Schleifenkonstrukte dar [16, S. 41–92]. Das so genannte Loop
”
Parallelism Pattern“ dient in diesem Fall dazu, das Programm durch Aufteilung der Durchläufe von rechenintensiven Schleifen zu optimieren.
Um eine ausgewogene Lastverteilung zwischen den Threads muss sich der
Programmierer mittels entsprechender OpenMP-Parameter selbst bemühen
[10, S. 122].
Da eine unüberlegte Verwendung von OpenMP-Anweisungen fatale Folgen haben kann (Absturz oder Stillstand der Applikation), sollte das betroffene Codestück vor der Implementierung auf mögliche Gefahrenpotenziale
untersucht werden, die das Programm an einer korrekten Ausführung hindern könnten.
3.2
Projekt Techdemo
Das unter dem Namen Techdemo geführte Projekt basiert auf den Prinzipien
des Component Based Object Management (CBOM) und wurde von sechs
Studentinnen und Studenten im Wintersemester 2006/2007 im Rahmen des
Unterrichtsgegenstandes Gamedevelopment II“ umgesetzt.
”
Das Komponentensystem CBOM wurde um notwendige Darstellungskomponenten für die 3D-Engine OGRE1 und Physik-Komponenten zur Anbindung an die freie Physik-Engine ODE2 zur so genannten CBGE (Component Based Game Engine) erweitert. Ziel der Techdemo war es nicht, ein
vollständiges Spiel zu implementieren, sondern ein proof of concept einer
komponentenbasierten Game-Engine zu liefern. Durch die modulare Implementierung als Komponentensystem gestalteten sich Änderungen am Design
einfacher. Darüber hinaus war eine Trennung von Programmierdetails und
konkretem Inhalt möglich. Kapselung von Objekteigenschaften und Kombination verschiedener Eigenschaften in unterschiedlichen Objekten sorgten
für große Flexibilität.
In zwei voneinander getrennten Räumen, die durch einen Korridor miteinander verbunden sind, werden zwei Physikdemonstrationen visualisiert
(siehe Abbildungen 3.1 und 3.2).
1
2
Open-source Graphics Rendering Engine - http://www.ogre3d.org
Open Dynamics Engine - http://www.ode.org
KAPITEL 3. VORSTELLUNG DER KONZEPTE
Abbildung 3.1: Benutzer kann Bomben auf Gegenstände werfen.
Abbildung 3.2: Ein Gaswürfel im Ambiente eines Zen-Gartens.
22
KAPITEL 3. VORSTELLUNG DER KONZEPTE
23
Per Mausklick können im ersten Raum Bomben geworfen werden, die
nahe liegende Gegenstände bei der Explosion beeinflussen. Durch Betreten
des Zwischenganges oder Modifikation der zugehörigen Konfigurationsdatei
ogrehead.xml, gelangt man in einen zweiten Raum. Dort kann mit linker
und rechter Maustaste ein halbdurchsichtiger Gaswürfel aufgeblasen und
ausgelassen werden.
Identifikation von notwendigen Parallelisierungen
Um mögliche Engpässe in der vorliegenden Applikation zu finden, wurde das
Programm AMD CodeAnalyst eingesetzt. Eine allgemeine Analyse zeigte,
dass die Darstellung der Grafik durch das Einbinden mehrerer Grafikobjekte
rechenintensiv war. Handelte es sich beim zugrunde liegenden System um
ein Multicore-System, so war bereits ohne Eingriff von OpenMP ein minimaler Performance-Gewinn messbar.
Das erstellte Profil zeigte, dass die geringe Lastverteilung vorhanden war
(siehe Abbildung 3.3). Dies war auf die Nutzung von externen Bibliotheken
wie OGRE und ODE zurückzuführen, während die Grundapplikation selbst
nicht die vorhandene Leistung der verfügbaren Kerne nutzte. Der Rest der
Applikation skalierte, sofern nicht auf erwähnte Methoden zurückgegriffen
wurde, nicht mit der Anzahl zugrunde liegender Prozessorkerne.
Der erste Benchmark des Projektes in Kalenderwoche 49/06 ergab, dass
die gedachte Trennung von Grafik- und Physikberechnungen nicht vorstellbar war, weil die Synchronität bei Positionsdarstellung der Objekte notwendig war.
Abbildung 3.3: Time Based Profile: Überblick der verbrauchten Ressourcen im Profil von CodeAnalyst. Die Applikation ohne Parallelisierungseingriffe wurde 40 Sekunden lang ausgeführt. Während die Techdemo
selbst hauptsächlich auf einem Kern läuft, werden für OGRE und ODEBibliotheksaufrufe in geringem Ausmaß beide Kerne verwendet.
KAPITEL 3. VORSTELLUNG DER KONZEPTE
24
Am rechenintensivsten gestalteten sich die Physikberechnungen der Techdemo. Versuchsweise könnte das Updateintervall der Physik verringert werden, um auch bei zahlreichen Objekten in der Szenerie eine adäquate Framerate erzielen zu können. Ansatzpunkte zur exemplarischen Parallelisierung
wären in der Grundapplikation gegeben, um beispielsweise GrafikupdateAufrufe für verschiedenste Objekttypen parallel ausführen zu lassen.
Folgende Stellen wurden als mögliche Ansatzpunkte zur Optimierung
festgehalten:
ObjectManager::broadCastMessage
OdeManager::collide
OdeManager::sendCollisionMessage
Abschätzen von Gefahrenpotenzialen
Welche Überlegungen zu den gewählten Bereichen im Vorfeld getätigt wurden, soll an dieser Stelle vorgebracht werden.
ObjectManager::broadCastMessage
Begutachtet wird ein Schleifenkonstrukt der Methode (siehe Listing 3.1, zu
finden in der Quelldatei ObjectManager.cpp).
Listing 3.1: broadCastMessage im Original
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void O b j e c t M a n a g e r :: b r o a d c a s t M e s s a g e
( M e s s a g e I d T y p e mid , M e s s a g e B o d y m e s s a g e B o d y ){
/ * [ . . . ] */
while ( oIt != r e c i p i e n t s . end ()) {
O b j e c t I d T y p e oid = oIt - > first ;
C o m p I d V e c t o r f a m i l y I d s = oIt - > second ;
for ( signed int i =0; i < f a m i l y I d s. size (); i ++) {
C o m p o n e n t * comp = q u e r y C o m p o n e n t ( oid , f a m i l y I d s[ i ]);
comp - > h a n d l e M e s s a g e ( mid , m e s s a g e B o d y );
}
oIt ++;
}
}
Zunächst musste die while-Schleife (siehe Listing 3.1, Codezeile 4) für
eine eventuelle Parallelisierung in ein for -Konstrukt umgewandelt werden.
Da die Variable recipients eine Methode size() zur Verfügung stellte,
waren alle Parameter für den neuen Schleifenkopf vorhanden und eine Umwandlung konnte einfach erfolgen.
KAPITEL 3. VORSTELLUNG DER KONZEPTE
1
2
3
4
5
6
7
25
Listing 3.2: broadCastMessage modifiziert
void O b j e c t M a n a g e r :: b r o a d c a s t M e s s a g e
( M e s s a g e I d T y p e mid , M e s s a g e B o d y m e s s a g e B o d y ){
/ * [ . . . ] */
for ( signed int j = 0; j < r e c i p i e n t s . size (); j ++) {
/ * [ . . . ] */
}
}
Um abschätzen zu können, ob eine korrekte Parallelisierung mit einer
Leistungssteigerung verbunden war, wurde die neu geformte Schleife (Listing
3.2, Codezeile 4) mit CodeAnalyst analysiert. Die Auswertung zeigte keine
Verschlechterung der Performance3 .
Ein tieferer Blick in das erstellte Profil ergab, dass die Methoden query
Component und handleMessage (vergleiche Listing 3.1, Codezeile 9 und 10)
bereits geringfügig auf die Prozessorkerne verteilt liefen (z.B. über Aufrufe
zur Physikbibliothek ODE). Hier war Vorsicht geboten.
OdeManager::collide
Zur Diskussion steht das Codestück in Listing 3.3 (zu finden in der Quelldatei Ode-Manager.cpp).
Besonderes Augenmerk war auf die externen Methodenaufrufe der ODEBibliothek dJointCreateContact und dJointAttach (siehe Codereferenz
3.3, Zeile 14 und 18) zu richten, die voraussichtlich nur von einem Thread
zur gleichen Zeit ausgeführt werden sollten. Da gerade diese beiden Stellen jedoch die zeitintensivsten Berechnungen in dieser Funktion darstellen,
schien eine Parallelisierung auf den ersten Blick nicht sinnvoll.
OdeManager::sendCollisionMessage
Ein möglicher Parallelisierungsansatz wäre, die Erstellung der Vektoren (vergleiche Listing 3.4, Zeile 11–12) und der zugehörigen OdePhysic-Komponenten (siehe Listing 3.4, Codezeile 2–5 und 6–9) mittels einer parallelen Region
zeitgleich auszuführen.
Da auf unterschiedliche Variablen zugegriffen wurde, konnte eine Datenabhängigkeit ausgeschlossen werden. Um herauszufinden ob sich die Methodenaufrufe für eine parallele Ausführung eigneten, musste wie zuvor bei
ObjectManager::broadCastMessage, noch eine Ebene tiefer geforscht werden. In diesem Fall wurden als erster Parameter unterschiedliche Objekt-IDs
3
Die Summe an CPU-Takten kann durch im Hintergrund laufende Systemprozesse
beeinflusst werden. Abweichungen der Messergebnisse durch Cache oder RAM-Belegung
sollten durch mehrmalige hintereinander ausgeführte Testläufe minimiert werden.
KAPITEL 3. VORSTELLUNG DER KONZEPTE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
26
Listing 3.3: OdeManager::Collide im Original
for ( signed int i = 0; i < n u m _ c o n t a c t s ; i ++) {
d C o n t a c t cgeom ;
cgeom . s u r f a c e. mode = d C o n t a c t S l i p 1 |
dContactSlip2 | dContactSoftERP |
dContactSoftCFM | dContactApprox1;
cgeom . s u r f a c e. mu = d I n f i n i t y;
cgeom . s u r f a c e. slip1 = ( dReal )0.1;
cgeom . s u r f a c e. slip2 = ( dReal )0.1;
cgeom . s u r f a c e. s o f t _ e r p = ( dReal )0.2;
cgeom . s u r f a c e. s o f t _ c f m = ( dReal )0.0;
cgeom . s u r f a c e. b o u n c e _ v e l = ( dReal )0.0;
cgeom . geom = c o n t a c t _ a r r a y [ i ];
d J o i n t I D joint = d J o i n t C r e a t e C o n t a c t ( mWorld ,
m C o n t a c t Join ts , & cgeom ); / / ODE f u n k t i o n
d B o d y I D bodya = d G e o m G e t B o d y ( ga );
d B o d y I D bodyb = d G e o m G e t B o d y ( gb );
d J o i n t A t t a c h ( joint , bodya , bodyb );
}
übergeben (vergleiche Listing 3.4, Codezeile 4–5 und 8–9), somit sollte eine
parallele Ausführung gefahrlos möglich sein.
Listing 3.4: sendCollisionMessage im Original (Ausschnitt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if ( h a s G e o m C o m p o n e n t ( ga ) && h a s G e o m C o m p o n e n t ( gb )) {
OdePhysic* odePhysic1 =
( O d e P h y s i c *) g e t O b j e c t M a n a g e r () - >
q u e r y C o m p o n e n t ( m C o m p o n e n t s [ ga ] - >
g e t O b j e c t I d () , " P h y s i c C o m p o n e n t ");
OdePhysic* odePhysic2 =
( O d e P h y s i c *) g e t O b j e c t M a n a g e r () - >
q u e r y C o m p o n e n t ( m C o m p o n e n t s [ gb ] - >
g e t O b j e c t I d () , " P h y s i c C o m p o n e n t ");
V e c t o r 3 f v1 = odePhysic1 - > g e t L i n e a r V e l ();
V e c t o r 3 f v2 = odePhysic2 - > g e t L i n e a r V e l ();
v1 = v1 - v2 ;
float force = v1 . length ();
/ * [ . . . ] */
}
KAPITEL 3. VORSTELLUNG DER KONZEPTE
27
Abbildung 3.4: Einblick in eines der zahlreichen Levels der Cube-Engine.
3.3
Projekt Cube
Da sich während der Analyse des Projekts Techdemo weniger Parallelisierungsmöglichkeiten fanden als ursprünglich erwartet, wurde die frei verfügbare Cube-Engine als weiteres Demonstrationsprojekt gewählt.
Was ist Cube?
Cube ist eine 3D-Game-Engine für so genannte 1st person shooter“-Spiele
”
im Single- und Multiplayermodus. Sie ist im Quelltext frei verfügbar und
basiert auf OpenGL respektive der SDL [52].
Cube is a landscape-style engine that pretends to be an indoor
FPS engine, which combines very high precision dynamic occlusion culling with a form of geometric mipmapping on the whole
world for dynamic LOD for configurable fps & graphic detail on
most machines. (W. van Oortmerssen, About Cube“ [52].)
”
Alle wichtigen Informationen rund um die Engine sowie Quellcode und
Levelmaterial (siehe Abbildung 3.4) werden von den Entwicklern kostenfrei
bereitgestellt.
Bezug des Quellcodes und Installation
Unter http://www.cubeengine.com und http://www.sourceforge.net
können sowohl das Spiel als auch der Quellcode heruntergeladen werden.
KAPITEL 3. VORSTELLUNG DER KONZEPTE
28
Während für das Spiel selbst (unter Windows) ein Installationsassistent
zur Verfügung steht, muss zum Übersetzen des Quellcodes die vorhandene
Projektdatei in ein MS Visual Studio 2005“-Projekt umgewandelt werden.
”
Eventuell auftretende Linker-Fehler sind meist auf inkorrekte Projekteinstellungen zurückzuführen.
Analyse des Quellcodes
Gesetztes Ziel war es, die Engine auf funktionelle Weise zu parallelisieren.
Aus diesem Grund war es nicht zwingend notwendig, im Vorfeld ein Profil
mit CodeAnalyst zu erstellen, um Ansatzpunkte zur Parallelisierung zu finden. Es genügte, die Implementierung der Game-Loop (vergleiche Quellcode
in der Codereferenz 3.5) auf mögliche Stellen zur Aufteilung auf mehrere
Prozessorkerne zu untersuchen.
Eine Trennung der Rechenoperationen, welche Aktualisierung und Darstellung der Spielelemente betrafen, schien ohne Hindernisse umsetzbar zu
sein (vergleiche hierzu Listing 3.5, Trennung zwischen Codezeile 11 und 12
möglich).
Die Endlosschleife der Game-Loop (siehe Listing 3.5, Zeile 1) müsste in
zwei separate Schleifen aufgetrennt werden, damit jeweils eine der identifizierten Regionen umfasst werden kann. Selbst wenn mehr als zwei Prozessorkerne und somit eine höhere Anzahl an Threads zur Verfügung stehen
würden, könnten nur zwei Threads effektiv genutzt werden.
3.4
Projekt ODE-Demo
Die Open Dynamics Engine“ (ODE) besteht aus Physikbibliotheken zur
”
Simulation so genannter rigid body dynamics und ist frei verfügbar. Im
Quellcodeverzeichnis finden sich mehrere Physiksimulations-Beispiele. Zwei
Demos davon, test crash“ und test space stress“, sollten herangezogen
”
”
werden, um die zugrunde liegende Physik-Engine versuchsweise zu parallelisieren.
Crash Test
Die Physikdemo crash test“ besteht aus einem Wagen, einer Kanone und
”
mehreren quaderförmigen Boxen, die zu einer Wand gestapelt sind. Per
Tastatureingabe kann der Wagen beschleunigt und gelenkt werden, um mit
den Würfeln zu kollidieren (siehe Abbildung 3.5). Alternativ kann die Kanone per Benutzerinteraktion eine Kugel abfeuern und damit die Wand zum
Einsturz bringen, doch wurde diese Option bei den folgenden Überlegungen
nicht in Betracht gezogen.
Um vergleichbare Ergebnisse der Wagen-/Wand-Kollision bei den Performance-Tests zu erhalten, sollte das Gefährt beim Starten der Demo au-
KAPITEL 3. VORSTELLUNG DER KONZEPTE
Listing 3.5: Game-Loop im Original
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
for (;;) {
int millis = S D L _ G e t T i c k s ()* g a m e s p e e d /100;
if ( millis - lastmillis >200) l a s t m i l l i s = millis -200;
else if ( millis - lastmillis <1) l a s t m i l l i s = millis -1;
if ( millis - lastmillis < m i n m i l l i s)
S D L _ D e l a y( minmillis -( millis - l a s t m i l l i s ));
c l e a r d l i g h t s ();
u p d a t e w o r l d ( millis );
if (! d e m o p l a y b a c k ) s e r v e r s l i c e (( int ) time ( NULL ) , 0);
static float fps = ( 1 0 0 0 . 0f / c u r t i m e+ fps * 5 0 ) / 5 1 ;
c o m p u t e r a y t a b l e ( player1 - >o .x , player1 - > o . y );
r e a d d e p t h ( scr_w , scr_h );
S D L _ G L _ S w a p B u f f e r s ();
extern void u p d a t e v o l (); u p d a t e v o l ();
if ( f r a m e s i n m a p ++ <5) {
player1 - > yaw += 5;
g l _ d r a w f r a m e ( scr_w , scr_h , fps );
player1 - > yaw -= 5;
};
g l _ d r a w f r a m e ( scr_w , scr_h , fps );
S D L _ E v e n t event ;
int l a s t t y p e = 0 , l a s t b u t = 0;
while ( S D L _ P o l l E v e n t (& event )) {
switch ( event . type ) {
/ *[... event - h a n d l i n g ...]* /
};
}
}
Abbildung 3.5: Crash Test: Ein kleiner Wagen kollidiert mit einer Wand
aus Würfeln und verursacht die Zerstörung dieser Mauer.
29
KAPITEL 3. VORSTELLUNG DER KONZEPTE
30
Abbildung 3.6: Der Bottleneck der Crash Test Demo ist auf eine einzige Methode innerhalb der ODE-Engine eingrenzbar (Gesamtaufwand oben,
konkrete Methoden im Detail unten).
tomatisch mit einer festgelegten Geschwindigkeit losfahren. Auch sollte die
noch fehlende Implementierung eines Rendering-Umschalters beispielsweise
via Tastatureingabe umgesetzt werden, um die Framerate der Demo auch
bei minimal gehaltener Grafikausgabe (nur Darstellung des Hintergrundes)
testen zu können. Dies war wichtig, um den Einfluss der Grafikkarte zu
reduzieren.
Ein Profil der originalen Demo (vergleiche Abbildung 3.6) zeigte, dass die
Methode SOR_LCP in quickstep.cpp mit 12.41% die meiste CPU-Zeit verbrauchte (siehe Quellcode in Listing 3.6, irrelevante Codeabschnitte werden
mit einem grünen Platzhalter markiert).
Ein Blick in den Quelltext besagter Methode gab Aufschluss darüber,
dass die identifizierten, rechenintensiven Anweisungen (vergleiche Listing
3.6, Zeile 21, 24, 33–44) selbst nicht effizient auf mehrere Threads aufgeteilt
werden konnten.
Die Operationen werden jedoch in einer Schleife (siehe Listing 3.6, Zeile
16 bis 47) abgearbeitet, wodurch der gesamte Zeitaufwand durch OpenMPThreading verringert werden könnte. Die Variablen iMJ_ptr (Zeile 10),
J_ptr (Zeile 11) und order (Zeile 13) werden vor der Schleife angelegt und
müssten vor gleichzeitigem Zugriff durch entsprechende Synchronisationsmechanismen geschützt werden.
KAPITEL 3. VORSTELLUNG DER KONZEPTE
31
Abbildung 3.7: Space Stress Test: Per Tastatureingabe kann die Grafikausgabe der Demo (oben links; oben rechts mit AABB-Boxen) minimalisiert
werden, sodass nur noch der Hintergrund gezeichnet wird (unten).
Space Stress Test
Die Darstellung der Simulation ist rechenintensiv. Durch tastaturgesteuertes Ausblenden der gelben und roten Grafikobjekte im Vordergrund kann
dieser Overhead vermieden und eine höhere Aktualisierungsrate erreicht werden (siehe Abbildung 3.7). Dies spiegelte sich auch in der Verteilung der
Rechenlast wieder.
Bei vollem Rendering inklusive Darstellung der AABB4 -Geometrie lag
die Hauptarbeit der Demo in Grafikoperationen des Grafikkartentreibers
(vergleiche Abbildung 3.8, oben). Wurde die Darstellung auf den Hintergrund beschränkt, verlagerte sich die Arbeitslast in den Physikbereich (siehe
Abbildung 3.8, unten).
Die Detailansicht des Analyse-Profils (siehe Abbildung 3.9) gab Aufschluss darüber, dass hauptsächlich in den beiden Methoden collideAABBs
und Block::Collide (beide zu finden in collision_quadreespace.cpp)
CPU-Zeit verbraucht wird. Block::Collide (vergleiche Listing 3.7) leitet
den Aufruf über eine weitere Collide-Methode (Listing 3.7, Zeile 10: die auf4
AABB steht für Axis Aligned Bounding Boxes und stellt eine in der Computergrafik
übliche, einfache Methode zur Kollisionserkennung dar.
KAPITEL 3. VORSTELLUNG DER KONZEPTE
32
Abbildung 3.8: Space Stress Test. Oben: Hauptarbeit bei vollem Rendering (vergleiche Abbildung 3.7, oben) im Grafikbereich. Unten: Rechenarbeit
in Physikkalkulationen bei minimalisiertem Rendering (vergleiche Abbildung
3.7, unten).
Abbildung 3.9: Detailansicht der Space Stress Demo: Der Schwerpunkt liegt auf zwei Methoden zur Kollisionserkennung und -behandlung
(collideAABBs und Block::Collide)
gerufene Instanz besitzt zwei Parameter mehr als die aktuelle betrachtete
Methode) an die rekursive Methode zur Kollisionserkennung collideAABBs
weiter. Eine Parallelisierung wäre nicht in dieser Einheit, sondern in der
übergeordneten Struktur am effizientesten. Es käme für eine Optimierung
demnach die Schleife (siehe Listing 3.7, Zeile 7–13) von Collide in Frage,
wo eine Rekursion erst anschließend für die child-Knoten erfolgt (vergleiche Listing 3.7, Zeile 14–22) und eventuelle Threads an dieser Stelle bereits
beendet wären.
KAPITEL 3. VORSTELLUNG DER KONZEPTE
33
Zunächst müsste die Liste der Geometrieobjekte g (erstellt in Listing 3.7,
Zeile 7) in eine passende Speicherform umgewandelt werden, um von unterschiedlichen Threads gleichzeitig auf unterschiedliche Indizes zugreifen
zu können. Da mit jedem Schleifendurchlauf weniger Subobjekte in der
durch g->next() veränderten, referenzierten Liste enthalten sind (siehe Listing 3.7, Zeile 12), müsste dies auch bei der Festlegung der Arbeitsmenge
pro Thread (durch die so genannten chunksize [16, S. 86]) berücksichtigt
werden.
KAPITEL 3. VORSTELLUNG DER KONZEPTE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
Listing 3.6: SOR LCP im Original
static void S O R _ L C P ( int m , int nb , d R e a l M u t a b l e P t r J ,
int * jb , dxBody * const * body , d R e a l P t r invI ,
d R e a l M u t a b l e P t r lambda , d R e a l M u t a b l e P t r fc ,
d R e a l M u t a b l e P t r b , d R e a l M u t a b l e P t r lo ,
d R e a l M u t a b l e P t r hi , d R e a l P t r cfm , int * findex ,
d x Q u i c k S t e p P a r a m e t e r s * qs )
{
const int n u m _ i t e r a t i o n s = qs - > n u m _ i t e r a t i o n s ;
/ * [ . . . ] */
d R e a l P t r i M J _ p t r = iMJ ;
d R e a l M u t a b l e P t r J_ptr = J ;
/ * [ . . . ] */
I n d e x E r r o r * order = ( I n d e x E r r o r *) alloca
( m * sizeof ( I n d e x E r r o r ));
/ * [ . . . ] */
for ( int i t e r a t i o n =0; i t e r a t i o n < n u m _ i t e r a t i o n s ;
i t e r a t i o n ++) {
/ * [ . . . ] */
for ( int i =0; i < m ; i ++) {
int index = order [ i ]. index ;
J_ptr = J + index *12;
i M J _ p t r = iMJ + index *12;
if ( findex [ index ] >= 0) {
hi [ index ] = dFabs ( hicopy [ index ]
* lambda [ findex [ index ]]);
lo [ index ] = - hi [ index ];
}
int b1 = jb [ index *2];
int b2 = jb [ index *2+1];
dReal delta = b [ index ] - lambda [ index ]* Ad [ index ];
d R e a l M u t a b l e P t r fc_ptr = fc + 6* b1 ;
/ * [ . . . ] */
dReal n e w _ l a m b d a = lambda [ index ] + delta ;
/ * [ . . . ] */
fc_ptr = fc + 6* b1 ;
fc_ptr [0] += delta * i M J _ p t r [0];
/ * [ . . . ] */
fc_ptr [5] += delta * i M J _ p t r [5];
if ( b2 >= 0) {
fc_ptr = fc + 6* b2 ;
fc_ptr [0] += delta * i M J _ p t r [6];
fc_ptr [1] += delta * i M J _ p t r [7];
/ * [ . . . ] */
fc_ptr [5] += delta * i M J _ p t r [11];
}
}
}
}
34
KAPITEL 3. VORSTELLUNG DER KONZEPTE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Listing 3.7: Collide im Original
void Block :: C o l l i d e( void * UserData ,
d N e a r C a l l b a c k * C a l l b a c k ){
# ifdef D R A W B L O C K S
D r a w B l o c k( this );
# endif
/ / C o l l i d e the local list
dxGeom * g = First ;
while ( g ) {
if ( G E O M _ E N A B L E D ( g )) {
C o l l i d e(g , g - > next , UserData , C a l l b a c k );
}
g =g - > next ;
}
/ / R e c u r s e for c h i l d r e n
if ( C h i l d r e n ){
for ( int i = 0; i < SPLITS ; i ++){
if ( C h i l d r e n[ i ]. G e o m C o u n t <= 1){ / / Early out
c o n t i n u e;
}
C h i l d r e n[ i ]. C o l l i d e( UserData , C a l l b a c k );
}
}
}
35
Kapitel 4
Arbeitsumgebung und
Implementierung
Das vorliegende Kapitel beschreibt die Parallelisierung der Projekte, die in
Kapitel 3 vorgestellt wurden. Nach einer Beschreibung Benchmark-Applikationen und der Entwicklungsumgebung, werden die ausgewählten Konzepte
im Detail ausgearbeitet und die Ergebnisse in einem nachfolgenden Effizienztest dokumentiert.
Parallel computing is attractive because it offers
users the potential of higher performance.
John L. Hennessy, 2001 [16].
4.1
4.1.1
Einrichtung der Test– und Arbeitsumgebung
AMD CodeAnalyst
Um Applikationen auf einem AMD64-System für den Multicore-Einsatz zu
optimieren, stellt AMD mit dem CodeAnalyst ein wichtiges Analysetool zur
Verfügung (siehe Abbildung 4.1). Unter http://developer.amd.com kann
kostenfrei eine Kopie der Software bezogen werden. Darüber hinaus gibt es
in diesem Bereich der Homepage ein umfangreiches Repertoire an aktuellen
Artikeln rund um das Thema Multicore“ [8].
”
4.1.2
Fraps
Fraps ist im Spielebereich vor allem als Capturing-Tool von Spielsequenzen bekannt. In der aktuellen Version 2.8.2 (Dezember 2006) besteht die
Möglichkeit, die Framerate über einen bestimmten Zeitraum zu messen und
die Ergebnisse in einer .csv-Datei zur weiteren Verwendung abzuspeichern
(siehe Abbildung 4.2). Die Software ist sowohl in einer limitierten freien
36
KAPITEL 4. ARBEITSUMGEBUNG UND IMPLEMENTIERUNG
37
Abbildung 4.1: AMD CodeAnalyst ermöglicht durch verschiedene
Profiling-Techniken das gezielte Optimieren von Multicore-Anwendungen.
Abbildung 4.2: Fraps erlaubt unter anderem eine einfache Messung der
durchschnittlichen Framerate.
Variante als auch in einer kostenpflichtigen Vollversion verfügbar (siehe
http://www.fraps.com).
4.1.3
MS Visual Studio
Um OpenMP (vgl. Kapitel 3.1) in MS Visual Studio 2003 einsetzen zu
können, bedarf es des Intel C++ 9.1 Compilers (als Evaluierungsversion frei
verfügbar) und der Konvertierung des Projektes in ein Intel C++ Projekt“.
”
In Visual Studio 2005 kann die Unterstützung für OpenMP in den Projekteigenschaften aktiviert werden (siehe Abbildung 4.3). Das notwendige
Manifest wird durch das Inkludieren der Datei omp.h automatisch erstellt.
KAPITEL 4. ARBEITSUMGEBUNG UND IMPLEMENTIERUNG
38
Abbildung 4.3: Visual Studio 2005 unterstützt OpenMP bei entsprechenden Projekteinstellungen unter C/C++ / Language / OpenMP Support
automatisch.
4.2
Implementierung
Eine korrekte Parallelisierung einer sequentiellen Anwendung mit OpenMP
verlangt nicht nur sichere Verwendung entsprechender Compiler-Anweisungen, sondern auch Kapselung dieser Konstrukte, für den Fall, dass OpenMP nicht unterstützt wird. Im Folgenden finden sich die in Kapitel 3 vorgestellten Codeabschnitte mit entsprechenden Parallelisierungsanpassungen.
4.2.1
Projekt Techdemo
ObjectManager::broadcastMessage
Die umgestalteten Schleife (siehe Listing 4.1 in Codezeile 1, ursprünglich ein
while-Konstrukt), verursachte keinen zeitlichen Overhead der Ausführungszeit. Auf diese Weise wurde der möglicherweise zeitaufwändige Einsatz des
Iteratorobjekts in der Schleifensignatur umgangen. Um einen reibungslosen
Ablauf der Applikation zu gewährleisten, musste die Abfrage der Komponente (siehe Listing 4.1, Zeile 13) abgesichert werden. Mittels einer critical
section konnte gewährleistet werden, dass zu einem Zeitpunkt nur maximal
ein Thread in diesem Bereich tätig ist (vergleiche Abbildung A.3). Dies
war wichtig, um fehlerhafte Ergebnisse in der Berechnung und Abstürze des
Programmes zu vermeiden.
KAPITEL 4. ARBEITSUMGEBUNG UND IMPLEMENTIERUNG
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
39
Listing 4.1: broadCastMessage mit OpenMP
for ( signed int j = 0; j < r e c i p i e n t s. size (); j ++) {
O b j e c t I d T y p e oid = oIt - > first ;
C o m p I d V e c t o r f a m i l y I d s = oIt - > second ;
# ifdef _ O P E N M P
# pragma omp p a r a l l e l for
# endif
for ( signed int i =0; i < f a m i l y I d s . size (); i ++) {
C o m p o n e n t* comp ;
# ifdef _ O P E N M P
# pragma omp c r i t i c a l
{
# endif
comp = q u e r y C o m p o n e n t ( oid , f a m i l y I d s[ i ]);
# ifdef _ O P E N M P
}
# endif
comp - > h a n d l e M e s s a g e ( mid , m e s s a g e B o d y );
}
oIt ++;
}
OdeManager::collide
Wegen der notwendigen Absicherung der Funktionsaufrufe dJointCreate
Contact (Listing 3.3, Zeile 14–15) und dJointAttach (Listing 3.3, Zeile 18)
war der Overhead durch notwendiges Thread-Management größer als die
erreichbare Leistungsoptimierung. Aus diesem Grund wurde dieser Teilbereich der CBGE in der finalen Implementierung sequentiell belassen. Zum
besseren Verständnis sei in Listing 4.2 der semantisch korrekt parallelisierte
Quellcode dargestellt.
OdeManager::sendCollisionMessage
Zwar wäre eine Optimierung der vorliegenden Methode nicht zwingend notwendig, doch wurde eine bessere Verteilung der Prozessorlast angestrebt.
Die Komponenten v1 und v2 werden in zwei separaten Regionen verarbeitet
(siehe Listing 4.3, Zeile 6–15 und 16–25).
4.2.2
Projekt Cube
Um eine funktionelle Parallelisierung umzusetzen, wurde das OpenMP-Konzept der parallelen Regionen eingesetzt (vergleiche [16, S. 93–140]). Der um
entsprechende OpenMP-Konstrukte erweiterte Quellcode ist in Listing 4.4
ersichtlich. Eventuell auftretende Linker-Fehler beim erstmaligen Überset-
KAPITEL 4. ARBEITSUMGEBUNG UND IMPLEMENTIERUNG
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
40
Listing 4.2: OdeManager::Collide mit OpenMP
# ifdef _ O P E N M P
# pragma omp p a r a l l e l for
# endif
for ( u n s i g n e d int i = 0; i < n u m _ c o n t a c t s ; i ++) {
d C o n t a c t cgeom ;
cgeom . s u r f a c e. mode = d C o n t a c t S l i p 1 |
dContactSlip2 | dContactSoftERP |
dContactSoftCFM | dContactApprox1;
cgeom . s u r f a c e. mu = d I n f i n i t y;
cgeom . s u r f a c e. slip1 = ( dReal )0.1;
cgeom . s u r f a c e. slip2 = ( dReal )0.1;
cgeom . s u r f a c e. s o f t _ e r p = ( dReal )0.2;
cgeom . s u r f a c e. s o f t _ c f m = ( dReal )0.0;
cgeom . s u r f a c e. b o u n c e _ v e l = ( dReal )0.0;
cgeom . geom = c o n t a c t _ a r r a y [ i ];
d J o i n t I D joint ;
# ifdef _ O P E N M P
# pragma omp c r i t i c a l
{
# endif
joint = d J o i n t C r e a t e C o n t a c t ( mWorld ,
m C o n t a c t Jo ints , & cgeom );
# ifdef _ O P E N M P
}
# endif
d B o d y I D bodya = d G e o m G e t B o d y ( ga );
d B o d y I D bodyb = d G e o m G e t B o d y ( gb );
# ifdef _ O P E N M P
# pragma omp c r i t i c a l
{
# endif
d J o i n t A t t a c h ( joint , bodya , bodyb );
# ifdef _ O P E N M P
}
# endif
}
zen des Projektes mit OpenMP-Anweisungen können durch Inkludieren der
Datei vcomp.lib in den Projekteinstellungen vermieden werden.
Die Variable fps wurde implizit als shared 1 deklariert, damit beide
1
Variablen, die vor dem Beginn einer parallelen Region deklariert werden und nicht
durch andere scoping clauses“ am Beginn dieser Region abgedeckt sind, werden automa”
tisch als shared gehandhabt und sind daher allen Threads zugänglich (vergleiche hierzu
Abschnitt A.2).
KAPITEL 4. ARBEITSUMGEBUNG UND IMPLEMENTIERUNG
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
41
Listing 4.3: sendCollisionMessage mit OpenMP
V e c t o r 3 f v1 , v2 ;
# ifdef _ O P E N M P
# pragma omp p a r a l l e l
# pragma omp s e c t i o n s
{
# pragma omp s e c t i o n
{
# endif
O d e P h y s i c* o d e P h y s i c 1 =
( O d e P h y s i c *) g e t O b j e c t M a n a g e r () - >
q u e r y C o m p o n e n t ( m C o m p o n e n t s [ ga ] - >
g e t O b j e c t I d () , " P h y s i c C o m p o n e n t ");
v1 = odePhysic1 - > g e t L i n e a r V e l ();
# ifdef _ O P E N M P
}
# pragma omp s e c t i o n
{
# endif
O d e P h y s i c* o d e P h y s i c 2 =
( O d e P h y s i c *) g e t O b j e c t M a n a g e r () - >
q u e r y C o m p o n e n t ( m C o m p o n e n t s [ gb ] - >
g e t O b j e c t I d () , " P h y s i c C o m p o n e n t ");
v2 = odePhysic2 - > g e t L i n e a r V e l ();
# ifdef _ O P E N M P
}
}
# endif
Threads darauf zugreifen können. Eine Sicherung der Variablenzugriffe auf
diesen Speicherbereich war nicht erforderlich, obwohl ein Thread schreibend
darauf zugreift (siehe Listing 4.4, Zeile 18: schreibender Zugriff auf zur Aktualisierung der im Spiel eingeblendeten Framerate; Zeile 31: Abfrage der
Variable in einem anderen Thread). Eventuell ungültige, veraltete Werte
beim nicht vorherbestimmbaren zeitlichen Zugriff auf fps sind angesichts
der hohen Aktualisierungsrate vernachlässigbar.
Wie in Abschnitt 3.3 erwähnt, war die Eingrenzung der Thread-Anzahl
auf lediglich zwei arbeitende Threads wichtig, um die beiden Sektionen sinnvoll aufteilen zu können und bereits zu Beginn der parallelen Region nur maximal zwei der verfügbaren Threads des Systems zu benutzen. Dies wurde
durch den Parameter num_threads(2) erreicht (siehe Listing 4.4, Zeile 8).
KAPITEL 4. ARBEITSUMGEBUNG UND IMPLEMENTIERUNG
4.2.3
42
Projekt ODE-Demo
Bei genauer Inspektion des Quellcodes stellte sich heraus, dass für die Darstellung der Demos unter Windows ein eigener Render-Thread verwendet
wurde (siehe Codereferenz 4.5, Zeile 7–36), der einen eventuellen Performance-Gewinn durch OpenMP von vornherein eindämmte. Auch war die
Verteilung der Rechenlast auf vorhandene Prozessorkerne im zuvor erstellten
Profil auf dieses Threading zurückzuführen.
Um eine Optimierung mit OpenMP an den identifizierten Stellen vornehmen zu können (vergleiche Kapitel 3.4), musste der Render-Thread eliminiert werden. Hierzu wurden die ursprünglich von einem Windows-Thread
verwalteten, relevanten Funktionen an jener Stelle direkt aufgerufen, wo zuvor der Render-Thread initialisiert wurde (siehe Listing 4.6, Zeile 7–46).
Die Verarbeitung von Benutzereingaben musste entfernt werden (vergleiche
hierzu Listing 4.5, Zeile 16–24 und Listing 4.6, Zeile 38), weil das Programm
ohne Auslagerung in den Render-Thread sonst an diesem Punkt zum Stillstand kam, bis eine Nachricht auftrat (z.B. Bewegung des Mauszeigers).
Crash Test
Um die Demo vergleichsweise mit minimal gehaltenem Rendering (nur Darstellung des Hintergrundes) testen zu können, wurde eine Variable hinzugefügt und abhängig von deren Wert entschieden, ob der Vordergrund der
Demo gezeichnet werden soll. Die initiale Startgeschwindigkeit des Wagens
wurde verändert, sodass der Wagen bei jedem Start der Demo automatisch
mit einem vordefinierten Wert beschleunigte.
Listing 4.7 zeigt den Quelltext der ODE-Funktion SOR_LCP nach erfolgter
Optimierung durch OpenMP. Aufgrund der schreibenden Zugriffe auf die
Variablen J_ptr, iMJ_ptr und order (siehe Referenz 4.7, Codezeile 17–20),
war eine Markierung dieser Variablen als private 2 zur Sicherstellung der
Datenintegrität notwendig.
Problematisch erwies sich hierbei, dass es sich bei den drei Variablen um
Pointer handelte und eine Deklaration als private Variable zwar den Pointer
betraf, jedoch nicht den Speicherbereich, auf den der Pointer zeigte. Durch
Zuweisungen auf J_ptr und iMJ_ptr innerhalb der zu parallelisierenden
Schleife (vergleiche Listing 4.7, Codezeile 18 und 19), musste nur für den
Pointer order für jeden Thread am Beginn der parallelen Ausführung jeweils
der notwendige Speicherbereich allokiert werden (siehe Codereferenz 4.7,
Zeile 12).
Im Zuge späterer Effizienzanalysen sollte die durchschnittliche Framerate
über einen bestimmten Zeitraum verglichen werden. Um nicht die absolute,
sondern simulierte Zeit zu messen, müsste die Simulationsschleife und in wei2
private kennzeichnet eine Variable, die für jeden Thread in einem eigenen Speicherbereich vorliegt (vergleiche hierzu Abschnitt A.2).
KAPITEL 4. ARBEITSUMGEBUNG UND IMPLEMENTIERUNG
43
terer Folge das Rendering abgebrochen und die Applikation beendet werden.
Die hierfür notwendige Kommunikation zwischen der Demo-Applikation und
der Physik-Bibliothek konnte nicht realisiert werden, weil ein Zugriff auf Variablen außerhalb des aktuellen Kontexts nicht möglich war.
Space Stress Test
Vor der iterativen Abarbeitung der Geometrie-Objekte wurde deren Speicherform angepasst (siehe Listing 4.8, Zeile 4–10), um per Index auf bestimmte Elemente direkt zugreifen zu können. Außerdem wurde das whileKonstrukt in eine for -Schleife umgewandelt (siehe Listing 4.8, Zeile 14–
16). Aus Performance-Gründen war es nicht möglich, die Länge des Vektors
dynamisch nach Bedarf zu erweitern, sondern musste mit einer passenden
Größe initialisiert und anschließend mit entsprechenden Werten befüllt werden (siehe Listing 4.8, Zeile 4 und 7). Hierfür war eine globale Variable
count gedacht, in der die Größe des Vektors beim vorhergehenden Durchlauf noch gespeichert war.
Um zu testen, wie stark die Länge des Vektors zwischen den einzelnen
Durchläufen differiert, wurde der Wert der Variable am Ende der Schleife
auf der Konsole ausgegeben (vergleiche Codeabschnitt 4.8, Zeile 5–11). Zwar
war die Varianz akzeptabel, doch der Wert mit durchschnittlich 2.83 Elementen selbst zu gering, als dass eine effiziente Verteilung der Rechenarbeit
auf mehrere Threads tatsächlich möglich wäre.
KAPITEL 4. ARBEITSUMGEBUNG UND IMPLEMENTIERUNG
44
Antialiasing settings 2x
Anisotropic filtering 4x
Image settings
Quality
Color Profile
n/a
Vertical sync
Off
Force mipmaps
None
Conformant texture clamp On
Extension limit
Off
Hardware acceleration Multi-display performance
Trilinear optimization On
Anisotropic mip filter optimization On
Anisotropic sample optimization On
Gamma correct antialiasing On
Transparency antialiasing Multisampling
Triple buffering
On
Negative LOD bias
Allow
Threaded optimization Off
OpenGL error reporting On
Abbildung 4.4: Mittlere Qualität der Grafikdarstellung zur Messung bei
durchschnittlichen Qualitätseinstellungen.
4.3
Effizienztest
Ausgangspunkt für die Effizienzanalyse sind die originale, unveränderte Implementierung sowie die parallelisierte Applikation, welche jeweils mit dem
MS Visual Studio 2005 Compiler erstellt wurde. Ausgeführt werden die Versionen mit der Auflösung 1024x768 Pixel auf einem einzelnen Prozessorkern
oder mit beiden Kernen im Standardmodus auf folgendem Testsystem:
CPU: AMD 64 X2 Dual Core 4400+ 2.21 GHz
Speicher: 2x 1024 MB RAM
Betriebssystem: MS Windows XP
Grafik: NVIDIA GeForce 7800 GT, genauere Grafikkarteneinstellungen siehe Abbildungen 4.4 und 4.5.
Um Störfaktoren zu minimieren, wurden während der Testdurchläufe
mögliche andere Anwendungen geschlossen und die Internetverbindung getrennt. Die Ergebnisse repräsentieren lediglich Richtwerte, die zum Zeitpunkt der Evaluierung auf dem spezifischen Testsystem gemessen werden
konnten und dienen als Argumentationsgrundlage in Kapitel 5.
4.3.1
Projekt Techdemo
Nachdem gewünschte Parallelisierungs-Anweisungen in den Quelltext eingefügt wurden, ohne dass Probleme bei der Ausführung der Applikation
KAPITEL 4. ARBEITSUMGEBUNG UND IMPLEMENTIERUNG
45
Antialiasing settings Off
Anisotropic filtering Off
Image settings
Performance
Color Profile
n/a
Vertical sync
Off
Force mipmaps
None
Conformant texture clamp On
Extension limit
Off
Hardware acceleration Multi-display performance
Trilinear optimization On
Anisotropic mip filter optimization On
Anisotropic sample optimization Off
Gamma correct antialiasing On
Transparency antialiasing Multisampling
Triple buffering
On
Negative LOD bias
Allow
Threaded optimization Off
OpenGL error reporting On
Abbildung 4.5: Geringe Qualität der Grafikdarstellung zur Minimalisierung der GPU-Last.
auftraten, war nicht nur die Aufteilung der CPU-Takte interessant, welche
in bisher erstellten Profilen ersichtlich war (vergleiche Abschnitt 3.2, Subkategorie: Techdemo). Die Geschwindigkeit der Applikation spiegelte sich vor
allem in der Framerate wieder.
Getestet wurde folgender Grafikmodus bei mittlerer Grafikqualität (vergleiche Auflistung 4.4):
Framed Direct 3D:
[Direct3D9 Rendering Subsystem]
Allow NVPerfHUD=No
Anti aliasing=None
Floating-point mode=Fastest
Full Screen=No
Rendering Device=NVIDIA GeForce 7800 GT
VSync=No
Video Mode=1024 x 768 @ 32-bit colour
Tabelle 4.1 bietet einen Überblick der ausgeführten Performance-Checks.
Jeder Testvorgang (betitelt mit Test 1“ bis Test 4“ in Tabelle 4.1) bestand
”
”
aus vier separaten Durchläufen. Aufgelistet sind die durchschnittlich gemessene Framerate der Applikation in Raum 1 sowie Raum 2 unter intensiver
Benutzeraktivität3 über die Dauer von 40 Sekunden. Es wurde anschlie3
Raum 1: über Gegenstände laufen und Bomben werfen; Raum 2: im Raum bewegen
und Volumen des Gasballes verändern
KAPITEL 4. ARBEITSUMGEBUNG UND IMPLEMENTIERUNG
46
Singlecore-System, ohne OpenMP
(idle) Test 1 Test 2 Test 3 Test 4
Raum 1
83.7
36.3
46.1
48.3
57.2
Raum 2 120.8
97.3
97.4
106.9
111.2
Singlecore-System, mit OpenMP
(idle) Test 1 Test 2 Test 3
Raum 1
83.4
40.0
42.6
52.7
Raum 2 120.5
97.5
105.4
107.2
Test 4
72.2
113.2
Multicore-System, ohne OpenMP
(idle) Test 1 Test 2 Test 3
Raum 1 196.8
54.3
55.9
64.0
Raum 2 216.2
216.3
223.5
226.6
Test 4
82.6
264.5
Multicore, mit OpenMP
(idle) Test 1 Test 2 Test 3
199.7
63.4
63.7
65.3
218.9
253.3
259.9
263.4
Test 4
76.5
273.3
Raum 1
Raum 2
Tabelle 4.1: Performance-Analyse der Techdemo: die Liste zeigt die
durchschnittliche Framerate bei einer Ausführungszeit von exakt 40 Sekunden. Grafikkarteneinstellungen vergleiche Abbildung 4.4, Quellcode (Release) übersetzt mit MS VisualStudio 2005.
ßend pro Test ein Durchlauf ohne Benutzereingaben gestartet, welcher in
Tabelle 4.1 als (idle)“ gekennzeichnet ist.
”
4.3.2
Projekt Cube
Im Zuge der Performance-Tests und durch Rücksprache mit den Entwicklern4 stellte sich heraus, dass die Engine besonders GPU-lastig ist. Dadurch
konnte es bei Grafikkarteneinstellungen mit hoher Qualität (vergleiche Auflistung 4.4) selbst bei einem Multicore-System zu Einbußen der Framerate
kommen.
Um diese Auswirkung möglichst gering zu halten, wurde das Spiel mit
geringer Grafikqualität (Details siehe Auflistung 4.4) getestet. Es wurden
mehrere Durchgänge des Spiels gestartet und ein repräsentativer Durchschnittswert zum Vergleich herangezogen (siehe Tabelle 4.2).
4
Forum verfügbar unter http://www.cubeengine.com/forum.php4
KAPITEL 4. ARBEITSUMGEBUNG UND IMPLEMENTIERUNG
P
P
P
P
P
P
P
P
P
P
P
P
P
P
P
P
1
2
3
4
Single
247
250
281
-
Map 1
Multi (orig.)
222 (171)
262 (255)
260 (250)
-
Single
87
189
122
162
1
2
3
4
5
6
Single
280
242
254
218
–
–
Map 3
Multi (orig.)
325 (224)
200 (57)
274 (111)
160
–
–
Single
248
269
234
279
249
250
1
2
3
4
5
6
Single
218
190
277
275
191
273
Map 5
Multi (orig.)
156 (47)
106 (37)
254 (178)
255 (163)
100 (38)
252 (186)
47
Map 2
Multi (orig.)
42 (23)
42 (40)
25 (18)
27 (26) (36)
Map 4
Multi (orig.)
246 (93)
252 (144)
204 (58)
309 (189)
261 (87)
259 (94)
Gesamtspiel
Level / Single :: Multi (orig.)
Map 1:
259 :: 248 (225.30)
Map 2:
140 :: 33.75 (26.75)
Map 3:
248.5 :: 239.75 (107.00)
Map 4: 254.83 :: 255.17 (110.83)
Map 5:
237.3 :: 187.17 (108.17)
Tabelle 4.2: Vergleich der durchschnittlichen Performance auf einem Singlecore-System (Spalte Single“) einem und Dualcore-System (Spalte
”
Multi“) an gewählten Positionen im Spiel Cube“. Die Parallelisierung der
”
”
Game-Loop bewirkte eine deutlich höhere Framerate. Quellcode übersetzt
mit MS VisualStudio 2005.
Im Test wurden nach der Reihe folgende fünf verschiedene Levels geladen, die allesamt in der Standardinstallation von Cube enthalten sind:
Map1: templemount (zu finden unter ”more maps(2)“)
Map 2: island pre (zu finden unter ”more maps(3)“)
Map 3: fox (zu finden unter ”more maps(3)“)
Map 4: metl3
Map 5: douze
Im Anhang sind die gewählten Positionen per Screenshot dokumentiert,
sodass die Vergleichbarkeit der gewonnenen Messergebnisse zwischen den
KAPITEL 4. ARBEITSUMGEBUNG UND IMPLEMENTIERUNG
48
einzelnen Testdurchläufen im Spiel nachvollzogen werden kann. Trotz erfolgter OpenMP-Eingriffe konnte die Performance des Singlecore-Computers
nicht übertroffen werden. Doch es war auf dem Multicore-System im Durchschnitt eine deutlich bessere Framerate als bei der nicht-optimierten, singlethreaded Applikation messbar (Wert jeweils in Klammern dargestellt).
4.3.3
Projekt ODE-Demo: Crash Test
Getestet wurde die Applikation mit integrierter Startgeschwindigkeit des
Wagens von 2.0f für vier Sekunden Ausführungszeit. Die Länge des Tests
wurde aufgrund der durchschnittlichen Dauer bis zum Einstürzen der Wand
bestimmt, weil ein Abbruch der Simulation nach einer bestimmten Simulationszeit nicht möglich war (vergleiche Abschnitt 4.2.3).
Es wurden mehrere Durchgänge der Demo gestartet und der durchschnittlich gemessene Wert der Framerate zum Vergleich herangezogen (vergleiche Tabelle 4.3).
Um eine eventuelle Limitierung durch die GPU zu vermeiden, wurde eine
zweite Testserie mit verminderter Grafikausgabe (lediglich Darstellung des
Hintergrundes) durchgeführt. Sowohl die Entfernung des Render-Threads,
als auch Erweiterung des Quellcodes um OpenMP-Konstrukte wirkte sich
positiv auf die Framerate aus.
Crash Test Demo mit vollständiger Grafikausgabe
original (Render-Thread)
original ohne Render-Thread
mit OpenMP ohne Render-Thread
Singlecore
172.50
–
–
Dualcore
235.50
281.00
355.00
Crash Test Demo mit verminderter Grafikausgabe
original (Render-Thread)
original ohne Render-Thread
mit OpenMP ohne Render-Thread
Singlecore
270.00
–
–
Dualcore
465.50
460.00
534.00
Tabelle 4.3: Vergleich der durchschnittlich gemessenen Framerate
der Crash Test Demo (erste 4 Sekunden) auf einem Single- und DualcoreSystem. Haben Veränderungen im Quellcode keine Auswirkung auf die Messergebnisse, so sind diese Stellen mit einem –“ markiert.
”
KAPITEL 4. ARBEITSUMGEBUNG UND IMPLEMENTIERUNG
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
Listing 4.4: Game-Loop der Cube-Engine, optimiert mit OpenMP
int main ( int argc , char ** argv ){
/ * [ . . . ] */
log (" m a i n l o o p ");
int ignore = 5;
static float fps = 30.0 f ;
# ifdef _ O P E N M P
# pragma omp p a r a l l e l s e c t i o n s n u m _ t h r e a d s (2)
{
# pragma omp s e c t i o n
{
# endif
for (;;)
{
int millis = S D L _ G e t T i c k s ()* g a m e s p e e d /100;
if ( millis - lastmillis >200) l a s t m i l l i s = millis -200;
/ * [ . . . ] */
fps = ( 1 0 0 0 . 0f / c u r t i m e+ fps * 5 0 ) / 5 1 ;
c o m p u t e r a y t a b l e ( player1 - > o .x , player1 - > o . y );
# ifdef _ O P E N M P
}
}
# pragma omp s e c t i o n
{
for (;;)
{
# endif
r e a d d e p t h( scr_w , scr_h );
S D L _ G L _ S w a p B u f f e r s ();
/ * [ . . . ] */
g l _ d r a w f r a m e ( scr_w , scr_h , fps );
S D L _ E v e n t event ;
int l a s t t y p e = 0 , l a s t b u t = 0;
while ( S D L _ P o l l E v e n t (& event ))
{
/ * [ . . . ] */
};
}
# ifdef _ O P E N M P
} // s e c t i o n end
} // s e c t i o n s end
# endif
quit ();
return 1;
};
KAPITEL 4. ARBEITSUMGEBUNG UND IMPLEMENTIERUNG
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
50
Listing 4.5: Ausgangssituation des Renderings ODE-Demo (windows.cpp)
Irrelevante Codesegmente werden mit einem grünen Platzhalter dargestellt.
void d s P l a t f o r m S i m L o o p ( / * [ . . . ] */ ) {
/ * [ . . . ] */
/ / set r e n d e r e r g l o b a l s
r e n d e r e r _ d c = dc ; r e n d e r e r _ w i d t h = w i n d o w _ w i d t h ;
r e n d e r e r _ h e i g h t = w i n d o w _ h e i g h t ; r e n d e r e r _ f n = fn ;
/ / start the r e n d e r i n g thread
DWORD threadId , t h i r d P a r a m = 0;
HANDLE h T h r e a d;
hThread = CreateThread(
NULL , 0 , r e n d e r i n g Thr ead , & thirdParam , 0 , & t h r e a d I d );
if ( h T h r e a d == NULL )
d s E r r o r (" Could not create r e n d e r i n g thread ");
/ / start GUI m e s s a g e p r o c e s s i n g
MSG msg ;
while ( G e t M e s s a g e (& msg , main_window ,0 ,0)) {
if (! T r a n s l a t e A c c e l e r a t o r ( main_window ,
accelerators ,& msg )) {
T r a n s l a t e M e s s a g e (& msg );
D i s p a t c h M e s s a g e (& msg );
}
}
/ / t e r m i n a t e r e n d e r i n g thread
r e n d e r e r _ r u n = 0;
DWORD ret = W a i t F o r S i n g l e O b j e c t ( hThread , 2 0 0 0 ) ;
if ( ret == W A I T _ T I M E O U T )
d s W a r n i n g (" Could not kill r e n d e r i n g thread (1)");
DWORD e x i t c o d e =0;
if (!( G e t E x i t C o d e T h r e a d ( hThread ,& e x i t c o d e)
&& e x i t c o d e == 123))
d s W a r n i n g (" Could not kill r e n d e r i n g thread (2)");
C l o s e H a n d l e ( h T h r e a d );
/ / d e s t r o y window
D e s t r o y W i n d o w ( m a i n _ w i n d o w );
}
KAPITEL 4. ARBEITSUMGEBUNG UND IMPLEMENTIERUNG
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
Listing 4.6: Rendering ohne Thread in windows.cpp
void d s P l a t f o r m S i m L o o p ( / * [ . . . ] */ ) {
/ * [ . . . ] */
/ / set r e n d e r e r g l o b a l s
r e n d e r e r _ d c = dc ; r e n d e r e r _ w i d t h = w i n d o w _ w i d t h ;
r e n d e r e r _ h e i g h t = w i n d o w _ h e i g h t ; r e n d e r e r _ f n = fn ;
/ / create openGL c o n t e x t and make it c u r r e n t
HGLRC glc = w g l C r e a t e C o n t e x t ( r e n d e r e r _ d c );
if ( glc == NULL )
d s E r r o r (" could not create OpenGL c o n t e x t ");
if ( w g l M a k e C u r r e n t ( renderer_dc , glc ) != TRUE )
d s E r r o r (" could not make OpenGL c o n t e x t c u r r e n t ");
/ / test openGL c a p a b i l i t i e s
int m a x t s i z e =0;
g l G e t I n t e g e r v ( G L _ M A X _ T E X T U RE _S IZE ,& m a x t s i z e );
if ( m a x t s i z e < 128) d s W a r n i n g (" max t e x t u r e size too
small (% dx % d )" , maxtsize , m a x t s i z e );
d s S t a r t G r a p h i c s ( r e n d e r e r _wi dth , r e n d e r e r _h eig ht ,
r e n d e r e r _ f n );
if ( renderer_fn - > start ) renderer_fn - > start ();
while ( r e n d e r e r _ r u n ) {
/ / local copy of r e n d e r e r _ s s to help p r e v e n t races
int ss = r e n d e r e r _ s s ;
d s D r a w F r a m e ( r e n d e r e r_ width , r e n d e r e r _ hei ght ,
renderer_fn , r e n d e r e r _ p a u s e && ! ss );
if ( ss ) r e n d e r e r _ s s = 0;
/ / read keys out of ring buffer and
/ / feed them to the c o m m a n d f u n c t i o n
while ( k e y b u f f e r _ h e a d != k e y b u f f e r _ t a i l ) {
if ( renderer_fn - > c o m m a n d)
renderer_fn - > c o m m a n d ( k e y b u f f e r[ k e y b u f f e r _ t a i l ]);
k e y b u f f e r _ t a i l = ( k e y b u f f e r _ t a i l +1) & 15;
}
S w a p B u f f e r s ( r e n d e r e r _ d c );
// deleted message processing
}
if ( renderer_fn - > stop ) renderer_fn - > stop ();
d s S t o p G r a p h i c s ();
/ / delete openGL c o n t e x t
w g l M a k e C u r r e n t ( NULL , NULL );
w g l D e l e t e C o n t e x t ( glc );
/ / d e s t r o y window
D e s t r o y W i n d o w ( m a i n _ w i n d o w );
}
KAPITEL 4. ARBEITSUMGEBUNG UND IMPLEMENTIERUNG
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
52
Listing 4.7: Parallelisierung der Crash Test Demo
Irrelevante Codesegmente werden mit einem grünen Platzhalter dargestellt.
static void S O R _ L C P ( / * [ . . . ] */ ) {
/ * [ . . . ] */
d R e a l P t r i M J _ p t r = iMJ ;
d R e a l M u t a b l e P t r J_ptr = J ;
/ * [ . . . ] */
I n d e x E r r o r * order = ( I n d e x E r r o r *) alloca
( m * sizeof ( I n d e x E r r o r ));
/ * [ . . . ] */
# ifdef _ O P E N M P
# pragma omp p a r a l l e l p r i v a t e ( J_ptr , iMJ_ptr , order )
order = ( I n d e x E r r o r *) alloca ( m * sizeof ( I n d e x E r r o r ));
# pragma omp for
# endif
for ( int i t e r a t i o n =0; i t e r a t i o n < n u m _ i t e r a t i o n s ;
i t e r a t i o n ++) {
/ * [ . . . ] */
J_ptr = J + index *12;
i M J _ p t r = iMJ + index *12;
/ * [ . . . ] */
}
}
Listing 4.8: Collide im Zuge der Optimierung. Für eine effiziente Parallelisierung waren nicht ausreichend Objekte in der Liste enthalten. Irrelevante
Codesegmente werden mit einem grünen Platzhalter dargestellt.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Block :: C o l l i d e( void * UserData ,
d N e a r C a l l b a c k * C a l l b a c k ){
/ * [ . . . ] */
std :: vector < dxGeom * > gV ( count );
count = 0;
while ( g ){
gV . at ( count ) = g ;
g = g - > next ;
count ++;
}
printf ("\ n c o u n t e r % d " , count );
g = First ;
for ( int i = 0; i < count ; i ++){
/ * [ . . . ] */
}
}
Kapitel 5
Diskussion der Ergebnisse
Ausgehend von den in Kapitel 4 vorgenommenen Effizienztests, dient dieses
Kapitel dazu, die jeweiligen Messwerte der originalen und optimierten Projekte zu vergleichen. Die im Zuge der Projektarbeit gewonnene Erfahrung
mit OpenMP sowie Probleme bei der Umsetzung bilden die Argumentationsgrundlage dieser umfangreichen Analyse.
However, a memory-constrained application may be only
50 percent faster on a dual-core processor. And an
I/O-constrained application may not be faster at all.
M. Reynolds, 2007 [55, S. 7].
Einleitung
Ausgehend von dem Gedankengang, den aktuellen Stand der Technologie
im Bereich Multicore-Prozessoren für Rechenprozesse in Computerspielen
zu nutzen, ergeben sich verschiedene Möglichkeiten.
Zunächst bietet sich die Möglichkeit der Aufteilung von sequentiell abgearbeiteten Kernelementen eines Spieles (Physik, Künstliche Intelligenz,
Audio, Grafik, ...) auf die vorhandenen Prozessorkerne. Beim Einsatz von
Multi-/Hyperthreading stünden auf einer Dualcore-Maschine vier virtuelle
Prozessoren zur Verfügung und jeder der oben genannten Spielebereiche
könnte auf einen solchen Kern verteilt werden. Zwar würden diese Aufgaben
parallel ablaufen werden, doch Berechnungen innerhalb dieser Teilbereiche,
weiterhin sequentiell abgearbeitet werden. Während jener Kern, dem die
Grafikelemente zugeordnet wurden, bereits fertig ist und sich im Leerlauf
befindet, dauert die Physikberechnung aufgrund ihrer Komplexität möglicherweise länger und bremst das System. Diese Herangehensweise wurde
am Beispiel Cube (vergleiche Abschnitt 3.4) demonstriert.
53
KAPITEL 5. DISKUSSION DER ERGEBNISSE
54
Alternativ kann den einzelnen Spielbereichen1 nacheinander die volle
Bandbreite an Rechenleistung zur Verfügung gestellt werden, damit Algorithmen innerhalb dieses Kernbereiches parallelisiert ablaufen. Berechnungen für den Bereich Künstliche Intelligenz würden in oben genanntem
Beispiel alle virtuellen Kerne nützen können. Anschließend würden die Prozessorkerne (bei entsprechenden Thread-Ausgleichsmechanismen ohne große
Leerlaufzeiten) an die nächste Spieleinheit wie beispielsweise die Physikberechnung an weitergegeben werden. Hier könnte wiederum die komplette
Multithreading-Ressource ausgenutzt werden.
Diese Herangehensweise wurde an den Beispielen Techdemo (siehe Abschnitt 3.3) und ODE-Demo demonstriert (vergleiche Abschnitt 3.5).
Die Verteilung von Rechenarbeit auf mehrere Prozessorkerne verspricht
also zumeist deutliche Leistungssteigerungen, was die Ergebnisse der durchgeführten Tests nur zum Teil bestätigen konnten.
Die Projekte in ihrem Gesamtumfang liefern die Grundlage zu einer Diskussion, ob nachträgliche Parallelisierungen mit OpenMP effizient vorgenommen werden können, in welchem Ausmaß eine Performance-Steigerung
möglich ist und welche Schwierigkeiten dabei auftreten können.
5.1
Projekt Techdemo
Der Einsatz von Multithreading am Beispiel Techdemo ist in der gezeigten
Form nur begrenzt sinnvoll, nachdem der sequentielle Anteil des Programms
weiterhin überwiegt. Zu Versuchszwecken ist ein solches Projekt interessant,
um Techniken der Suche nach möglichen Ansatzpunkten zur Parallelisierung
entwickeln zu können. Für zukünftige Projekte kann die daraus gewonnene
Erfahrung bereits im Vorfeld der Implementierung in das Codedesign einfließen.
Durchschnittliche Ergebnisse beim Benchmarking zeigen, dass eine Parallelisierung (siehe Abbildung 5.1, vergleiche oberes und unteres Bild) die
Verteilung der Rechenlast verbessert. Die hellgrünen respektive schwarzen
Balken im oberen Bereich des jeweiligen Profils geben einen grafischen Überblick über die Lastverteilung.
Auf dem Dualcore-Computer ist die Last in der optimierten Methode
sendCollisionMessage besser verteilt, was eine stabile Ausführungszeit
(fps) auch bei steigender Anzahl an Kollisionen bewirkt.
ohne Optimierung: 16 CPU0 / 8 CPU1 Takte
mit Optimierung: 9 CPU0 / 10 CPU1 Takte
1
Eine Parallelisierung des Grafikbereiches wäre nur sinnvoll, wenn zumindest zwei Grafikprozessorkerne vorhanden sind.
KAPITEL 5. DISKUSSION DER ERGEBNISSE
CBGE::OdeManager::sendCollisioinMsg
CBGE::OdeManager::sendCollisioinMsg$omp$1
55
0.03
0.02
16
9
8
10
Abbildung 5.1: sendCollisionMessage: Benchmark-Ergebnis der originalen Methode (oben) und der optimierten Methode (unten) im Detail.
Blaue Linien stellen Punkte der Thread-Erzeugung bzw. -Terminierung dar.
Bei broadCastMessage konnte eine bessere, wenngleich nicht ideale Lastverteilung erreicht werden. Dies ist auf die innerhalb des Bereiches aufgerufene Methode queryComponent zurückzuführen. Diese greift zur Suche einer
bestimmten Komponente indirekt auf ein Iteratorobjekt (Xtree) zurück. Die
Suchroutine selbst wird bei Erfolg mittels return-Statement frühzeitig beendet, weshalb eine Optimierung mit OpenMP erschwert wird. Zwar ist ein
Abbrechen einer parallelen Schleife mittels stop-Statement in C++ möglich
(Verzweigungen durch eine return-Anweisung sind innerhalb eines OpenMPBlocks unzulässig), doch kann eine synchrone Beendigung der Threads nicht
gewährleistet werden.
Weitere Ergebnisse der Analyse belegen, dass sowohl die vorgenommene
Umformung der Schleife als auch deren Parallelisierung im Vergleich zur originalen Version mit einer Leistungssteigerung verbunden sind. Einen Überblick über die Verteilung der Rechenarbeit bieten die hellgrünen und schwarzen Balken im oberen Bereich des jeweiligen CodeAnalyst-Profils (siehe Abbildung 5.2).
Bei der Ausführung auf einem Dualcore-Prozessor und einem SinglecoreProzessor sind deutliche Unterschiede in der Framerate zu erkennen. Die
Versionen mit beziehungsweise ohne OpenMP-Konstrukte weisen im Single-
KAPITEL 5. DISKUSSION DER ERGEBNISSE
while (oIt!=recipients.end()) {
56
0.01
1
6
Abbildung 5.2: Lastverteilung in ObjectManager::broadCastMessage im
Zuge der Optimierung. Ergebnis durch Umformung der Schleife (dunkelgraue
bzw. grüne Markierung in der Mitte) und Ergebnisse der Parallelisierung der
Schleife (hellgraue Markierung unten) im Vergleich zur originalen Version
(rote Markierung oben).
core-Test erwartungsgemäß ähnliche Messergebnisse auf, wohingegen am
Dualcore-System nur jene des Leerlaufs (idle) circa gleich sind.
Der Leistungszuwachs von einem Singlecore- auf ein optimiertes Dualcore-System ist spürbar, doch nur zu einem Teil auf die durchgeführten Parallelisierungen zurückzuführen: Bis auf zwei Ausnahmen2 liegt auf einem
Dualcore-System die Framerate der ursprünglichen Applikation nur knapp
unter dem korrespondierenden Wert der optimierten Demo (wie das Balkendiagramm in Abbildung 5.3 veranschaulicht).
Dies ist auf zwei maßgebliche Ursachen zurückzuführen. Zunächst kann
das Task-Scheduling des Betriebssystems zu einer geringen Leistungssteigerung führen, indem Berechnungen durch den Aufruf von externen Bibliotheken (z.B. ode.dll) auf einem anderen Prozessorkern als die Hauptapplikation
ausgeführt werden (vergleiche Profil 3.3).
Da der Großteil des Quellcodes weiterhin sequentiell ausgeführt wird,
konnte die Anwendung um nur 11.04 % (genaue Messwerte in Tabelle 4.1,
Durchschnittswerte ersichtlich in Abbildung 5.3) beschleunigt werden. Der
Zusammenhang zwischen dem Grad der Parallelisierung und der erreichba2
Vergleiche Tabelle 4.1 – Testdurchlauf Release auf Dualcore: a) 82.6 fps gemessen in
Raum 1 ohne OpenMP; b) 264.5 fps gemessen in Raum 2 ohne OpenMP.
KAPITEL 5. DISKUSSION DER ERGEBNISSE
57
[ ] original
ohne OpenMP
auf Singlecore-System
Techdemo
mit/ohne OpenMP
[ ] original
ohne OpenMP
auf Dualcore-System
46.98
Raum 1
64.20
[ ] optimiert
mit OpenMP
auf Dualcore-System
67.23
Raum 2
103.20
232.73
262.48
50
70
100
230 260
Framerate
(fps)
Abbildung 5.3: Durchschnittliche Framerate der Techdemo im Zuge
der Optimierung. Durch OpenMP (grüner Balken) konnte die durchschnittliche Framerate im Vergleich zur originalen Applikation (orange und blauer
Balken) verbessert werden.
ren Leistungssteigerung wurde im Abschnitt 1.2 ausführlich behandelt.
Die Techdemo wurde an zwei exemplarischen Stellen um OpenMP-Anweisungen erweitert. Eine vollständige Optimierung ist damit nicht erfolgt. Es wurde versucht, die Rechenoperationen durch gezielte Eingriffe
gleichmäßiger auf vorhandene Prozessorkerne zu verteilen. Zwar konnten
keine idealen Ergebnisse erwartet werden, doch galt es, verschiedene Parallelisierungsmaßnahmen einzusetzen, um den Umgang mit OpenMP zu
erlernen. Weitere Verbesserungen des Projektes Techdemo durch Parallelverarbeitung wären vor allem im Bereich Grafik sinnvoll. Dies würde eine
Einarbeitung und Modifikation des freien Quellcodes der ODE- und OGREEngine bedingen, wofür der Zeit- und Arbeitsaufwand zum Zeitpunkt der
Projektdurchführung nicht abgeschätzt werden konnte.
5.2
Projekt Cube
Die Game-Loop der Cube-Engine wurde funktionell parallelisiert, sodass
unabhängige Rechenoperationen von unterschiedlichen Threads ausgeführt
werden. Für die Umsetzung konnte ein Großteil des sequentiellen Quellcodes
unverändert übernommen werden, wodurch sich weitere Implementierungen
und eine etwaige Fehlersuche einfacher gestalten. Eine effiziente, parallele
Applikation sollte außerdem mit der Anzahl zugrunde liegender Prozessorkerne skalieren können. Da die identifizierten Tasks betreffend Aktualisie-
KAPITEL 5. DISKUSSION DER ERGEBNISSE
58
Framerate (fps)
ohne Optimierung
mit Optimierung
228
193
116
Ø 227.93
Ø 192.76
Ø 115.61
[ ] original, ohne OpenMP, auf Singlecore-System
CubeVersion
[ ] original, ohne OpenMP, auf Dualcore-System
[ ] optimiert, mit OpenMP, auf Dualcore-System
Abbildung 5.4: Durchschnittliche Framerate der Cube-Engine im Zuge
der Optimierung. Durch OpenMP (grüner Balken) konnte die durchschnittliche Framerate im Vergleich zur originalen Applikation (blauer Balken) auf
einem Multicore-System deutlich verbessert werden.
rung und Darstellung des Spieles nur sinnvoll auf zwei arbeitende Threads
aufgeteilt werden können, erfüllt die vorliegende Implementierung lediglich
auf einem Multicore-System mit zwei Prozessorkernen die Anforderung der
Nutzung vorhandener Ressourcen [51, S. 30, S.124f.].
Durch die parallele Abarbeitung kam es auf einem Dualcore-System zu
einer deutlichen Erhöhung der Framerate um durchschnittlich 72.65%. Abbildung 5.4 liefert einen Vergleich der in den Tests gemessenen Framerate.
Das nachträglich erstellte Benchmark-Profil (siehe Abbildung 5.5) zeigt die
Veränderung der Lastverteilung der Cube-Engine vor und nach der Optimierung durch OpenMP. Die Rechenzeit pro Prozessorkern ist gleichmäßiger verteilt und der Gesamtaufwand der Anwendung hat von ursprünglich 65.64% auf 52.51% abgenommen (vergleiche Abbildung 5.5: Summe
der CPU-Takte beider Kerne ursprünglich 52656 Takte, optimiert lediglich
42060 Takte).
Interessant ist in diesem Zusammenhang die Tatsache, dass die Framerate trotz Parallelverarbeitung nicht mit der originalen Version auf einem
Singlecore-Rechner vergleichbar ist. Es konnte lediglich der PerformanceVerlust bei Migration von einem Singlecore- auf ein Dualcore-System verringert werden. Seitens der Entwickler der Cube-Engine seien die Ursa-
KAPITEL 5. DISKUSSION DER ERGEBNISSE
59
Abbildung 5.5: CPU-Last der Cube-Engine: deutliche Verbesserung
durch OpenMP. Oben: originale, unveränderte Version. Unten: Engine mit
parallelisierter Game-Loop.
chen der nicht für Multicore-Prozessoren entwickelten Applikation auf das
Task-Handling des Betriebssystems und/oder Grafikkartentreiber zurückzuführen. Ein darauf folgender Test auf einem Intel Dualcore-System3 ergab
bei Verwendung beider Prozessorkerne sowohl unter Gentoo Linux als auch
unter Windows XP ähnliche Einbußen der Framerate, wie auf dem ursprünglichen AMD-Testsystem. Die Aktivierung von Multithreading ( threaded
”
optimization“) in den erweiterten Einstellungen des Grafikprozessors führte
erwartungsgemäß zu einer Leistungsverbesserung. Dies verdeutlicht, dass
Grafik-intensive Anwendungen tendenziell eher vom Threading des Grafikprozessors als einer Optimierung in anderen Kernbereichen der SpieleApplikation (z.B. Physik) profitieren.
Dass Applikationen, die nicht für die Nutzung mehrerer Prozessoren optimiert wurden (entweder bereits im Codedesign oder nachträglich durch
beispielsweise OpenMP), die bereitgestellte Leistung von Multicore-Systemen nicht zur Gänze nutzen können, stellt kein neuartiges Problem dar. Unter
3
Intel Core 2 Duo E6600 2x 2.40 GHz 4 MB shared Cache, 2x 1024 MB RAM,
NVIDIA GeForce 7600 GS 256 MB RAM PCIe
KAPITEL 5. DISKUSSION DER ERGEBNISSE
60
dem Projekttitel Aurora“ begann die Fakultät für Informatik an der Uni”
versität Wien bereits 1997 in Form von mehreren Forschungsprojekten das
Potenzial und die damit verbundene Problematik der Multicore-Architektur
zu evaluieren. Im Rahmen einer zweitägigen Konferenz im Juni 2007, die
auch ein umfangreiches Vortragsprogramm zum Thema parallele Datenverarbeitung bietet, sollen aktuelle Ergebnisse präsentiert und diskutiert werden [56].
Wie an der Cube-Engine demonstriert, kann durch nachträgliche Parallelisierungs-Eingriffe ein gewisser Speedup erreicht werden, doch dient diese
Maßnahme hauptsächlich zur Schadensbegrenzung möglicher Geschwindigkeitseinbußen durch Multicore-Prozessoren. Selbst wenn über 80% der Applikation4 im Nachhinein mit OpenMP parallelisiert werden könnte, würden
ineffiziente Datenstrukturen und/oder mangelhaftes Speichermanagement
die Ausführungszeit (unabhängig vom zugrunde liegenden Betriebssystem)
voraussichtlich weiterhin bremsen.
5.3
Projekt ODE-Demo
Parallele Programme zeichnen sich dadurch aus, zeitaufwändige Berechnungen durch parallele Abarbeitung schneller lösen zu können. Dies ist jedoch
nur möglich, wenn die Applikation in zumindest einer Dimension (Tasks
oder Daten) in zumindest zwei Bereiche unterteilbar ist und diese weiterhin
rechenintensiv genug sind, um den Overhead entsprechender Parallelisierungsmechanismen zu rechtfertigen [51, S. 24].
Ist das Ausmaß an Daten für eine effiziente Aufteilung zu gering, resultieren daraus zwei Probleme: Zunächst entsteht durch die Parallelisierung
ein gewisser Overhead. Des Weiteren wird die effiziente Sicherung der Datenintegrität erschwert. Zugriffe auf gemeinsame Variablen können mittels
einer critical section (vergleiche Abbildung 5.6) gegen Zugriffsfehler abgesichert werden. Obwohl jeder Thread eine solche Region ausführt, wird durch
eine mutual exclusion gewährleistet, dass zu einem Zeitpunkt nur maximal
ein Thread in diesem Bereich tätig ist.
Im Fall der Space Stress Test“-Demo kam erschwerend hinzu, dass durch
”
das Design so genannter spaces“ in der Physikberechnung nur jeweils eine
”
geringe Anzahl an Objekten zur gleichen Zeit auf Kollisionen getestet werden
mussten. Während dies für ein sequentielles Programm von Vorteil ist, wird
eine mögliche Optimierung mit OpenMP ineffizient.
Im Durchschnitt würde die Crash Test“-Demo bei erfolgreicher Paralle”
lisierung mit zunehmender Anzahl an Prozessorkernen wegen dem Overhead,
der aus einem ungleichen Verhältnis von Rechenarbeit und Threads entsteht vermutlich sogar langsamer werden. Dieser Grenzfall birgt Potenzial
4
Eine Anwendung ist nachträglich nicht zu 100% parallelisierbar.
KAPITEL 5. DISKUSSION DER ERGEBNISSE
61
critical section
waiting to enter
critical section
Although each
thread executes
a critical section,
only one critical
section executes
at any one time,
assuring mutual
exclusions.
Abbildung 5.6: Das Verhalten von critical sections in OpenMP [16].
für zukünftige Forschung, ab wie vielen Elementen das konkrete QuellcodeSegment (vergleiche Abschnitt 3.4) effizient auf mehrere Threads aufgeteilt
werden könnte.
Nachträgliche Anpassungen des Quellcodes und ein sicherer Einsatz entsprechender OpenMP-Anweisungen können sich als problematisch und zeitkritisch erweisen, wenn die Implementierung der Applikation nicht oder nur
geringfügig bekannt ist. So wurde beim Projekt ODE-Demo erst im Zuge
der Effizienzanalyse aufgrund der verhältnismäßig geringen Leistungssteigerung festgestellt, dass ein Teil der Physik-Engine Aufgaben an einen Thread
delegierte. Da dieser Thread sich lediglich um Benutzereingaben und Grafikdarstellung kümmerte, war es nicht möglich gleichzeitig mit OpenMP in
einer anderen Physikroutine Verbesserungen durch Parallelverarbeitung zu
erreichen.
Um abwägen zu können, welchen Gewinn die geplante Parallelisierung
ausgehend von einer komplett sequentiellen Applikation erreichen könnte,
wurde der Render-Thread versuchsweise aus der Physikbibliothek entfernt.
Die resultierende deutliche Verbesserung der Framerate auf dem DualcoreTestsystem wurde auf unausgeglichene Lastverteilung zwischen der Grundapplikation und dem Render-Thread zurückgeführt.
Unausgeglichene Arbeitslast führt in der Regel zu Leerlaufzeiten und
in diesem Sinne einer Verschwendung von Prozessor-Ressourcen (wie Abbildung 5.7 visualisiert). Zwar konnte keine ideale Testumgebung simuliert
werden, doch zeigte eine Umformung des Quellcodes deutliche Auswirkungen (siehe Abbildung 5.8).
KAPITEL 5. DISKUSSION DER ERGEBNISSE
62
mehrere unabhängige Tasks
sollen auf 4 Recheneinheiten verteilt werden
schlechte Lastverteilung
gute Lastverteilung
Abbildung 5.7: Die Qualität der Lastverteilung wirkt sich auf die Performance der Applikation aus. Quelle: [47, S. 68].
Framerate (fps)
vollständige
Grafikausgabe
limitierte
Grafikausgabe
440
[ ] Ø 221.25 fps
original, ohne OpenMP
(Singlecore-System)
[ ] Ø 350.50 fps
original, ohne OpenMP
(Dualcore-System)
350
[ ] Ø 444.50 fps
optimiert mit OpenMP
(Dualcore-System)
534.0
465.5
270.0
355.0
235.5
172.5
220
Version der
ODE-Demo
Abbildung 5.8: Durchschnittliche Framerate der ODE-Demo Crash
”
Test“ im Zuge der Optimierung. Durch OpenMP (grüner Balken) konnte die
durchschnittliche Framerate im Vergleich zur originalen Applikation (blauer
Balken) auf einem Multicore-System deutlich verbessert werden.
KAPITEL 5. DISKUSSION DER ERGEBNISSE
63
Abbildung 5.9: Rechenaufwand nach erfolgter Parallelisierung: Die
Physikberechnung (oben, allgemeine Performance) wurde durch Parallelisierung der identifizierten Problemstelle (unten, SOR LCP) um zwei Prozent
verbessert.
Der Speedup betrug bei sequentieller Grafikberechnung5 auf einem Dualcore-System mit vollständiger Grafikausgabe rund 50%. Bei verringerter
Grafikausgabe (lediglich Darstellung des Hintergrundes), ebenfalls bei sequentieller Grafikberechnung, konnte eine Framerate-Steigerung von 17%
erzielt werden. Das Benchmark Profil in Abbildung 5.9 zeigt den durch
OpenMP letztendlich um 2.3% verringerten Gesamtaufwand der Physikkalkulation (vergleiche Messwerte vor der Optimierung mit 12.41% in Abbildung 3.6). Die geringe Gewinnspanne“ war in diesem Fall auf die bereits
”
im Originalzustand sehr schnell aktualisierende Applikation (ca. 456 fps)
und damit verbundene Performance-Grenze zurückzuführen.
Abhängigkeiten in Datenstrukturen limitieren die Parallelisierbarkeit des
Quellcodes, falls eine Auflösung der Beziehung nicht möglich ist. Im Idealfall
kann eine Iteration über einen Datensatz ohne Umwege in eine passende
for -Schleife umgewandelt werden. Ist eine Veränderung des Datenformats
notwendig (beispielsweise von einer verlinkten Liste in einen Vektor mit
direktem Zugriff auf bestimmte Indices), können weit reichende Änderungen
5
Sequentielle Grafikberechnung konnte durch Entfernung des ursprünglich vorhandenen
Render-Threads erreicht werden.
KAPITEL 5. DISKUSSION DER ERGEBNISSE
64
am Codedesign vonnöten sein, um den veränderten Datentyp einzubetten.
Heikel ist darüber hinaus die Handhabung von Pointer-Variablen. Selbst
durch vorausschauende Deklaration als private Variablen in OpenMP, kann
es zu schwerwiegenden Folgen durch unerlaubte Speicherzugriffe kommen,
wenn die zugehörigen Speicherstellen selbst nicht vervielfältigt wurden. Oft
ist es aufwändig, derartige Fehler im Quellcode zu lokalisieren.
Kapitel 6
Conclusio
Den Grundstein für diese Diplomarbeit lieferten drei Vorprojekte im Bereich Spiele-Programmierung. Es soll in diesem letzten Kapitel zusammengefasst werden, unter welchen Umständen durch nachträglichen Einsatz von
OpenMP tatsächlich Leistungssteigerungen zu erwarten sind und welche
Grenzen diese Art der Parallelverarbeitung mit sich bringt.
Multi is everywhere and software is the key.
Shu-ling Garver & Bob Crepps, 2006 [26].
Diese Diplomarbeit evaluierte die Problematik der nachträglichen Parallelisierung von Computerspielen mit OpenMP. Als Ausgangspunkt dienten
die vorgestellten Projekte Techdemo“ (vergleiche Abschnitt 3.2), Cube“
”
”
(vergleiche Abschnitt 3.3) und ODE-Demo“ (vergleiche Abschnitt 3.4).
”
Diese wurden auf mögliche Ansatzpunkte zur parallelen Ausführung überprüft und um OpenMP-Anweisungen erweitert.
Das Gesamtprojekt zeigt, dass der nachträgliche Einsatz von OpenMP
in Computerspielen unter gewissen Voraussetzungen tatsächlich mit einer
Leistungssteigerung verbunden ist.
Wie gut“, im Sinne der resultierenden Performance-Zunahme, eine Spie”
le-Anwendung nachträglich durch parallele Ausführung verbessert werden
kann, hängt hierbei nicht von der gewählten Technik (z. B. OpenMP oder
Win32 API) ab, sondern wird vor allem durch den Grad der Parallelisierung sowie der zugrunde liegenden Hardware beeinflusst. Zwar sind bei
OpenMP nicht alle Feinheiten der Threading-API steuerbar (wie bei Win32Threading [51]), jedoch ist die Umsetzung durch schrittweise nachträgliche
Erweiterung des Quellcodes einfacher.
Hardware-seitig kann unter anderem die Rechnerarchitektur beziehungsweise Speicherhierarchie die Ausführungszeit beeinflussen. Können die
65
KAPITEL 6. CONCLUSIO
66
notwendigen Instruktionen direkt aus dem Cache geladen werden, erfolgt der
Zugriff schneller, als wenn die Daten aus dem Hauptspeicher geholt werden
müssen. Darüber hinaus wirkt sich die Architektur des Bussystems (Intel:
FSB, AMD: Direct Connect) auf die Speicherzugriffszeiten aus.
Der Parallelisierungsgrad und in weiterer Folge die Skalierbarkeit der
Anwendung wird durch das Software-Design respektive durch verwendete
Algorithmen eingegrenzt. Aufgrund der erforderlichen Kommunikation und
Synchronisation zwischen den Threads zur Sicherstellung der Datenintegrität, verursachen Datenabhängigkeiten einen zeitlichen Overhead. Da
dieser Overhead nicht gänzlich umgangen werden kann, ohne die korrekte
Ausführung des Programms zu gefährden, sollte versucht werden, die notwendige Kommunikation auf ein Mindestmaß zu beschränken. Oft können
Abhängigkeiten durch alternatives Code-Design abgeschafft werden.
Mit steigender Prozessoranzahl wird die Synchronisation der Cache-Inhalte schwieriger (so genannte Kohärenz1 ). Es muss daher ein Mittelmaß
zwischen feingranularer (für einen maximalen Parallelisierungsgrad) und
grobgranularer (für eine minimale Kommunikation) Parallelisierung gefunden werden. Neben Design-Überlegungen zur idealen Verteilung der Rechenarbeit auf vorhandene Prozessoren, sind nach erfolgter Implementierung in
jedem Fall Evaluierungen zur Laufzeit erforderlich, um die Lastverteilung
optimieren zu können [30].
Applikationen skalierbar zu gestalten war in der bisherigen Spiele-Entwicklung nicht von Bedeutung, weil steigende Taktfrequenzen der Prozessoren keine Veränderungen am Code selbst erforderten, um die Anwendung
zu beschleunigen. Bereits jetzt sind Dualcore-Prozessoren im Desktop- und
Spiele-Segment alltäglich. Es ist im Sinne der Produktion einfacher, mehrere Prozessoren miteinander zu verbinden, als die physikalisch begrenzte
Prozessorleistung weiter zu erhöhen.
Anwendungen, die ursprünglich nur für Singlecore-Systeme gedacht waren, können die auf einem Multicore-Computer eingebüßte Performance
durch den Einsatz von OpenMP zurückgewinnen. Das Ergebnis würde besser ausfallen, wenn die Spiele-Applikation von Anbeginn für eine parallele
Ausführung konzipiert wird. Der Zeitaufwand, ein solches Multicore-Spiel
zu entwickeln, soll im Vergleich zur Singlecore-Variante zwei bis dreifachen
Aufwand verursachen [59] [21]. Es bleibt zu hoffen, dass in Zukunft verstärkt
Unterstützung in Form von verbesserten Entwicklungsumgebungen geboten
wird, damit die Prozessor-Leistung von Multicore-Systemen zur Gänze genutzt werden kann.
Wie bereits John L. Gustafson feststellte: je leistungsfähiger der Prozessor
ist, desto größere Probleme sollen in weniger Zeit damit lösbar sein [34].
1
siehe http://de.wikipedia.org/wiki/Cache-Kohärenz
Anhang A
Parallel programming in
OpenMP
Das hier zusammengefasste Buch Parallel programming in OpenMP“ er”
leichtert den Einstieg in Multithreading sowie OpenMP. Abbildungen und
weiterführende Informationen können den entsprechenden Kapiteln des Buches entnommen werden (siehe Literatureintrag [16]).
A.1
Einleitung
Der Performance-Gewinn durch korrekten Einsatz paralleler Verarbeitungstechniken wächst je nach Referenzapplikation deutlich mit zunehmender Anzahl der Prozessoren. Die Entwicklung von parallelisiertem Code erfordert
erhöhten Design- und Implementierungsaufwand. Bevor ein Algorithmus
auf einer Multicore-Maschine umgesetzt werden kann, sollte sich der Programmierer Gedanken machen, wie die Rechenlast auf mehrere Prozessoren
sinnvoll aufgeteilt werden kann.
OpenMP ist keine eigene Programmiersprache sondern besteht aus einer Ansammlung von Compiler-Direktiven zur Beschreibung von parallelem
Code. Diese Instruktionsanweisungen – in Form von Kommentaren in Fortran sowie #pragmas in C++ – sollen die Kompatibilität beim Portieren zu
OpenMP-freien Umgebungen erhöhen.
Durch die Anweisung OMP_NUM_THREADS kann die Anzahl zu verwendender Threads und somit die Ausprägung der Parallelisierung festgesetzt
werden (Aufruf der Methode omp_get_num_threads() liefert eine eindeutige Identifikationsnummer mit Wertebereich 0 – OMP_NUM_THREADS-1). Da
die Ausführungsreihenfolge der Threads nicht vorbestimmbar ist, sind Synchronisationsmechanismen notwendig, um durch verbesserte Kommunikation zwischen den Threads einen bestimmten Programmablauf garantieren
zu können.
67
ANHANG A. PARALLEL PROGRAMMING IN OPENMP
A.2
68
Erste Schritte mit OpenMP
Während MPI1 für Parallelverarbeitung eine Unterstützung in Form von
Aufrufen der Laufzeitbibliothek zur Verfügung stellt, werden bei OpenMP
Compileranweisungen – ergänzt durch Aufrufe von Laufzeitbibliotheken und
Umgebungsvariablen – in das Programm eingebettet, um Shared-memoryParallelität zu ermöglichen.
Da OpenMP unabhängig vom zugrunde liegenden Betriebssystem ist,
beschränkt sich das Portieren eines sauber programmierten OpenMP Projektes auf das erneute Compilieren desselben. Darüber hinaus, werden für C
und C++ Implementierungen durch Inkludieren der Datei omp.h OpenMPTypdefinitionen und Bibliotheksfunktionsprototypen bereitgestellt.
Die bereitgestellten Spracherweiterungen für C und C++ können in folgende Kategorien unterteilt werden:
Pragmas in der Form #pragma omp [...]
Durch das Schlüsselwort omp wird ein OpenMP-Pragma vom Compiler bei vorhandener Unterstützung bearbeitet, andernfalls ignoriert.
Auf diese Weise kann zugrundeliegender Quellcode sowohl für seriellen
als auch parallelen Applikationsfluss verwendet werden.
In dem Makro _OPENMP muss sich das Datum (in der Form yyyymm)
der verwendeten OpenMP-Spezifikation wiederfinden.
Eine kleine Anzahl von parallelen Kontrollmechanismen dient der Organisation des Datenflusses in paralleler Form nach dem fork and
join Prinzip. Hierzu zählen die Anweisungen parallel (um mehrere
Threads zu erzeugen, welche die im Inneren des Blockes definierten
Aufgaben jeweils gleichzeitig bearbeiten) sowie do und for (um Arbeit
zwischen existierenden Threads aufzuteilen und somit das Phänomen
des loop-level parallelism auszunutzen).
Datenumgebungskonstrukte zur Kommunikation zwischen Threads:
Für die Dauer des OpenMP-Programmes existiert ein sogenannter
Master-Thread, der durch entsprechende Anweisungen die Aufgaben
an neue parallele Threads mit einem privaten Adressraum delegiert
(siehe Abbildung A.1).
Ob eine Variable in parallelen Regionen nur für den aktuellen Thread
oder alle parallel laufenden Threads sichtbar sein soll, wird durch scoping clauses unterschieden:
shared kennzeichnet eine allgemein zugängliche Variable, die
nur an einer einzigen Stelle im Speicher, den sich alle Threads teilen,
referenziert ist. Kommunikation zwischen einzelnen Threads entsteht
1
http://de.wikipedia.org/wiki/Message Passing Interface
ANHANG A. PARALLEL PROGRAMMING IN OPENMP
69
Master thread executes serial
portion of the code.
Master thread encounters parallel
for directive, creates slave threads.
Master and slave threads divide
iterations of parallel for loop and
execute them concurrently.
Implicit barrier: Wait for all threads
to finish their iterations.
Master thread resumes execution
after the for loop, slave threads
disappear.
Abbildung A.1: Sequenzdiagramm des Laufzeitverhaltens von OpenMP
durch Veränderung einer solchen Variable.
private kennzeichnet eine Variable, die für jeden Thread und
somit an mehreren Speicherstellen abgelegt ist. Da der Speicherbereich nur durch diesen einen Thread zugänglich ist, kann eine solche
Variable nur von dem zugehörigen Thread verändert oder ausgelesen
werden (z.B. temporäre Variablen). Der Initialzustand einer privaten
Variable ist undefiniert, weil sie für jeden Thread einen neuen Speicherbereich zugeteilt bekommt. Am Ende einer parallelen Sektion werden
diese Instanzen gelöscht und der Master-Thread übernimmt die weitere (serielle) Ausführung (siehe Abbildung A.2).
reduction kennzeichnet eine Variable, die private und shared
Attribute vereint. Hierbei handelt es sich um ein Objekt, auf das eine
arithmetische Reduktion angewandt wird, deren Implementierung von
Compiler und Laufzeitumgebung effizient umgesetzt werden kann (z.B.
Summierung temporärer lokaler Variablen am Ende einer parallelen
Anweisung)
Konstrukte zur Synchronisierung der Ausführungsreihenfolge vorhan-
dener Threads respektive Zugriffsreihenfolge auf Variablen mehrerer
Threads in Form einer mutual exclusion 2 (z. B. durch das critical
Statement) oder event synchronisation 3 (das barrier Statement stellt
die einfachste Form dar, um zu garantieren, dass jeder Thread nach
dieser Anweisung den bisherigen Code komplett abgearbeitet hat).
Schleifen ohne Abhängigkeiten zwischen den Schleifendurchläufen liefern
bei paralleler Abarbeitung der einzelnen Durchläufe, unabhängig von der
2
3
gewährt nur einem einzigen Thread gleichzeitig Zugriff auf eine shared-Variable
signalisiert das Auftreten eines Thread-übergreifenden Ereignisses
ANHANG A. PARALLEL PROGRAMMING IN OPENMP
Global shared
memory
z
a
x
y
z
a
Parallel execution
(multiple threads)
Each thread has a
private copy of i
References to i are
to the private copy
i
All data references
are to global
shared instances
Serial execution
(master thread only)
Global shared
memory
n
70
x
y
n
i
References to z, a, x, y, n
are global shared instances
i
i
i
i
Abbildung A.2: Das Verhalten von privaten Variablen in OpenMP – Ein
Thread arbeitet im selben shared-Adressraum des originalen sequenziellen
Master-Threads, mit zusätzlichem Zugriff auf die jeweils eigenen Variablen.
Ausführungsreihenfolge der zugeordneten Threads, immer das selbe Ergebnis. Die Aufteilung der (als unabhängig angenommenen) Schleifen- Bruch”
stücke“ ist seitens OpenMP nicht festgelegt, sondern wird von der Implementierung des Compilers beeinflusst. Sobald Schleifenkonstrukte komplexer gestaltet sind, indem im Inneren einer Schleife einzelnen Variablen oder
einem Array Werte zugewiesen werden, ist die korrekte Zuordnung von
Geltungsbereichen schwierig. Der parallele Bereich muss zusätzlich durch
Synchronisationsmechanismen abgesichert werden (siehe Abbildung A.3).
Der Ansatz, Parallelisierung auf Ebene einer Schleife vorzunehmen (engl.
loop-level parallelism) wird als fine-grained Parallelität bezeichnet, weil hier
nur kleine Aufgaben (z.B. ein Schleifendurchlauf pro Thread) parallel ausgeführt werden. Im Gegensatz dazu setzt eine parallel region sogenannte
coarse-grained Parallelität um, bei der jeder ausführende Thread das selbe
Codestück (z.B. Zugriff auf ein Array in mehreren aufeinander folgenden
Schleifen) gleichzeitig aber asynchron abarbeitet. Zwar erfordert der Einsatz paralleler Regionen einen höheren Analyse- und Implementierungsaufwand seitens des Programmierers, doch sind auf diese Weise sowohl bessere
Skalierbarkeit als auch Performance erzielbar als mit einer feingranularen
Parallelisierung.
ANHANG A. PARALLEL PROGRAMMING IN OPENMP
71
critical section
waiting to enter
critical section
Although each
thread executes
a critical section,
only one critical
section executes
at any one time,
assuring mutual
exclusions.
Abbildung A.3: Zugriffe auf gemeinsame Variablen können mittels einer
critical section gegen Fehler wie race conditions abgesichert werden. Obwohl
jeder Thread eine critical section ausführt, wird durch eine mutual exclusion
gewährleistet, dass zu einem Zeitpunkt nur maximal ein Thread in diesem
Bereich tätig ist.
A.3
Ausnutzung der Parallelität auf Schleifenebene
Schleifen bilden oft einen Flaschenhals in der Abarbeitungsgeschwindigkeit
einer Applikation und bieten daher einen sinnvollen Ansatzpunkt zur schrittweisen Leistungssteigerung durch Multithreading. Da die parallel ausgeführten Aufgaben des Schleifeninneren kleine Einheiten (oft nur eine oder wenige Anweisungen) darstellen, spricht man von fine grained Parallelisierung.
OpenMP stellt hierfür in C++ das parallel for Konstrukt zur Verfügung:
#pragma omp parallel for [clause [clause]...]]
for (index = first; test_expression; increment_expression)
{
body of the loop
}
Zu beachten gilt, dass nicht jede serielle Schleife unbedacht parallelisiert werden kann. Übermäßige Parallelisierung oder ungleich verteilte Prozessorlast führen üblicherweise zu Performance-Einbußen, während Datenabhängigkeiten die Korrektheit des Programmes negativ beeinflussen können. Die optionale clause Anweisung beinhaltet Angaben zu Lastverteilung
ANHANG A. PARALLEL PROGRAMMING IN OPENMP
72
und Synchronisation der Threads, sowie Informationen zu Schleifenvariablen
(Geltungsbereich, Initialisierung, ...) und erlaubt folgende Konstrukte:
private, shared, default und reduction: vgl. hierzu Abschnitt A.2
reduction (op / intrinsic:list)
if (logical expression)
Die Variable index muss verpflichtend vom Typ Integer sein und es
muss die Variable test_expression einen Rückschluss auf die Durchlaufanzahl der Schleife geben. So kann bereits zur Laufzeit festgestellt werden,
wie oft eine Schleife durchlaufen wird, ohne selbige ausführen zu müssen
(zulässige Konstrukte in der Form index < end, wobei als Vergleichsoperatoren <, >, <=, >= möglich sind). Für increment_expression sind die
Operatoren ++, --, +=, -= und = erlaubt. Für den sogenannten body
der Schleife werden die einzelnen Schleifendurchläufe unter den aus dem
Master-Thread entsprungenen Slave-Threads aufgeteilt. Wenn ein solcher
Slave-Thread seine zugeordnete(n) Iteration(en) abgearbeitet hat, wartet
er an einer impliziten Barriere, bis auch die restlichen Slave-Threads ihre
Arbeit erledigt haben und gemeinsam zerstört werden können.
Zwischen verschiedenen Threads kann eine Kommunikation über Variablen im gemeinsamen Adressraum stattfinden. Hierfür werden von OpenMP
Attribute zur Eingrenzung der Sichtbarkeit von Variablen im gemeinsamen
Adressraum zur Verfügung gestellt. Um die Korrekteit von parallelisierten Berechnungen sicherzustellen, ist es nicht nur notwendig, Variablen mit
entsprechenden Attributen zu versehen, sondern auch (möglichst bereits im
Vorfeld der Implementierung) vorhandene Datenabhängigkeiten des Algorithmus abzuschaffen.
A.4
Parallele Regionen
Parallele Regionen sind Bereiche der mehrfachen, gleichzeitigen Ausführung
durch Threads. Der Quellcode wird hierbei von jedem einzelnen Thread
komplett durchlaufen und ausgeführt ( SPMD“ 4 -Parallelität). Innerhalb
”
einer solchen parallelen Region stellt OpenMP sogenannte work-sharing constructs zur Verfügung, um die Ausführung von iterativen als auch noniterativen Teilbereichen auf vorhandene Threads aufzuteilen. In C/C++ definiert #pragma omp parallel [clause [clause]...] den darauffolgenden Block (es muss sich hierbei um einen strukturierten5 Codeblock han4
Single-Program Multiple-Data
d.h. der Bereich wird am Beginn der parallelen Region an einer Stelle betreten und
am Ende an einer Stelle verlassen. Verzweigungen nach außen wie beispielsweise returnStatements verletzen diese Regel. Ein Verlassen mittels exit in C/C++ ist hingegen
erlaubt, doch werden die anderen arbeitenden Threads asynchron beendet, weshalb nicht
abgeschätzt werden kann, wann jeder Thread gestoppt und das Programm beendet wird.
5
ANHANG A. PARALLEL PROGRAMMING IN OPENMP
73
Master thread
Slave threads
in parallel region
Abbildung A.4: Parallele Regionen dienen zur vervielfältigten
Ausführung. Schematische Darstellung des Thread-Handlings von OpenMP.
deln) als parallele Region, wobei als clause-Bedingung folgende Angaben
zulässig sind:
private, shared, default und reduction: vgl. hierzu Abschnitt A.2.
if (logical expression): zur Ausführungszeit wird aufgrund dieser
Bedingung entschieden, ob die parallele Region parallel oder nur seriell
(bei nicht erfüllter Bedingung) von einem Master-Thread ausgeführt
wird.
copyin (list): ermöglicht die Kommunikation zwischen dem MasterThread und den Slave-Threads über threadprivate-Variablen.
Am Ende einer parallelen Region existiert eine implizite Barriere, von
wo an ein Master-Thread die Arbeit (bis zur nächsten parallelen Region)
allein fortsetzt (siehe Abbildung A.4).
Zusammenhang zwischen threadprivate und der copyin-Klausel
Die threadprivate-Anweisung markiert einen Block (oder eine globale Variable in C/C++), sodass für jeden ausführenden Thread eine eigene Kopie mit
den gleichen Initialisierungswerten des Master-Threads angelegt wird. Dies
beeinflusst auch Variablen innerhalb eines solchen Blocks, sodass Veweise
innerhalb eines Threads immer auf die eigene Kopie einer Variable zugreifen, unabhängig vom Geltungsbereich. Der Zugriff auf eine solche private
Instanzvariable eines anderen Threads ist nicht möglich. Darüber hinaus
darf ein threadprivate-Block nicht innerhalb einer private-Klausel vorkommen. In C/C++ lautet die Syntax #pragma omp threadprivate (list) .
ANHANG A. PARALLEL PROGRAMMING IN OPENMP
74
Diese Angabe muss nach der Deklaration eines Blocks (oder eines Geltungsbereichs oder einer globalen Variable in C/C++) erfolgen.
Da Threads am Ende einer parallelen Region zwar scheinbar verschwinden, aber genaugenommen nur schlafend“ auf die nächste parallele Region
”
warten, bleibt ihr Status bis zum Beginn der nächsten Region bestehen6 .
Ebenso der Gehalt von Daten innerhalb des threadprivate-Konstrukts.
Der Zugriff eines Slave-Threads auf den Inhalt von threadprivate-Variablen des Master-Threads ist über die copyin-Klausel möglich, die im Rahmen
eines parallel-Statements mittels copyin (list) eingesetzt werden kann.
Der Parameter list kann entweder eine Liste von Variablen innerhalb eines
threadprivate-Abschnittes, der Name eines solchen Blocks oder eine globale
threadprivate-Variable in C/C++ sein. Auf diese Weise können private Variablen eines Slave-Threads mit den korrespondierenden Variablenwerten des
Master-Threads initialisiert werden.
work-sharing
Innerhalb einer parallelen Region stellt OpenMP sogenannte work-sharingAnweisungen zur Verfügung, um Arbeit auf mehrere Threads aufzuteilen
anstatt diese in vervielfältigter Form gleichzeitig ausführen zu lassen.
do-Anweisung:
um Schleifendurchläufe aufzuteilen, wie Abbildung
A.5 illustriert.
Die Syntax in C/C++ lautet:
#pragma omp for [clause [clause] ...]
for-loop
Als clause sind die bereits diskutierten Geltungsbereichsangaben private, lastprivate und reduction, sowie ordered- und schedule-Klauseln
zulässig. Um die implizite Barriere am Ende der Schleife zu umgehen, kann ein nowait am Ende des Pragmas hinzugefügt werden.
Wird eine parallele Region mit einer solchen do-Anweisung kombiniert
und ist in diesem Bereich lediglich loop level parallelism zu implementieren, ergibt sich in der Kurzschreibweise das bereits vorgestellte
#pragma omp for Schleifenkonstrukt.
sections-Anweisung:
um verschiedene non-iterative Bereiche aufzuteilen. Die Syntax in C/C++ lautet:
6
Dieser Zustand wird seitens OpenMP so lang garantiert, wie die Anzahl arbeitender
Threads konstant bleibt. Wird beispielsweise durch einen Aufruf einer Laufzeitbibliotheksfunktion die Anzahl der Threads geändert, wird der Thread-Pool neu erstellt und
eventuelle threadprivate-Variablen mit entsprechenden Daten des Master-Threads initialisiert.
ANHANG A. PARALLEL PROGRAMMING IN OPENMP
75
#pragma omp for
for ( signed int i = ... ) {
...
}
replicated execution
in parallel region
work-sharing
in parallel region
Abbildung A.5: Kombination von mehrfacher Ausführung und Arbeitsteilung in einer parallelen Region.
#pragma omp sections [clause [clause] ...]
{
[#pragma omp section]
block
[#pragma omp section]
block
...
}
Als Einsatzbereich paralleler Regionen bieten sich Programmbereiche
an, wo eine individuelle parallele Ausführung (z.B. aufgrund geringer
Arbeitslast) nicht effizient umgesetzt werden kann, allerdings eine Verteilung der einzelnen Tasks auf vorhandene Threads möglich ist. Jede
der definierten Sektionen (die section-Anweisung für den ersten Block
ist optional) wird einmal ausgeführt und jeder Thread führt null oder
mehr Sektionen aus, wobei weder die Reihenfolge der Ausführung noch
die Zuordnung zu einem bestimmten Thread im Vorfeld bestimmt werden kann.
single-Anweisung: um einen Abschnitt innerhalb einer parallelen Region zur Ausführung von nur exakt einem Thread zu kennzeichnen.
Die Syntax in C/C++ lautet:
#pragma omp single [clause [clause] ...]
block
Durch ihre Funktionalität (Arbeit wird einem einzigen Thread zugewiesen) unterscheidet sich die single-Anweisung maßgeblich von anderen work-sharing-Konstrukten. Jede clause muss in C/C++ vom
Typ private-, firstprivate- oder eine nowait-Anweisung sein. Am Ende
des single-Bereichs ist eine implizite Barriere vorhanden, am Beginn
müsste diese bei Bedarf zusätzlich angegeben werden.
ANHANG A. PARALLEL PROGRAMMING IN OPENMP
Variable
OMP_SCHEDULE
Example
dynamic, 4“
”
OMP_NUM_THREADS
16
OMP_DYNAMIC
TRUE
FALSE
TRUE
FALSE
OMP_NESTED
or
or
76
Description
Specify the schedule type for
parallel loops with a runtime
schedule.
Specify the number of threads
to use during execution.
Enable/disable dynamic adjustment of threads.
Enable/disable nested parallelism.
Tabelle A.1: Überblick über verfügbare Umgebungsvariablen zur Steuerung der Ausführungsparameter einer parallelen Applikation.
Einschränkungen
Format und Einsatz von work-sharing-Konstrukten unterliegen in OpenMP
einigen wichtigen Einschränkungen.
Die Struktur des Codeabschnittes innerhalb einer work-sharing-
Anweisung muss einem Block von null oder mehr aufeinander folgenden Statements entsprechen, der am Beginn betreten und am Ende
verlassen wird.
Alle Threads müssen auf jedes work-sharing-Konstrukt in der selben
Reihenfolge treffen. Wird ein Konstrukt ausgelassen, so muss es von
allen Threads gleichermaßen ausgelassen werden. Es muss für alle
Threads einen einheitlichen Eingangs- und Ausgangspunkt am Beginn
und am Ende des Blocks geben.
Verschachtelung von work-sharing-Konstrukten ist nicht sinnhaft
und nicht erlaubt.
Kontrollmechanismen
OpenMP stellt einige Mechanismen zur Verfügung, um die Parallelisierungstiefe zur Laufzeit abzufragen und zu steuern. Tabelle A.1 fasst die verfügbaren Umgebungsvariablen zur Ausführungszeit einer parallelen Region zusammen.
Über eine if -Anweisung am Beginn des zur parallelen Ausführung gekennzeichneten Bereichs wird dynamisch entschieden, ob der anschließende Quellcode tatsächlich parallel (Bedingung liefert true) oder seriell (Bedingung liefert false zurück) ausgeführt wird.
ANHANG A. PARALLEL PROGRAMMING IN OPENMP
77
Darüber hinaus kann mittels logical function omp_in_parallel –
einer Methode der Laufzeitbibliothek von OpenMP – festgestellt werden, ob der Bereich in dem der Aufruf stattfindet, von einem seriellen
Thread oder mehreren parallelen Threads ausgeführt wird. Das Ergebnis dieser Abfrage kann dazu verwendet werden, sich je nach dem
Parallelisierungsstatus der Umgebung, für einen seriellen oder parallelen Algorithmus zu entscheiden.
Während der Ausführung der Applikation kann über die Umgebungs-
variable OMP_NUM_THREADS sowie einen Aufruf zur Laufzeitbibliothek
mittels omp_set_num_threads(2) die Anzahl der arbeitenden Threads
für die weitere Ausführungszeit auf einen beliebigen Wert geändert
werden (ausgenommen direkt innerhalb eines parallel ausgeführten
Abschnittes). Abfragen in der Form omp_get_num_threads() und
omp_get_num_procs() liefern die Anzahl verfügbarer Threads respektive Prozessoren des zugrundeliegenden Systems. Durch Kombination
kann über omp_set_num_threads(omp_get_num_procs()) die Anzahl
der Threads dynamisch auf die jeweils verfügbare Menge an Prozessoren gesetzt werden, sodass jedem Kern später nur ein Thread zugeordnet wird.
In Tabelle A.1 sind verschiedene Laufzeitbibliotheksfunktionen zur Steuerung der Ausführungsparameter einer parallelen Applikation zusammengefasst.
A.5
Synchronisation
Da in einem OpenMP-Programm Kommunikation über Variablen im gemeinsamen Adressraum erfolgt, müssen Zugriffe auf diese Speicherbereiche
koordiniert werden, um eine korrekte Ausführung gewährleisten zu können.
Die Ausführungsreihenfolge von Schreib- und Leseoperationen kann die Korrektheit des Rechenergebnisses beeinflussen und abhängig vom Algorithmus
zu abweichenden Ergebnissen führen.
Um das Risiko von Verletzungen der Datenintegrität7 gering zu halten, sollten Variablen, die nicht zum Datenaustausch zwischen verschiedenen Threads gedacht sind, nach Möglichkeit als private deklariert werden.
Die reduction-Anweisung kann sich bei komplexeren Rechenoperationen als
hilfreich erweisen.
7
Unter data race sind gleichzeitige Schreib- und Lesezugriffe auf Daten zu verstehen,
doch es ist nur dann eine Synchronisation vonnöten, wenn Schreiboperationen involviert
sind.
ANHANG A. PARALLEL PROGRAMMING IN OPENMP
void
omp_set_num_threads(int)
int omp_get_num_threads()
int omp_get_max_threads()
int omp_get_thread_num()
int omp_get_num_procs()
int omp_set_dynamic(int)
int omp_get_dynamic()
int omp_in_parallel()
int omp_set_nested(int)
int omp_get_nested()
Set the number of threads to use in a
team.
Return the number of threads in the
currently executing parallel region.
Return 1 if invoked from serial code
or a serialized parallel region.
Return the maximum value that calls
to omp_get_num_threads may return. This value changes with calls
to omp_set_num_threads.
Return the thread number within the team, a value within
the inclusive interval 0 and
omp_get_num_threads - 1. Master
thread is 0.
Return the number of processors
available to the programm.
Enable/disable dynamic adjustment
of number of threads used for a parallel construct.
Return .TRUE. if dynamic threads is
enabled, and .FALSE. otherwise.
Return .TRUE. if this function is invoked from within the dynamic extent of a parallel region, and .FALSE.
otherwise.
Enable/disable nested parallelism. If
disabled, then nested parallel regions
are serialized.
Return .TRUE. if nested parallelism
is enabled, and .FALSE. otherwise.
Tabelle A.2: Überblick über verfügbare Laufzeitbibliotheksfunktionen zur
Steuerung der Ausführungsparameter einer parallelen Applikation.
78
ANHANG A. PARALLEL PROGRAMMING IN OPENMP
79
Synchronisation durch mutual exclusions
Es soll der Zugriff auf einen Datensatz zur einer bestimmten Zeit kontrolliert
werden. Die Absicherung der betreffenden Codestelle erfolgt in der Regel
mittels locks (z.B. einen in verschiedene Sektionen unterteilten Binärbaum
nur an einer dieser Sektionen sperren) oder critical sections (z.B. einen kompletten Binärbaum für exklusiven Zugriff sperren).
Die critical section-Anweisung sperrt für den aktuellen Thread die
kritische Region mit dem Namen name bis der Block wieder verlassen
wird. Wird der optionale Parameter name nicht angegeben, werden
alle im Programm definierten critical sections für diese Zeit gesperrt.
Aus diesem Grund sollte die Anweisung möglichst direkt um den kritischen Bereich positioniert werden (z.B. innerhalb eines if -Blocks anstatt die Abfrage selbst mit einzukapseln).
Syntax in C/C++:
#pragma omp critical [(name)]
block
OpenMP liefert keine Lösung zur Vermeidung von Deadlocks, daher
muss der Programmierer selbst dafür sorgen, dass kein gegenseitiges
Warten der Threads untereinander auftritt.
Das atomic-Konstrukt dient dazu, um lediglich eine einzelne Anwei-
sung zur Veränderung einer Skalarvariable in eine kritische Region zu
verpacken.
#pragma omp atomic
x < binop >= expr
...
Syntax in C/C++:
#pragma omp atomic
/* One of */
x++, ++x, x--, or --x
Anmerkung: x ist eine Skalarvariable vordefinierten Typs, binop ist
ein vordefinierter arithmetischer oder logischer Operator.
Alternativ werden von OpenMP verschiedene Sperrmechanismen
der Laufzeitumgebung zur Verfügung gestellt. Diese sind in ihrer
Handhabung flexibler als die bisher vorgestellten OpenMP-Direktiven.
Event Synchronisation
Im Gegensatz zu mutual exclusions, kann mit folgenden Anweisungen die
Ausführungsreihenfolge von kritischen Bereichen gesteuert werden.
ANHANG A. PARALLEL PROGRAMMING IN OPENMP
80
Eine Barriere garantiert, dass diese Stelle erst dann überschritten
wird, wenn alle Threads den hier endenden Block abgearbeitet haben.
Syntax in C/C++:
#pragma omp barrier
Eine Barriere dient demnach als globaler Synchronisationspunkt (üblicherweise am Ende einer Schleife eingesetzt) und schließt eine parallele
Region/Schleife für alle Threads gesammelt ab. Um eine Barriere zu
umgehen, können mehrere parallele Schleifen in eine zusammengefasst
oder die nowait-Klausel eingesetzt werden.
Die ordered-Anweisung kann dazu verwendet werden, um die durch
Aufteilung auf Threads nicht deterministische Ausführungsreihenfolge
der Schleifendurchläufe zu ordnen. Bevor ein gekennzeichneter Block
betreten werden kann, muss jener Thead mit der Berechnung des vorhergehenden Schleifendurchlaufes ebenfalls an dieser Stelle angelangt
sein. Die betroffene Schleife muss in der Signatur ebenfalls mit einer
ordered-Information versehen werden.
Syntax in C/C++:
#pragma omp ordered
block
#pragma omp parallel for ordered
for( ..., ... , ... )
{
...
/* wait until the previous iteration */
/* has finished its ordered section */
#pragma omp ordered
...
}
Die master-Anweisung kennzeichnet Quellcode, der nur vom Masterthread des Threadpools ausgeführt werden darf.
#pragma omp master
Im Gegensatz zu anderen work-sharing-Anweisungen, muss der in ein
master -Konstrukt gekapselte Block nicht von allen Threads erreicht
werden und es existiert keine implizite Barriere am Ende.
ANHANG A. PARALLEL PROGRAMMING IN OPENMP
81
Individuelle Synchronisation
OpenMP unterstützt den Einsatz von eigens erstellten Synchronisationskonstrukten durch einfache Lade-/Speicherzugriffe auf gemeinsame Speicherbereiche [16, Abschnitt 5.5].
A.6
Performance
Die durch Parallelisierung erreichbare Performance wird durch fünf Kriterien
maßgeblich beeinflusst.
Coverage
Unter coverage“ (dt. Umfang, Deckung) ist der prozentuelle Anteil von
”
parallelem Code im Quellcode der Applikation zu verstehen. Um Leistungssteigerungen durch Parallelisierung erzielen zu können, muss ein ausreichender Teil des Codes parallelisiert werden. Mit steigender Prozessorenanzahl
wird die erzielbare Leistungssteigerung maßgeblich vom Anteil des seriellen
Codes beeinflusst, wie es im Gesetz von Amdahl mathematisch festgehalten
wird (vergleiche Abschnitt 1.2).
Unabhängig von effizienter Umsetzung der parallelisierbaren Quellcodeabschnitte, wird die Performance-Zunahme der Applikation durch frn
sequentiell verbleibenden Code eingeschränkt. Je mehr Prozessoren p involviert sind, desto spürbarer ist diese Leistungsbarriere. Wenngleich eine hohe
Flächendeckung allein kein Garant für eine Verbesserung der Ausführungsgeschwindigkeit eines Programmes darstellt, sollte versucht werden, dass
der parallele Anteil des Quellcodes überwiegt. Im Idealfall sollte bereits im
Codedesign eine Parallelisierung eingeplant werden.
Granularity
Die granularity“ (dt. Granularität, Körnung) gibt Aufschluss darüber, wie
”
viel Arbeit in jeder parallelen Region geleistet wird. Mit jeder parallelen
Region respektive Schleife wird für die parallele Ausführung (Aufteilung
der Arbeit auf einzelne Threads am Anfang und Synchronisation am Ende)
ein gewisser Overhead in Kauf genommen. Durch Parallelisierung gewonnene Performance wird durch Cache- und Synchronisationseffekte verringert. Diese Kosten-Nutzen-Relation im Auge behaltend, sollten Codeteile
nur dann parallelisiert werden, wenn eine signifikante Zeitersparnisse erwartet werden kann (der Aufwand kann durch leere parallele Regionen respektive Schleifen getestet werden).
ANHANG A. PARALLEL PROGRAMMING IN OPENMP
82
Load Balancing
Unter guter oder schlechter Lastverteilung (engl. load balancing) ist gemeint, wie die Arbeit auf verschiedene Recheneinheiten aufgeteilt ist.
Da eine parallelisierte Schleife erst dann beendet werden kann, wenn der
letzte Thread seine Arbeit beendet hat, ist die erzielbare Geschwindigkeit
vom langsamsten Thread abhängig. Um eine ausgeglichene Lastverteilung
zu realisieren, stellt OpenMP sowohl statische 8 als auch dynamische9 Arbeitsaufteilung zur Wahl. Abhängig davon, ob Threads gleichzeitig oder
bereits zeitlich different10 auf eine parallele Schleife treffen und ob geringe
oder hohe Abweichungen in der gemessenen Ausführungszeit der einzelnen
Schleifendurchläufe bestehen, muss der Einsatz von statischem oder dynamischem Scheduling individuell entschieden werden.
Locality
Lokalität verweist auf den Aufwand, Information zwischen verschiedenen
Prozessoren auf dem darunter liegenden System zu teilen. Caches sind dazu
entwickelt, Datenlokalität auszunutzen. Auf Singlecore-Systemen muss der
Anwender versuchen, dass multiple Referenzen auf gleiche oder naheliegende
Speicherbereiche zeitlich kurz aufeinanderfolgen. Bei Multicore-Systemen
muss darüber hinaus sicher gestellt werden, dass andere Prozessoren nicht
gleichzeitig auf eine bestimmte Cachezeile zugreifen. Zwei Hauptfaktoren
beeinflussen die Ausprägung von Lokalitätseffekten: die Größe des Datensatzes der Prozessoren (große Datensätze sind im Vergleich zu kleinen Datensätzen weniger von dem Zusammenhang zwischen Lokalität und Scheduling betroffen); das Ausmaß an Wiederverwendung von abgearbeitetem
Code (Wiederverwendung innerhalb eines parallelen Arbeitsteils verringert
die Bedeutung des Scheduling).
False sharing und inconsistent parallelization sind zwei Situationen, die
aus Lokalitätsproblemen folgen können.
Synchronisation
Synchronisationsmechanismen verweisen auf den Aufwand, Information zwischen verschiedenen Prozessoren konsistent auszutauschen. OpenMP stellt
hierfür verschiedene Anweisungen zur Verfügung, die sich vor allem bei unbedachtem Einsatz negativ auf die Performance auswirken können.
8
static scheduling: jeder Thread erhält zu Beginn n = Schleifendurchläufe/Threads an
Schleifendurchläufen zugeteilt.
9
dynamic scheduling: jeder Thread bekommt zu Beginn zunächst nur einen Schleifenteil
und erst nach Abarbeitung einen weiteren verbleibenden Schleifendurchlauf, bis keine
Arbeit mehr verfügbar ist
10
Zeitliche Differenz entsteht durch Beendigung einer vorhergehenden parallelen Region
mit der nowait-Klausel.
ANHANG A. PARALLEL PROGRAMMING IN OPENMP
83
barriers: Fehlt entsprechende Unterstützung, kann der Einsatz von
Barrieren je nach zugrunde liegendem System zeitaufwändig sein.
mutual exclusions: Diese Form der Synchronisation ist in der Regel
zeitaufwändiger als eine Barriere.
Anhang B
Screenshots Cube
An dieser Stelle sind die für den Effizienztest der Cube-Engine herangezogenen Positionen im Spiel (sortiert nach Testreihenfolge) dokumentiert.
B.1
templemount
Abbildung B.1: Map: templemount – Position 1.
84
ANHANG B. SCREENSHOTS CUBE
Abbildung B.2: Map: templemount – Position 2.
Abbildung B.3: Map: templemount – Position 3.
85
ANHANG B. SCREENSHOTS CUBE
B.2
island pre
Abbildung B.4: Map: island pre – Position 1.
Abbildung B.5: Map: island pre – Position 2.
86
ANHANG B. SCREENSHOTS CUBE
Abbildung B.6: Map: island pre – Position 3.
Abbildung B.7: Map: island pre – Position 4.
87
ANHANG B. SCREENSHOTS CUBE
B.3
fox
Abbildung B.8: Map: fox – Position 1.
Abbildung B.9: Map: fox – Position 2.
88
ANHANG B. SCREENSHOTS CUBE
Abbildung B.10: Map: fox – Position 3.
Abbildung B.11: Map: fox – Position 4.
89
ANHANG B. SCREENSHOTS CUBE
B.4
metl3
Abbildung B.12: Map: metl3 – Position 1.
Abbildung B.13: Map: metl3 – Position 2.
90
ANHANG B. SCREENSHOTS CUBE
Abbildung B.14: Map: metl3 – Position 3.
B.5
douze
Abbildung B.18: Map: douze – Position 1.
91
ANHANG B. SCREENSHOTS CUBE
Abbildung B.15: Map: metl3 – Position 4.
Abbildung B.19: Map: douze – Position 2.
92
ANHANG B. SCREENSHOTS CUBE
Abbildung B.16: Map: metl3 – Position 5.
Abbildung B.20: Map: douze – Position 3.
93
ANHANG B. SCREENSHOTS CUBE
Abbildung B.17: Map: metl3 – Position 6.
Abbildung B.21: Map: douze – Position 4.
94
ANHANG B. SCREENSHOTS CUBE
Abbildung B.22: Map: douze – Position 5.
Abbildung B.23: Map: douze – Position 6.
95
Anhang C
Inhalt der CD-ROM
File System: Joliet
Mode: Single-Session (CD-ROM)
C.1
Diplomarbeit
Pfad:
/diplomarbeit/
da dm05006.pdf . . . .
da dm05006.ps . . . . .
C.2
Quellcode
Pfad:
/quellcode/
cube.rar . . . . . . . . .
ode-0.8.rar . . . . . . .
techdemo.rar . . . . . .
Diplomarbeit (PDF–File)
Diplomarbeit (PS–File)
Projekt Cube
Projekt Ode–Demo
Projekt Techdemo
C.3
Online–Quellen
Pfad:
/literatur/
. . . . . . . . . . . . . .
C.4
Sonstiges
Pfad:
/
images/ . . . . . . . . .
Dokumente und Bildmaterial
Bilder und Grafiken
96
Literaturverzeichnis
[1] Advanced Micro Devices, Inc.: Multicore processors - the next
evolution in computing. URL, http://multicore.amd.com/Resources/
33211A Multi-Core WP en.pdf, 2005. Kopie als PDF (33211A MultiCore WP en.pdf), abgerufen im März 2007.
[2] Advanced Micro Devices, Inc.:
AMD Desktop Processor
Benchmarks. URL, http://www.amd.com/us-en/assets/content type/
DownloadableAssets/AMD Athlon 64 X2 Benchmarks May06.pdf, 2006.
Kopie als PDF (AMD Athlon 64 X2 Benchmarks May06.pdf), abgerufen im März 2007.
[3] Advanced Micro Devices, Inc.:
AMD Athlon 64 FX
Prozessor Modell-Nummern und Merkmalsvergleich.
URL,
http://www.amd.com/de-de/Processors/ProductInformation/0,
Kopie als PDF (Ver,30 118 9485 9488%5E10756,00.html, 2007.
gleich der Modell-Nummern.pdf, abgerufen im Juni 2007.
[4] Advanced Micro Devices, Inc.: AMD Athlon 64 FX Prozessor Schlüssel-Architekturmerkmale. URL, http://www.amd.com/us-en/
assets/content type/Additional/41039A 01 AFX diag.jpg, 2007. Kopie
als JPG (41039A 01 AFX diag.jpg), abgerufen im Mai 2007.
[5] Advanced Micro Devices,
Inc.:
Dual–core Diagram.
URL,
http://www.amd.com/us-en/assets/content type/Additional/
33635BDual-CoreDiagramFIN.swf, 2007. Kopie als SWF (33635BDualCoreDiagramFIN.swf), abgerufen im Mai 2007.
[6] Advanced Micro Devices, Inc.: Near–Term Product Outlook .
URL, http://www.amdcompare.com/techoutlook/, 2007. Kopie als PDF
(3-Year-Print-Processors.pdf), abgerufen im März 2007.
[7] Advanced Micro Devices, Inc.:
Quad–Core Upgradefähigkeit. URL, http://www.amd.com/de-de/Processors/ProductInformation/
0,,30 118 13703 14598,00.html, 2007. Kopie als PDF (Quad-CoreUpgradefaehigkeit.pdf), abgerufen im Mai 2007.
97
LITERATURVERZEICHNIS
98
[8] Aitken, P., T. Anderson, S. Apiki, A. Bailey, A. McNaughton, A. Morales, J. O’Brien, Larry Whitney und A. Zeichick: Surviving and Thriving in a Multi-Core World.
URL,
http://developer.amd.com/resourcecenter mc.jsp, 2006. Kopie als PDF
(ThrivingandSurvivinginaMulti-CoreWorld.pdf), abgerufen im März
2007.
[9] Altavilla, D.: AMD Athlon FX . URL, http://www.hothardware.
com/articleimages/Item767/blockdiag.png, 2007. Kopie als PNG (blockdiag.png), abgerufen im Mai 2007.
[10] Andrews, G. R.: Foundations of Multithreaded, Parallel, and Distributed Programming. Addison-Wesley, 2000. ISBN 0-201-35752-6.
[11] Benini, L. und G. De Micheli: The Challenges of Nextgen Multicore Networks-on-chip Systems. URL, http://www.ddj.com/dept/64bit/
197003159, 2007. Kopie als PDF (The Challenges of Nextgen Multicore
Networks-on-chip Systems.pdf), abgerufen im März 2007.
[12] Bursky, D.: Intel CTO: Multicore Performance Standards Needed.
URL, http://www.ddj.com/dept/64bit/192205025, 2006. Kopie als PDF
(Intel CTO Multicore Performance Standards Needed.pdf), abgerufen
im März 2007.
[13] Bursky, D.: Multicore Solutions Proliferating. URL, http://www.ddj.
com/dept/64bit/192501596, 2006. Kopie als PDF (Multicore Solutions
Proliferating.pdf), abgerufen im März 2007.
[14] Carleton, G. und W. Shands: Performance Analysis and Multicore
Processors. URL, http://www.ddj.com/dept/debug/184417069, 2006.
Kopie als PDF (Performance Analysis and Multicore Processors.pdf),
abgerufen im März 2007.
[15] Case, L.: Double Double: Intel Core 2 Extreme QX6700 . URL, http://
www.extremetech.com/print article2/0,1217,a=192948,00.asp, 2006. Kopie als PDF (double double.pdf), abgerufen im März 2007.
[16] Chandra, R., L. Dagum, D. Kohr, D. Maydan, J. McDonald
und R. Menon: Parallel programming with OpenMP . Morgan Kaufmann Publisher, 2001. ISBN 1-55860-671-8.
[17] Cole, B.: EEMBC Unveils Plans for Multicore Benchmarks. URL,
http://www.ddj.com/dept/64bit/197007730, 2007.
Kopie als PDF
(EEMBC Unveils Plans for Multicore Benchmarks.pdf), abgerufen im
März 2007.
LITERATURVERZEICHNIS
99
[18] Crawford, C.: Where’s The Software To Catch Up To Multicore
Computing? . URL, http://www.ddj.com/dept/64bit/197001193, 2007.
Kopie als PDF (Where’s The Software To Catch Up To Multicore.pdf),
abgerufen im März 2007.
[19] De Gelas, J.: The Quest for More Processing Power, Part One:
Is the single core CPU doomed? . URL, http://www.anandtech.com/
cpuchipsets/showdoc.aspx?i=2343, February 2005. Kopie als PDF (AnandTech - Quest for More Processing Power - Part 1.pdf), abgerufen
im Februar 2007.
[20] De Gelas, J.: The Quest for More Processing Power, Part Three:
Multi-core of Intel and AMD compared. URL, http://www.anandtech.
com/showdoc.aspx?i=2419&p=1, Mai 2005. Kopie als PDF (AnandTech
- Quest for More Processing Power - Part 3.pdf), abgerufen im Februar
2007.
[21] De Gelas, J.: The Quest for More Processing Power, Part Two:
Multi-core and multi-threaded gaming. URL, http://www.anandtech.
com/cpuchipsets/showdoc.aspx?i=2377, March 2005. Kopie als PDF
(AnandTech - Quest for More Processing Power - Part 2.pdf), abgerufen im Februar 2007.
[22] Domeika, M.: Development and Optimization Techniques for Multicore Processors. URL, http://www.ddj.com/dept/64bit/192501977,
2006. Kopie als PDF (Development and Optimization Techniques for
Multicore.pdf), abgerufen im März 2007.
[23] Erickson, J.: Two For the Price of One-Maybe. URL, http://www.
ddj.com/dept/architect/184406087, 2005. Kopie als PDF (Two For the
Price of One-Maybe.pdf), abgerufen im März 2007.
[24] Fujimoto, S.: Designing low-power multiprocessor chips. URL,
http://www.embedded.com/showArticle.jhtml?articleID=197005272,
2007. Kopie als PDF (Designing low-power multiprocessor chips.pdf),
abgerufen im März 2007.
[25] Garrett, R.: Designing Custom Embedded Multicore Processors.
URL, http://www.ddj.com/dept/64bit/197002463, 2007. Kopie als PDF
(Designing Custom Embedded Multicore Processors.pdf), abgerufen im
März 2007.
[26] Garver, S. und B. Crepps: The New Era of Tera–scale Computing. URL, http://www.intel.com/cd/ids/developer/asmo-na/eng/
294188.htm?prn=Y, 2006. Kopie als PDF (The New Era of Tera-scale
Computing.pdf), abgerufen im März 2007.
LITERATURVERZEICHNIS
100
[27] Gaudin, S.: The Quad-Core War Begins: AMD Launches Quad FX .
URL, http://www.ddj.com/dept/64bit/196600649, 2006. Kopie als PDF
(The Quad-Core War Begins AMD Launches Quad FX.pdf), abgerufen
im März 2007.
[28] Gaudin, S.: Intel Unveils Three More Quad-Core Processors. URL,
http://www.ddj.com/dept/64bit/196801864, 2007. Kopie als PDF (Intel
Unveils Three More Quad-Core Processors.pdf), abgerufen im März
2007.
[29] Gonsalves, A.: Lots Of Work Awaits Intel As It Pushes 80-Core Processors Out Of Experimental Stage. URL, http://www.ddj.com/dept/
64bit/197006948, 2007. Kopie als PDF (Lots Of Work Awaits Intel As
It Pushes 80-Core.pdf), abgerufen im März 2007.
[30] Gorlatch, S.: Parallele Systeme. URL, http://pvs.uni-muenster.de:
8014/pvs/lehre/SS06/ps/folien/1-06Print.pdf, 2006. Kopie als PDF (106Print.pdf), abgerufen im Mai 2007.
[31] Gruener, W.: AMD launches 4x4 platform Quad FX . URL, http://
www.tgdaily.com/content/view/30000/135/, 2006. Kopie als PDF (TG
Daily - AMD launches 4x4 platform Quad FX.pdf), abgerufen März
2007.
[32] Gruener, W.: Intel unleashes quad-core processors. URL, http://
www.tgdaily.com/content/view/29737/135/, 2006. Kopie als PDF (Intel
unleashes quad-core processors.pdf), abgerufen im März 2007.
[33] Gruener, W.: AMD’s first quad-core to ship in late summer .
URL, http://www.tgdaily.com/content/view/31139/118/, 2007. Kopie
als PDF (TG Daily - quad-core to ship in late summer.pdf), abgerufen
im März 2007.
[34] Gustafson, J.: Reevaluating Amdahl’s Law . URL, http://www.scl.
ameslab.gov/Publications/Gus/AmdahlsLaw/Amdahls.pdf, 1988. Kopie
als PDF (Amdahls.pdf), abgerufen im Mai 2007.
[35] Hughes, C.: Object Oriented Multithreading Using C++. Wiley, 1997.
ISBN 0-471-18012-2.
[36] Intel Coorporation: Intel Core 2 Extreme Processor . URL, http://
www.intel.com/products/processor/core2XE/prod brief.pdf, 2006. Kopie
als PDF (dualcore prod brief.pdf), abgerufen im März 2007.
[37] Intel Coorporation: Intel Core 2 Extreme Quad-Core Processor . URL, http://www.intel.com/products/processor/core2XE/qc prod
brief.pdf, 2006. Kopie als PDF (qc prod brief.pdf), abgerufen im März
2007.
LITERATURVERZEICHNIS
101
[38] Intel Coorporation: Intel Core 2 Duo Processor Architecture. URL, http://www.intel.com/technology/architecture/coremicro/
demo/demo.htm, 2007. Kopie als SWF (demo.swf), abgerufen im März
2007.
[39] Intel Coorporation: Intel Desktop Processor Comparison Chart.
URL, http://compare.intel.com/pcc/default.aspx?familyID=1&culture=
en-US, 2007. Kopie als PDF (Intel Product Comparison Chart.pdf),
abgerufen im März 2007.
[40] Intel Coorporation: Intel Multicore. URL, http://www.intel.com/
multi-core/, 2007. Abgerufen im März 2007.
[41] Intel Coorporation: Multi–Core Developer Community. URL,
http://www3.intel.com/cd/ids/developer/asmo-na/eng/328626.htm,
2007. Abgerufen im März 2007.
[42] Johnson, R. C.: Intel’s Teraflops Chip: An Architecture That’s ’Outside The Box’ . URL, http://www.ddj.com/dept/64bit/197005306, 2007.
Kopie als PDF (Intel’s Teraflops Chip An Architecture That’s Outside.pdf), abgerufen im März 2007.
[43] Kugler, A.: Leistungssteigerung: DirectX 10- Grafik und Mehrkernprozessoren. URL, http://www.chip.de/artikel/c1 artikelunterseite
24506153.html, 2007. Kopie als PDF (CHIP Leistungssteigerung.pdf),
abgerufen im März 2007.
[44] Lau, O.: Amdahl, Moore und Gustafson. c’t Magazin, 15:214–225,
2006.
[45] Littschwager, T.: Der 4-Kern-Prozessor im ersten Test. URL,
http://www.chip.de/bildergalerie/c1 bildergalerie v1 22510498.html,
2006. Abgerufen im Mai 2007.
[46] Lyman, J.: Performance Soars Amid Intel-AMD Dual-Core Duel.
http://www.technewsworld.com/story/42792.html, 2005. Kopie als
PDF (Performance Soars Amid Intel-AMD Dual-Core Duel.pdf), abgerufen im März 2007.
[47] Mattson, T. G., B. A. Sanders und B. L. Massingill: Patterns for Parallel Programming. The software patterns series. AddisonWesley, First Aufl., 2005. ISBN 0-321-22811-1.
[48] Merrit, R.: Consortium Preps Multicore CPU Benchmarks. URL,
http://www.ddj.com/dept/64bit/196603811, 2006. Kopie als PDF (Consortium Preps Multicore CPU Benchmarks.pdf), abgerufen im März
2007.
LITERATURVERZEICHNIS
102
[49] Merrit, R.: Multicore faces a long road. URL, http://www.embedded.
com/showArticle.jhtml?articleID=196702526, 2006.
Kopie als PDF
(Embedded- Multicore faces a long road.pdf), abgerufen im Juni 2007.
[50] Neitzel, D.: Intel zeigt Teraflop–CPU mit 80 Kernen. URL, http:
//www.chip.de/news/c news druckansicht 24211357.html, 2007. Kopie
als PDF (Intel zeigt Teraflop-CPU mit 80 Kernen.pdf), abgerufen im
Mai 2007.
[51] Neuendorf, O.: Windows Multithreading. mitp, 2003. ISBN 3-82660989-1.
[52] Oortmerssen, W. van: Cube. URL, http://www.cubeengine.com,
2005. Abgerufen im Mai 2007.
[53] Raby, M.: Price/Performance Charts. URL, http://www.tgdaily.com/
images/stories/article images/processor performance 032307/graph1.jpg,
2007. Kopie als JPG (Price Performance Charts.jpg), abgerufen im
Mai 2007.
[54] Ramanathan, R.: Intel Multi-Core Processors.
URL, http:
//www.intel.com/technology/architecture/downloads/quad-core-06.pdf,
2006. Kopie als PDF (quad-core-06.pdf), abgerufen im März 2007.
[55] Reynolds, M., V. Thurner und R. Wirt: Transition to Multi–
Core: The Time Is Now . URL, http://cache-www.intel.com/cd/00/00/
23/54/235413 235413.pdf, 2005. Kopie als PDF (235413 235413.pdf),
abgerufen im Mai 2007.
[56] Schikuta, E. und T. Weishäupl: Aurora. URL, http://www.cs.
univie.ac.at/project.php?pid=61, 2007. Abgerufen im Mai 2007.
[57] Szydlowski, C.: Multithreaded Technology and Multicore Processors.
Dobb’s Journal, 30:58–60, 2005. Kopie als PDF (220943 220943.pdf),
abgerufen im Mai 2007.
[58] Tom, C.:
Athlon 64 FX60 Review: FX Goes Dual Core.
URL,
http://www.amdzone.com/modules.php?op=modload&name=
Sections&file=index&req=printpage&artid=222, 2006. Kopie als JPG
(64architecture.jpg), abgerufen im Mai 2007.
[59] Visarius, D. und C. Yerli: Crytek im /dev-Talk . /GameStar/dev,
01:44–47, 2005.
[60] Wilson, G. V.: Deja Parallel All Over Again. URL, http://www.ddj.
com/dept/architect/184407780, 2005. Kopie als PDF (Deja parallel all
over again.pdf), abgerufen im März 2007.