Shared-Memory Parallelisierung von C++ Programmen



Ähnliche Dokumente
Evaluation. Einleitung. Implementierung Integration. Zusammenfassung Ausblick

DAP2-Programmierpraktikum Einführung in C++ (Teil 2)

Objektorientierte Programmierung II

Master-Thread führt Programm aus, bis durch die Direktive

Programmierung mit C Zeiger

Mapra: C++ Teil 4. Felix Gruber. 6. Mai IGPM, RWTH Aachen. Felix Gruber (IGPM, RWTH Aachen) Mapra: C++ Teil 4 6.

C++ Teil 6. Sven Groß. 27. Mai Sven Groß (IGPM, RWTH Aachen) C++ Teil Mai / 14

Programmierkurs C++ Templates & STL (1/2)

C++ Teil 7. Sven Groß. 30. Nov Sven Groß (IGPM, RWTH Aachen) C++ Teil Nov / 13

Das erwartet dich in diesem Buch 8. Kapitel 1 Aufbruch ins Programmierabenteuer 14

Verschlüsseln eines Bildes. Visuelle Kryptographie. Verschlüsseln eines Bildes. Verschlüsseln eines Bildes

Crashkurs C++ - Teil 1

Einige Grundlagen zu OpenMP

Dynamische Datentypen. Destruktor, Copy-Konstruktor, Zuweisungsoperator, Dynamischer Datentyp, Vektoren

C++ Teil 5. Sven Groß. 13. Mai Sven Groß (IGPM, RWTH Aachen) C++ Teil Mai / 18

Pthreads. David Klaftenegger. Seminar: Multicore Programmierung Sommersemester

Einführung in die Programmierung mit C++

Probeklausur: Programmierung WS04/05

Programmieren in C++

C++ Teil 9. Sven Groß. 17. Juni Sven Groß (IGPM, RWTH Aachen) C++ Teil Juni / 17

Konzepte der parallelen Programmierung

Programmieren in Java -Eingangstest-

Assoziative Container in C++ Christian Poulter

C++ Teil 12. Sven Groß. 18. Jan Sven Groß (IGPM, RWTH Aachen) C++ Teil Jan / 11

1. Übung zu "Numerik partieller Differentialgleichungen"

Objektorientiertes Programmieren in C++

Shared-Memory Parallelisierung von C++ Programmen

Multi- und Many-Core

Liste MI / Liste I Programmieren in C++

Objektorientierte Programmierung. Kapitel 22: Aufzählungstypen (Enumeration Types)

Software Entwicklung 1

6 ZEIGER UND REFERENZEN - ALLGEMEINES

Informationsverarbeitung im Bauwesen

1. Einführung in OpenMP

Programmieren in C/C++ und MATLAB

2. Programmierung in C

Organisatorisches. Folien (u.a.) auf der Lva-Homepage Skriptum über MU Online

4 Objektorientierte Programmierung mit Java 4.1 Java-Grundlagen

OpenMP. Viktor Styrbul

C++ Teil 10. Sven Groß. 17. Dez IGPM, RWTH Aachen. Sven Groß (IGPM, RWTH Aachen) C++ Teil Dez / 14

Tag 8 Repetitorium Informatik (Java)

HSR Rapperswil 2001 Markus Rigling. Programmieren: Vererbung. 1 Variante 2

Vorlesung Objektorientierte Programmierung Klausur

Teil 8: Dynamische Speicherverwaltung. Prof. Dr. Herbert Fischer Fachhochschule Deggendorf Prof. Dr. Manfred Beham Fachhochschule Amberg-Weiden

C/C++-Programmierung

Java-Grundkurs für Wirtschaftsinformatiker

OpenCL. Programmiersprachen im Multicore-Zeitalter. Tim Wiersdörfer

Organisatorisches. Folien (u.a.) gibt's auf der Lva-Homepage zum Download

4. Objektorientierte Programmierung mit C++

Übungsaufgaben: 1. Objektorientierte Programmierung - Teil 1

