25.02.2014 Aufrufe

Linux-Magazin In Zockerhänden (Vorschau)

Erfolgreiche ePaper selbst erstellen

Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.

Programmieren<br />

www.linux-magazin.de TBB 3.0 05/2011<br />

100<br />

Abbildung 3: Beim Sudoku müssen alle Felder mit<br />

Zahlen von 1 bis 9 belegt werden. Eine Ziffer darf in<br />

Reihe, Spalte und Block nur einmal vorkommen.<br />

Zeiger übergibt. Da die TBB-Bibliothek<br />

nach Ende der »execute«-Methode die<br />

Task automatisch löscht, ist es allerdings<br />

unmöglich, Ergebnisse der Kind- an die<br />

Eltern-Task zu übergeben. Daher erhält<br />

die Kind-Task einen Zeiger auf eine Speicherstelle,<br />

in die das Ergebnis einzutragen<br />

ist. Da die Eltern-Task während der<br />

Ausführung der Kinder pausiert, ist sichergestellt,<br />

dass kein anderer auf diese<br />

Speicherstelle zugreift.<br />

Wie arbeitet nun die »execute()«-Methode?<br />

Der Aufruf »nextfree()« liefert das<br />

nächste freie Feld zurück. Anschließend<br />

testet eine Schleife für alle Zahlen zwischen<br />

1 und 9, ob diese als Kandidat<br />

für das Feld in Frage kommen. Für jeden<br />

ermittelten Kandidaten erzeugt das<br />

Programm eine neue Kind-Task und<br />

speichert diese in einer Liste. Der Aufruf<br />

»spawn_and_wait_for_all()« startet<br />

die Kinder-Tasks und pausiert die auf-<br />

rufende Eltern-Task. Wenn alle Kinder<br />

sich zurückgemeldet haben, werden die<br />

Ergebnisse eingesammelt.<br />

Listing 5 enthält das Hauptprogramm.<br />

Die Wurzel-Task erzeugt rekursiv sehr<br />

viele Kinder-Tasks. Den Rest erledigt die<br />

TBB-Bibliothek und verteilt diese Task-<br />

Objekte auf die einzelnen Threads. Der<br />

Entwickler ist vom Programmieren mit<br />

Threads und tückischen Problemen wie<br />

etwa Deadlocks befreit.<br />

Abbildung 4 zeigt die Skalierung des parallelen<br />

Lösers auf einer Hexacore-CPU für<br />

ein Sudoku. Je mehr Cores zum Einsatz<br />

kommen, umso schneller ist das Sudoku<br />

gelöst. Die lineare Kurve zeigt die maximal<br />

mögliche Beschleunigung. Bei sechs<br />

Kernen ist die tatsächlich messbare aber<br />

wegen des steigenden Verwaltungsaufwands<br />

nur knapp fünffach.<br />

Parallele Datenstrukturen<br />

Die TBB versprechen eine Reihe von Containerklassen,<br />

die im Gegensatz zur STL<br />

verbindlich Thread-safe sind. Allerdings<br />

ist das nicht ganz korrekt: Um beurteilen<br />

zu können, ob eine Klasse gegen einen<br />

gleichzeitigen Zugriff mehrerer Threads<br />

geschützt ist, muss man jede einzelne<br />

Methode der Klasse betrachten. Dabei<br />

wird deutlich, dass die Containerklassen<br />

der TBB nur Thread-sicher in Hinblick<br />

auf bestimmte Methoden sind. Beispielsweise<br />

sollte der Aufruf des Konstruktors<br />

oder Destruktors immer nur durch einen<br />

einzigen Thread erfolgen.<br />

Der »concurrent_vector« verhält sich<br />

Thread- safe, wenn ein neues Element mit<br />

Speed-Up<br />

Anzahl der Rechenkerne<br />

Real<br />

Linear<br />

Abbildung 4: Theoretisch steigt die Performance<br />

linear mit der Zahl der Kerne, doch in der Praxis<br />

fordert der Verwaltungsaufwand seinen Teil.<br />

»push_back()« angehängt wird. Paralleler<br />

Zugriff mit dem »[]«-Operator auf ein<br />

Element im Vektor ist möglich, aber nicht<br />

Thread- safe, das ist Sache des Entwicklers.<br />

Elemente lassen sich aus der Datenstruktur<br />

im Gegensatz zum STL-Pendant<br />

nicht einmal mehr entfernen.<br />

Einzig der Container »concurrent_hash_<br />

map« bietet die Möglichkeit, den gleichzeitigen<br />

Zugriff auf ein einzelnes Element<br />

zu verhindern. Hierzu gibt es Accessor-<br />

Objekte, also intelligente Zeiger auf ein<br />

Element in der Hashtabelle. Solange ein<br />

Zeiger existiert, dürfen andere Threads<br />

nicht auf das referenzierte Element zugreifen<br />

und werden suspendiert.<br />

Das Beispiel in Listing 6 zeigt die parallele<br />

Berechnung des Histogramms eines<br />

Grauwertbildes. Die Methode »insert()«<br />

fügt ein neues Element in die Hashtabelle<br />

ein, wenn dieses noch nicht existiert.<br />

Gleichzeitig wird ein Zugriffselement »a«<br />

Listing 5: Hauptprogramm<br />

01 #include <br />

02 #include <br />

03 using namespace std;<br />

04<br />

05 #include "tbb/task_scheduler_init.h"<br />

06 #include "tbb/tick_count.h"<br />

07 using namespace tbb;<br />

08<br />

09 #include "sudoku.h"<br />

10 #include "SudokuTask.h"<br />

11<br />

12 int main(int argc, char* argv[]) {<br />

13 cout print();<br />

20<br />

21 unsigned long subtasks=0;<br />

22 SudokuTask* root=new(task::allocate_root())<br />

SudokuTask(puzzle,&subtasks);<br />

23<br />

24 tick_count t0=tick_count::now();<br />

25 task::spawn_root_and_wait(*root);<br />

26 tick_count t1=tick_count::now();<br />

27<br />

28 cout

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!