• Keine Ergebnisse gefunden

Paralleles H¨ochstleistungsrechnen Dr. S. Lang, J. Pods Abgabe: 11. Dezember 2012, 12 Uhr, per E-mail an jurgis.pods@iwr.uni-heidelberg.de

N/A
N/A
Protected

Academic year: 2021

Aktie "Paralleles H¨ochstleistungsrechnen Dr. S. Lang, J. Pods Abgabe: 11. Dezember 2012, 12 Uhr, per E-mail an jurgis.pods@iwr.uni-heidelberg.de"

Copied!
5
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

IWR, Universit¨at Heidelberg Wintersemester 2012/13

Aufgabenblatt 6 5. Dezember 2012

Ubungen zur Vorlesung¨

Paralleles H¨ochstleistungsrechnen Dr. S. Lang, J. Pods

Abgabe: 11. Dezember 2012, 12 Uhr, per E-mail anjurgis.pods@iwr.uni-heidelberg.de

Ubung 12¨ Peterson Lock mit Threads (5 Punkte)

Betrachten Sie das folgende Programmsegment, in dem zwei Threads eine gemeinsame Variable inkrementieren. Nat¨urlich werden die Prozesse im Allgemeinen nicht sequentiell hintereinander aus- gef¨uhrt, weshalb man nicht das bei sequentieller Ausf¨uhrung zu erwartende Ergebnis 20 000 000 errei- chen wird:

1 p a r a l l e l i n c r e m e n t

2 {

3 c o n s t int s e c t i o n s = 1 0 0 0 0 0 0 0 ;

4 int c o u n t = 0;

5

6 P r o c e s s Π1 P r o c e s s Π2

7 { {

8 for (int i =0; i < s e c t i o n s ; i ++) for (int i =0; i < s e c t i o n s ; i ++)

9 { {

10 c o u n t += 1; c o u n t += 1;

11 } }

12 } }

13 }

Den kritischen Abschnitt (critical section, CS) kann man beispielsweise mit dem in der Vorlesung vor- gestellten Peterson-Algorithmus absichern. Das zeigt in unserer abstrakten Notation folgendes Listing:

1 p a r a l l e l i n c r e m e n t - p e t e r s o n

2 {

3 c o n s t int s e c t i o n s = 1 0 0 0 0 0 0 0 ;

4 int in1 = 0 , in2 = 0 , l a s t = 1;

5 int c o u n t = 0;

6

7 P r o c e s s Π1 P r o c e s s Π2

8 { {

9 for (int i =0; i < s e c t i o n s ; i ++) for (int i =0; i < s e c t i o n s ; i ++)

10 { {

11 in1 = 1; in2 = 1;

12 // * // *

13 l a s t = 1; l a s t = 2;

14 // * // *

15 w h i l e ( in2 l a s t == 1) ; w h i l e ( in1 l a s t == 2) ;

16 c o u n t += 1; c o u n t += 1; // CS

17 in1 = 0; in2 = 0;

18 } }

19 } }

20 }

Teilaufgabe (a)

Seit 2011 gibt es einen neuen C++-Standard, C++11. Dieser enth¨alt nun auch Threads in der Stan- dardbibliothek und hat zu diesem Zweck ein komplett aufconcurrent programming basierendes Spei- chermodell in die Sprache integriert. Die neuen Thread-Klassen basieren auf den PThreads aus C- Zeiten, haben jedoch eine echte (gew¨ohnungsbed¨urftige) C++11-Syntax bekommen. Beachten Sie zur Verwendung unbedingt die Hinweise am Ende des Blattes!

Auf der Homepage stehen Ihnen in einer zip-komprimierten Datei c++11threadtools.zip einige Hilfsdateien zur Verwendung der C++11-Threads zur Verf¨ugung. In der Datei checkpeterson.cc befindet sich eine Implementierung des oben angegebenen naiven Peterson-Locks. Das Programm k¨onnen Sie mit dem Befehlmake checkpetersonubersetzen. Testen Sie mit mindestens 5 Durchl¨¨ aufen,

(2)

ob die Variable countdas

”richtige“ Ergebnis liefert. Schauen Sie als n¨achstes im Makefile die Zeilen 4 und 5 an:

4 C F L A G S = - g - O0 - c - W a l l - std = c + + 1 1 - p t h r e a d

5 #C F L A G S = - O3 - c - std = c + + 1 1 - p t h r e a d

Kommentieren Sie Zeile 4 aus und Zeile 5 ein, und kompilieren Sie neu mit make clean und da- nachmake checkpeterson. Dadurch erzeugen Sie optimierten Code. Wiederholen Sie die Messungen.

Erhalten Sie im nicht-optimierten bzw. optimierten Fall die korrekten Ergebnisse? Haben Sie eine Erkl¨arung, warum der Peterson-Lock auch im nicht-optimierten Fall nicht funktioniert?