Java als erste Programmiersprache

Einführung in die STL

Creational Patterns. Seminar Software-Entwurf. Thomas Liro WS 2004/05.

Prinzipien der objektorientierten Programmierung (OOP)

Java Referenzdatentypen genauer betrachtet

Einführung in die Informatik: Programmierung und Software-Entwicklung, WS 11/12 1. Kapitel 11. Listen. Listen

kurze Wiederholung class templates

Vorlesung Programmieren

Übersicht. Speichertypen. Speicherverwaltung und -nutzung. Programmieren in C

Typsystem Plattform- und Sprachenunabhängigkeit in.net

Tag 4 Repetitorium Informatik (Java)

Inhaltsverzeichnis. Kapitel i: Schnelleinstieg 13. Kapitel 2: Was sind Programme? 17. Kapitel 3: Wie erstellt man eigene Programme?

Abschnitt 10: Datenstrukturen

Überblick. 6. Konstruktor und Destruktor - obligatorische Elementfunktionen einer Klasse

Durch die Teil-von-Beziehung soll ausgedrückt werden, dass ein Objekt A als (physikalischer) Teil eines Objekts B angesehen wird. Insbesondere kann ei

5.4 Arrays. Oft müssen viele Werte gleichen Typs gespeichert werden. Idee: Lege sie konsekutiv ab! Greife auf einzelne Werte über ihren Index zu!

Memory Models Frederik Zipp

