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.