Teilaufgabe (b)

In der Datei membarrier.hh finden Sie eine sogenannte Memory Barrier, realisiert durch einen Assembler-Befehl. Mittels dieser Memory Barrier l¨asst sich die out-of-order-execution manuell be- einflussen bzw. verhindern. Zur Erinnerung: Mittels out-of-order-execution k¨onnen Maschinenbefehle vorgezogen werden, falls sich die zur Ausf¨uhrung ben¨otigten Daten bereits fertig berechnet im Speicher befinden. Die Memory Barrier kann nun vorgeben, in welcher Reihenfolge Befehle oder Speicherope- rationen ausgef¨uhrt werden:

1 O P _ 1 ;

2 . . .

3 O P _ n ;

4 P H L R :: m e m o r y B a r r i e r () ;

5 OP_ { n + 1 } ;

Die Memory Barrier erzwingt hier, dass die Operationen OP_1 bis OP_n vollst¨andig ausgef¨uhrt sind, bevorOP_{n+1}bearbeitet werden darf. F¨ugen Sie nun an den mit einem Stern// *gekennzeichneten Stellen eine Memory Barrier ein, und testen Sie erneut mehrere Male mit und ohne Optimierung (vor einem erneutenmakeunbedingtimmer einmake cleanausf¨uhren!). Wie verh¨alt sich Ihr Programm?

Diskutieren Sie kurz Ihre Beobachtungen.

Teilaufgabe (c)

Entfernen Sie die memBarrier()-Aufrufe nun wieder. In C/C++ k¨onnen Speicherbereiche so mar- kiert werden, daß die Reihenfolge von Lese- und Schreiboperationen auf diesen Speicherbereichen beim ¨Ubersetzen des Programms nicht ver¨andert werden darf. Dies geschieht mit dem Schl¨usselwort volatile. Lese- und Schreiboperationen aufvolatile-Variablen gelangen in exakt im Programm ste- hender Reihenfolge in das ¨ubersetzte Programm und d¨urfen nicht wegoptimiert werden. Machen Sie die vier gemeinsamen Variablen in[0], in[1], last und count zu volatile-Variablen. ¨Ubersetzen Sie wiederum optimiert und nicht-optimiert und wiederholen Sie das Experiment wie in (a) und (b) mehrmals. Was beobachten Sie? Versuchen Sie, die von Ihnen beobachteten Effekte zu erkl¨aren.

Teilaufgabe (d)

Wiederholen Sie Teilaufgabe (c), nun allerdings wieder mit dem Aufruf der Memory Barrier an den gekennzeichneten Stellen. Welche Effekte beobachten Sie nun?

Teilaufgabe (e)

Eine alternative zu den auf Assemblerbefehlen basierenden Memory Barriers ist die Verwendung von atomarenAnweisungen. In C++11 sind diese jetzt Teil der Sprache. Verwenden Sie f¨ur die Z¨ahlvariable stattint count nunstd::atomic<int> count. Was ergibt sich jetzt?

Ubung 13¨ Threads: Semaphoren (5 Punkte)

In der Vorlesung habe Sie das Konzept von Semaphoren sowie von inaktivem Warten ¨uber Bedin-

(3)

gungsvariablen kennengelernt. Interessanterweise gibt es in der C++-Bibliothek keine Implementie- rung einer Semaphore, wir m¨ussen also selbst eine erstellen. In dem auf der Homepage zur Verf¨ugung gestellten Archivc++11threadtools.ziphat die Klasse Semaphorefolgendes Layout:

1 c l a s s S e m a p h o r e