Aufbau von Klassen. class punkt {...

Theorie zu Übung 8 Implementierung in Java

Neben der Verwendung von Klassen ist Vererbung ein wichtiges Merkmal objektorientierter

Praxisorientierte Einführung in C++ Lektion: "Allgemeines"

Inhalt. 4.5 Arbeit mit Zeigern (engl. Pointer)

Das Einsteigerseminar Objektorientierte Programmierung in Java

TEIL I: OBJEKTORIENTIERUNG UND GRUNDKURS JAVA GRUNDLAGEN DER PROGRAMMIERUNG... 4

Grundlagen der OO- Programmierung in C#

Entwurfsmuster und Frameworks Singleton

Klassen als Objekte. Smalltalk vs. Objective-C. Self-Nachrichten an Klassen in Objective-C. Klassen als Objekte. Smalltalk: Everything is an object

Transkript:

Shared-Memory Parallelisierung von C++ Programmen 9. Februar 2006

1 Übersicht Ergebnisse Zusammenfassung 2 3

Übersicht Ergebnisse Zusammenfassung Übersicht Verbreitete Parallelisierungstechniken für Shared-Memory: OpenMP: Direktiven-gesteuerte Shared-Memory Parallelisierung für C, C++ und FORTRAN. Posix-Threads: Shared-Memory (System-)Bibliothek mit umfangreicher Funktionalität, sprachunabhängig. UPC: Übermenge von C99 mit Distributed-Shared-Memory Modell, kann mit C++ kombiniert werden. MPI-2: wurde hier nicht betrachtet. Betrachtung der Parallelisierung des STREAM-Benchmarks und der DROPS Programmkernel. Vergleichskriterien: Vorgehensweise, Tool-Unterstützung. Performance und Aufwand. Performance nach Tuning.

Übersicht Ergebnisse Zusammenfassung Performance: STREAM-Benchmark 8 7 C OpenMP = 1 C Posix-Threads C UPC Ideal 6 5 Speedup 4 3 2 1 0 1 2 3 4 5 6 7 8 Threads

Übersicht Ergebnisse Zusammenfassung Aufwand für Tuning Opteron-Plattform hat ccnuma-eigenschaften: berücksichtigen! OpenMP: Initialisierungsschleife mit gleicher (paralleler) Arbeitsverteilung wie die Berechnung. Posix-Threads: Initialisierungsschleife. Thread-Pool zur Vermeidung des Aufwandes der Erstellung und Beendigung von Threads. CPU-Binding der Threads. UPC: ist kein Problem. Compiler erzeugt schlechten Code. Verschiedene manuelle Optimierungen im Code und im Compiler (beta) verwenden.

Übersicht Ergebnisse Zusammenfassung Performance: STREAM-Benchmark (tuned) 8 7 C OpenMP C Posix-Threads C UPC Ideal 6 5 Speedup 4 3 2 1 0 1 2 3 4 5 6 7 8 Threads

Übersicht Ergebnisse Zusammenfassung Zusammenfassung Alle drei Parallelisierungstechniken eignen sich für die Parallelisierung des STREAM-Benchmarks und DROPS. UPC bietet leistungsfähige Möglichkeiten der Datenplatzierung. Ad-Hoc Parallelisierung ggfs. aufwändig. Posix-Threads: Hohe Flexibilität, auch für die Programmierung irregulärer Codes geeignet. Anpassung der Arbeitsverteilung ist aufwändig und muss manuell programmiert werden. OpenMP: Schnellste Entwicklung und größter Benutzerkomfort. Nur minimale Codeänderungen notwendig, Möglichkeit zur seriellen Compilation besteht weiter. Tool-Unterstützung, z.b. Test auf serielle Äquivalenz.

1 Übersicht Ergebnisse Zusammenfassung 2 3

Thread-Safety Ein Code ist thread-safe, wenn er bei gleichzeitiger Ausführung durch mehrere Threads korrekt ist. Hier betrachtete STLs: Sun C++ und STLport (eng verwandt mit SGI STL), GNU C++ und Intels Modifikationen. Zwei Szenarien: Mehrere Threads greifen (schreibend) auf eine Instanz eines Datentypes zu. Mehrere Threads greifen (schreibend) auf mehrere Instanzen zu, nie mehr als ein Thread auf eine Instanz. Eine Funktion ist reentrant, wenn: sie nur Variablen vom Stack benutzt, sie nur von aktuellen Argumenten abhängt, alle aufgerufenen Funktionen es auch sind.

Ergebnis Die betrachteten STLs bieten ausschließlich reentrante Funktionen an. In der Sun STL und in STLport enthalten lediglich spezialisierte Allokatoren statische Variablen, die über Locking geschützt werden. Zusammenfassung: Ausschließlicher Lesezugriff ist sicher. Gleichzeitiger (schreibender) Zugriff mehrerer Threads auf mehrere unterschiedliche Instanzen ist sicher. Gleichzeitiger (schreibender) Zugriff mindestens eines Threads muss von der Applikation verwaltet werden.

Einleitung Einige Datentypen haben sich als ungünstig zur Verwendung auf NUMA-Architekturen erwiesen, z.b: std::valarray. Eine Verteilung der Daten ist nach dem erstmaligen Anfassen nicht mehr direkt möglich. Zwei Ansätze zur Verteilung der Daten: Nutzung von Fähigkeiten des Betriebssystems (hier: Solaris) um Codemodifikationen zu minimieren. Nutzung von C++ Sprachmitteln mit der Hoffnung auf Portabilität (First-Touch). Möglichkeiten zur Allokation dynamischen Speichers: Zeiger, Objekte aus Klassenbibliotheken (z.b. STL), Allgemeine Klassen.

Zeiger Inhaltsverzeichnis Im Allgemeinen leicht an eine vorgegebene Datenverteilung anzupassen: einzelner Zeiger auf Datenbereich kann explizit parallel initialisiert werden, Array von Zeigern wird parallel verteilt angelegt. Beispielcode ADI: Poisson-Löser. Berechnung erfolgt auf der Variablen gridpoint grid. Betriebssystem: Benutzung von pmadvise mit Strategie MADV ACCESS MANY. : Optimierung der Verteilung beim Anlegen des Feldes: // A l l o c a t i o n o f v a r i a b l e g r i d #pragma omp p a r a l l e l f o r f o r ( i n t i = 0 ; i < N; i ++) { g r i d [ i ] = new g r i d P o i n t ; }

Performance: ADI mit Initialisierungstechniken 4.5 4 original pmadvise parinit 3.5 3 Speedup 2.5 2 1.5 1 0.5 0 1 2 3 4 5 6 7 8 Threads

Objekte aus Klassenbibliotheken Falls Objekte keine Möglichkeit zur Beeinflussung der Speicherverwaltung anbieten: Einsatz von Betriebssystemfunktionalität, z.b. madvise(). Modifikation des Objektes, z.b. im Konstruktor. std::valarray bietet keine Möglichkeiten über seine Schnittstelle. Eine Modifikation der Allokationstechnik wurde durchgeführt, ist aber nicht zwischen Compilern portabel. std::vector bietet in weiten Teilen die gleiche Schnittstelle, erlaubt aber zusätzlich die Angabe eines Allokators. Ein Allokator ist eine Klasse, die Aufgaben der Speicherverwaltung übernimmt, und eine von der STL vorgegebene Schnittstelle implementiert. Beim Austausch von std::valarray mit std::vector ist auf die Performance zu achten.

Allokator-Framework Framework zur Evaluation verschiedener Allokationstechniken: Allokator: implementiert die von der STL geforderte Schnittstelle, verwendet HeapManager. HeapManager: verwaltet Speicher, der vom HeapLayer allokiert wurde. HeapLayer: allokiert physikalischen Speicher durch Systemoder Bibliotheksaufrufe. DistributedHeapAllocator: implementiert einen Allokator, der Speicher direkt nach der Allokation mittels OpenMP parallel mit null beschreibt. Das Scheduling wird durch einen Templateparameter festgelegt. Der DistributedHeapAllocator kann mit std::vector verwendet werden.

Performance: STREAM-Benchmark mit Verteilung 8 7 C++ OpenMP, madvise C++ OpenMP, vector Ideal C++ OpenMP, myvalarray 6 5 Speedup 4 3 2 1 0 1 2 3 4 5 6 7 8 Threads

Performance: Routine y Ax aus DROPS (sparse MatVec) 4 3.5 OpenMP OpenMP dist-vector Ideal 3 2.5 Speedup 2 1.5 1 0.5 0 1 2 3 4 Threads

Allgemeine Klassen Über ein Mixin kann die Speicherverwaltung einer Klasse modifiziert werden, wie in [Berger & Zorn + 01] vorgestellt. template<c l a s s Object, c l a s s HeapManager> c l a s s PerClassHeapMixin : p u b l i c Object { p u b l i c : i n l i n e void operator new ( s i z e t s z ) { r e t u r n getheap ( ). m a l l o c ( s z ) ; } s t a t i c HeapManager & getheap ( ) { s t a t i c HeapManager theheap ; r e t u r n theheap ; } [... ] } ; Die Allokation von Speicher wird vom HeapManager verwaltet.

Übersicht Bei der Verwendung von Datenstrukturen, die viele kleine Objekte anlegen, wurden Probleme beim Speichermanagement unter Solaris und Windows festgestellt. Hier: Betrachtung von std::map, aber auch mit std::list und ähnlichen Datentypen können ähnliche Probleme auftreten. Unter Solaris werden spezielle Allokatoren zur effizienten seriellen Allokation verwendet. Lösungsansätze: Verwendung einer Betriebssystem-nahen Bibliothek zur parallelen Allokation, z.b. libmtmalloc. Modifikation der Speicherverwaltung seitens der Anwendung durch Anforderung größerer Blöcke. std::map erlaubt die Angabe eines Allokators.

Performance: MAP-Benchmark 6 5 Linux = 1 Linux, Chunk-Allokator Solaris Solaris, mtmalloc Solaris, mtmalloc, Chunk-Allokator Windows Windows, Chunk-Allokator Speedup 4 3 2 1 0 1 2 3 4 Threads

Performance: Routine SetupSystem1 aus DROPS 4 3.5 Linux, malloc Linux, Chunk-Allokator Solaris, mtmalloc Solaris, Chunk-Allokator + mtmalloc Ideal 3 2.5 Speedup 2 1.5 1 0.5 0 1 2 3 4 Threads

Einleitung Parallelisierung eines High-Level Codes: Interne Parallelisierung. Externe Parallelisierung. Verwendung in Parallelisierung (Thread-Safety). PCG( const Mat& A, Vec& x, const Vec& b,... ) { Vec p ( n ), z ( n ), q ( n ), r ( n ) ; Mat A( n, n ) ; [... ] f o r ( i n t i = 1 ; i <= m a x i t e r ; ++i ) { q = A p ; double a l p h a = rho / ( p q ) ; x += a l p h a p ; r = a l p h a q ; [... ]

Objektorientierte Codes Es kann bereits aufwändig sein, seriell eine optimale Performance zu erreichen (Template Expressions). Interne Parallelisierung: abgeschlossene parallele Region innerhalb der Memberfunktionen. Vorteil: Keine Änderung der Schnittstelle, korrekte Verwendung garantiert. Nachteil: Aufwand beim Erzeugen und Beenden der Threads. Ebenfalls zur internen Parallelisierung zählen z.b. parallele Erweiterungen der STL. Diese können bei den betrachteten Codes aber nicht gewinnbringend eingesetzt werden. Externe Parallelisierung: parallele Region außerhalb der Memberfunktionen, innerhalb wird Orphaning eingesetzt. Vorteil: Verringerung des Aufwandes des Threadmanagements. Nachteil: Fehlerhafte Verwendung möglich.

Performance: objektorientierter PCG-Löser Intel C++ Compiler 9.0 auf Sun Fire V40z: Version 1 2 3 4 Intern 219 108 83,2 65,5 Intern (temp. Vektor) 218 108 82,5 65,1 Extern 216 107 82,3 65 C-Code 216 107 82,3 65 Sun C++ Studio10 Compiler auf Sun Fire E6900: Version 1 2 3 4 6 8 Intern 375 185 156 112 68 42 Intern (temp. Vektor) 427 211 178 128 78 47 Extern 320 161 141 97 61 37 C-Code 315 157 138 95 57 35

Parallelisierung von nicht OpenMP konformen Schleifen Bei Verwendung von Zeigern oder Iteratoren (Kapselung eines Zeigers in der STL) sind die entstehenden Schleifen nicht von der kanonischen Form, wie sie OpenMP fordert. Wichtigste Forderung: Berechnung der Schleifeniterationen im Vorraus möglich. Dies würde auch bei Iteratoren (distance()) und Zeigern (Zeigerarithmetik) gehen.

Parallelisierung von nicht OpenMP konformen Schleifen Ansatz 1: Erstellung einer parallelisierbaren Schleife: long l = 0, l S i z e = 0 ; f o r ( i t = l i s t 1. b e g i n ( ) ; i t!= l i s t 1. end ( ) ; i t ++) l S i z e ++; v a l a r r a y <CComputeItem > i t e m s ( l S i z e ) ; f o r ( i t = l i s t 1. b e g i n ( ) ; i t!= l i s t 1. end ( ) ; i t ++) { i t e m s [ l ] = &( i t ) ; l ++; } #pragma omp p a r a l l e l f o r d e f a u l t ( s h a r e d ) f o r ( long l = 0 ; l < l S i z e ; l ++) { i t e m s [ l ] >compute ( ) ; }

Parallelisierung von nicht OpenMP konformen Schleifen Ansatz 2: Intels Taskqueuing: #pragma i n t e l omp p a r a l l e l t a s k q { f o r ( i t = l i s t 2. b e g i n ( ) ; i t!= l i s t 2. end ( ) ; i t ++) { #pragma i n t e l omp t a s k { i t >compute ( ) ; } } // end f o r } // end omp p a r a l l e l

Parallelisierung von nicht OpenMP konformen Schleifen Ansatz 3: single nowait Technik: #pragma omp p a r a l l e l p r i v a t e ( i t ) { f o r ( i t = l i s t 3. b e g i n ( ) ; i t!= l i s t 3. end ( ) ; i t ++) { #pragma omp s i n g l e nowait { i t >compute ( ) ; } } // end f o r } // end omp p a r a l l e l

Performance: parallele Iteratorschleifen 40 35 30 Intel,Exp1,5x1000 Intel,Exp2,5x1000 Intel,Exp3,5x1000 Intel,Exp1,40x1000 Intel,Exp2,40x1000 Intel,Exp3,40x1000 Sun,Exp1,2560x1 Sun,Exp3,2560x1 Time (sec) 25 20 15 10 5 0 1 2 3 4 Threads

(1) Insgesamt war die Parallelisierung der betrachteten Anwendungsprogramme mit OpenMP erfolgreich. Bereits diskutiert: Schleifen mit Indexvariablen von anderem Typ als Integer. Schleifen über size t werden ebenfalls nicht akzeptiert, da vorzeichenlos, aber häufig verwendet. Die Privatisierung von Membervariablen wird nicht unterstützt. Dies wäre angenehm beim Chunk Allokator und bei der internen Parallelisierung. Eine Routine kann nicht erkennen, ob sie aus einem Worksharingkonstrukt aufgerufen wird. Dies wäre zur Garantierung der Korrektheit bei der externen Parallelisierung angenehm.

(2) Die OpenMP Spezifikation geht nicht in allen Punkten präzise auf die Programmiersprache C++ ein. Privatisierung von Klassen: OpenMP: Private Variablen sind nicht initialisiert. C++: Für eine Instanz einer Klasse muss immer ein Konstruktor aufgerufen werden. Der Intel C++ Compiler tut dies, der Sun C++ Compiler nicht. Dies führt zu schwer auffindbaren Fehlern. Viele Compiler haben Schwierigkeiten, C++ Konstrukte wie Exceptions, Templates, Vererbung im Zusammenhang mit OpenMP umzusetzen. Dies hat die Parallelisierung von DROPS gezeigt.

FIRE Inhaltsverzeichnis Das Image-Retrieval System FIRE wird am Lehrstuhl für Informatik 6 der RWTH Aachen von Thomas Deselaers und Daniel Keysers entwickelt. Ziele von FIRE: Vergleich von Bildmerkmalen (Features) Vergleich von Bildvergleichsmaßen (Distanzen) Untersuchung der Korrelation von Features Die Parallelisierung wurde durch die objektorientierte Programmierung stark unterstützt (Aufwand: ca. 4 Wochen). Die Programmiersprache C++ hat die Abhängigkeitsanalyse deutlich vereinfacht. Die Flexibilität der Entwicklung durfte nicht eingeschränkt werden. Bei der OpenMP Parallelisierung wurden nur wenige Zeilen Code hinzugefügt.

FIRE Inhaltsverzeichnis 30 27.5 batch() getscores() Beste Kombination Ideal 25 22.5 20 Speedup 17.5 15 12.5 10 7.5 5 2.5 0 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 Threads

1 Übersicht Ergebnisse Zusammenfassung 2 3

Zusammenfassung OpenMP lässt sich gut zur Parallelisierung von C++ Programmen einsetzen. Es bietet dem Programmierer den meisten Komfort im Vergleich mit Posix-Threads und UPC. Skalierbarkeit von OpenMP C++ Programmen ist nicht immer zwischen Hardwarearchitekturen, Betriebssystemen und Compilern portabel: Opteron: NUMA-Architektur und. Solaris und Windows: Speicherverwaltung. Effizienz der OpenMP Implementierung. Diese Punkte können mit der aktuellen OpenMP Spezifikation nicht direkt adressiert werden. Die C++ Programmiersprache bietet Lösungsansätze. Objektorientiertes Design kann ungewollte Abhängigkeiten im Code verhindern und die Abhängigkeitsanalyse erleichtern.

Ende Inhaltsverzeichnis Vielen Dank für Ihre Aufmerksamkeit.