2 {

3 p u b l i c:

4

5 // ! \ b r i e f m a k e a s e m a p h o r e w i t h i n i t i a l v a l u e

6 S e m a p h o r e (int i n i t ) ;

7

8 // ! \ b r i e f m a k e a s e m a p h o r e w i t h i n i t i a l v a l u e 0

9 S e m a p h o r e () ;

10

11 // ! \ b r i e f d e c r e m e n t v a l u e

12 v o i d P () ;

13

14 // ! \ b r i e f r e l e a s e s e m a p h o r e

15 v o i d V () ;

Ihre Aufgabe ist es, dieP- undV-Methoden der Semaphore implementieren. Neben dem Wert, den die Semaphore verwalten soll, brauchen Sie noch einenstd::mutexund einestd::condition_variable Die Dateisemaphore.hh enth¨alt obige Header-Information, in der Datei semaphore.ccbefindet sich das Grundger¨ust, deren Methoden Sie ausimplementieren sollen.

Beachten Sie die Schnittstellen der C++-Objekte. Ein guter Startpunkt ist die Thread-Referenz auf http://en.cppreference.com/w/cpp/thread. Machen Sie sich außerdem mit dem RAII- Prinzip ver- traut (http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization), das in C++

extensiv genutzt wird.

Ubung 14¨ Threads: Erzeuger-Verbraucher-Problem mit Ringpuffer (10 Punkte) In der Vorlesung haben Sie besprochen, wie Erzeuger-Verbraucher-Probleme mit Semaphoren gel¨ost werden k¨onnen. Dieses Problem sollen Sie nun mit C++ Threads implementieren: Ein Puffer wird vom Erzeuger gef¨ullt. Ist der Erzeuger am Ende des Puffers angelangt, beschreibt er den Pufferanfang neu, ¨altere dort gespeicherte Auftr¨age werden ¨uberschrieben. Ein Verbraucher liest zu bearbeitende Aufgaben vom Pufferende. F¨ur das Programm brauchen Sie

• einen Puffer, der den gew¨unschten Datentyp speichern kann,

• eine Semaphore, die freie Pufferpl¨atze absichert,

• eine Semaphore, die belegte Pufferpl¨atze absichert.

• Initialisieren Sie die Semaphore, die freie Pufferpl¨atze absichert, zu Beginn mit der Anzahl aller Pufferpl¨atze, da diese initial frei sind.

Schreiben Sie nun Funktionen, die Erzeuger und Vebraucher implementieren. Verwenden Sie f¨ur die Implementierung das bereitgestellte leere Ger¨ust producerconsumer.cc. Dieses kann durch Aufruf von make ohne Anpassung des Makefiles ubersetzt werden. F¨¨ ur diese Aufgabe ben¨otigen Sie Ihre L¨osung aus der vorherigen Semaphoren-Aufgabe. Wer diese nicht l¨osen kann, melde sich bitte per E-Mail beim Tutor.

Testen Sie Ihr Programm mit einer Pufferl¨ange von 5. Der Puffer darf beliebige Datentypen spei- chern. Der Erzeuger soll in einer Schleife 20 St¨ucke produzieren und als produzierte Ware die aktuelle Schleifen-Z¨ahlvariable in den Puffer schreiben. Lassen Sie beide Threads ausgeben, welchen Pufferplatz sie gerade bearbeiten und welche Ware sie dort abgelegt bzw. gefunden haben.

Hinweise zu Threads in C++

Das Thread-Basisobjektstd::threadexistiert seit der Ver¨offentlichung von C++11 in der Standard- bibliothek. Im Gegensatz zu anderen Programmiersprachen wie z.B. Java leitet der Programmierer jedoch nicht von dieser Basisklasse ab, um einen Thread zu implementieren. Stattdessen wird dem Thread-Objekt im Konstruktor eine Funktion ¨ubergeben, die der Thread ausf¨uhren soll. Ein Beispiel:

(4)

1 // T h i s f u n c t i o n w i l l be c a l l e d f r o m a t h r e a d

2 v o i d c a l l _ f r o m _ t h r e a d () {

3 std :: c o u t < < " H e l l o W o r l d ! " < < std :: e n d l ;

4 }

1 int m a i n () {

2 // L a u n c h a s i m p l e t h r e a d

3 std :: t h r e a d t1 ( c a l l _ f r o m _ t h r e a d ) ;

4 // J o i n the t h r e a d w i t h the m a i n t h r e a d

5 t1 . j o i n () ;

In der main-Methode wird einstd::thread-Objekt erzeugt, dem ein Pointer auf die Functioncall_from_thread

¨ubergeben wird. Der Thread f¨uhrt diese Methode aus und wird mittels t1.join() wieder mit dem Hauptthread (der, der die main-Methode ausf¨uhrt) verbunden, also beendet.

F¨ur einfache Funktionen reicht diese Syntax aus. Will man jedoch ein Thread-Objekt mit einem Zustand versehen, kommt man so schnell an sein Grenzen. Der C++11-Standard setzt f¨ur diese F¨alle auf die sogenanntenlambda functions 2, also ad hoc definierte anonyme Funktionen, mit denen beliebig komplexe Funktionen zusammengestellt werden k¨onnen. Ein Beispiel:

1 // C l a s s i m p l e m e n t i n g a s t a t e f u l t h r e a d

2 c l a s s M y T h r e a d

3 {

4 p u b l i c:

5 M y T h r e a d (int i )

6 {

7 v a l u e = i ;

8 }

9

10 v o i d run ()

11 {

12 std :: c o u t < < " H e l l o World , v a l u e = " < < v a l u e < < std :: e n d l ;

13 }

14

15 v o i d run (int m y v a l u e 1 , int m y v a l u e 2 )

16 {

17 if( m y v a l u e 1 > m y v a l u e 2 )

18 std :: c o u t < < " H e l l o World , m y v a l u e = " < < m y v a l u e 1 < < std :: e n d l ;

19 e l s e

20 std :: c o u t < < " H e l l o World , m y v a l u e = " < < m y v a l u e 2 < < std :: e n d l ;

21 }

22

23 p r i v a t e:

24 int v a l u e ;

25 };

Der Thread ist nun in eine Klasse gekapselt, in der main-Methode wird ein Objekt dieser Klasse erzeugt und mittels einer Referenz an eine lambda function ¨ubergeben, die wiederum im Konstruktor des Threadst2ubergeben wird:¨

1 M y T h r e a d m y t h r e a d ( 1 2 ) ;

2

3 // L a u n c h a s l i g h t l y m o r e c o m p l e x t h r e a d u s i n g a l a m b d a f u n c t i o n

4 std :: t h r e a d t2 ( [& m y t h r e a d ]() { m y t h r e a d . run () ; } ) ;

5 t2 . j o i n () ;

6

7 // Use a l a m b d a f u n c t i o n w i t h p a r a m e t e r s

8 std :: t h r e a d t3 ( [& m y t h r e a d ](int v1 , int v2 ) { m y t h r e a d . run ( v1 , v2 ) ; } , 5 , 3 ) ;

9 t3 . j o i n () ;

10 std :: t h r e a d t4 ( [& m y t h r e a d ](int v1 , int v2 ) { m y t h r e a d . run ( v1 , v2 ) ; } , 2 , 13 ) ;

11 t4 . j o i n () ;

Am Beispiel der Threadst3, t4wird gezeigt, wie man zus¨atzliche Parameter an die lambda function

¨ubergeben kann. Die Parameter werden einfach als komma-separierte Liste hinter der lambda function an den Thread ¨ubergeben.

Das Ganze kann man beliebig komplex gestaltet. Besonders unsch¨on wird es, wenn man nun noch templatisierte Klassen verwendet, da man dann innerhalb einer Zeile den gesamten Klammer-Zoo (), {}, [], <>verwenden darf.

Auf der Vorlesungs-Homepage finden Sie ein zip-Archiv c++11threadtools.zip, das einige Werk- zeuge zum Thread-Handling und Beispiele bereitstellt.

2http://en.wikipedia.org/wiki/Anonymous_function#C.2B.2B

(5)

• Das oben angef¨uhrte Beispiel befindet sich inthreads_test.cc

• Es ist ein Makefile beigelegt, ¨ubersetzen k¨onnen Sie unter Linux mit make.

• F¨ur die L¨osung der Semaphoren-Aufgabe m¨ussen Sie dasMakefile nicht anpassen.

• F¨ur die L¨osung des Erzeuger-Vebraucher-Problems verwenden Sie bitte das bereitgestellte Ger¨ust producerconsumer.cc. Dieses ist imMakefileals target angegeben und wird bei Eingabe von makeubersetzt.¨

• Die Ger¨uste resources.cc,tsp.ccben¨otigen wir auf dem n¨achsten Aufgabenblatt.

• Beachten Sie die ausf¨uhrlichen Hinweise auf der Homepage.

Referenzen

ÄHNLICHE DOKUMENTE

Messen Sie wiederum die FLOPs und vergleichen Sie die Performance der sequentiellen und parallelen Varianten2. Praktisch sind Plots der FLOPs ¨ uber der Problemgr¨

(b) Schreiben Sie die for-Schleife so um, dass die Abh¨ angigkeit zwischen den Schleifendurchl¨ aufen vermindert wird (Tipp: In einer Schleife Instruktionen f¨ ur i und

Cube Connected Cycles (CCC) k¨ onnen aus Hypercubes der Dimension d konstruiert werden, indem jeder Knoten des Hypercubes durch einen d-elementigen (inneren) Ring ersetzt wird. Die

er wieder die Kontrolle ¨ uber das Programm erh¨ alt, und der Empf¨ anger die Zeit, die er ben¨ otigt, die Nachricht vollst¨ andig zu empfangen.. Um zuverl¨ assige Werte zu

Wie- derholen Sie einige Simulationen ohne diese Flags (optimiert nur mit O3), und diskutieren Sie die Performance in diesem Fall.. Die Karte im Pool hat theoretisch

(b) Schreiben Sie die for-Schleife so um, dass die Abh¨angigkeit zwischen den Schleifendurchl¨aufen vermindert wird (Tipp: In einer Schleife Instruktionen f¨ ur i und

Cube Connected Cycles (CCC) k¨onnen aus Hypercubes der Dimension d konstruiert werden, indem jeder Knoten des Hypercubes durch einen d-elementigen (inneren) Ring ersetzt wird?. Die

Allge- mein sollen Sie in dieser ¨ Ubung Rechenzeiten ¨ uber der Problemgr¨oße oder der Anzahl der beteiligten Threads messen.. Plotten Sie ein Zeit-m -Diagramm f¨ ur