Tipp 3: Programmiere in Tasks und nicht in Prozessorkern-Threads
Programme sollten nicht in Threads organisiert und den individuellen Rechner-Cores zugewiesen werden, sondern auf Taskebene programmiert werden. Das Zuordnen von Threads zu den Cores würde die Skalierbarkeit des Programms nachteilig beeinträchtigen. TBB favorisiert diesen task-orientierten Ansatz. Dabei hilft der in TBB integrierte Task-Scheduler, der durch ein effizientes „Task Stealing“-Konzept eine hohe Skalierbarkeit der Prozessor-Performance selbst bei zukünftig steigender Anzahl von Prozessorkernen bietet.
Tipp 4: Minimiere den Einsatz von Locks
Das Verwenden von Locks ist bei der Programmierung paralleler Applikationen nahezu unumgänglich, um Ressourcen für den Zugriff durch eine Applikation zu sperren. Damit vermeidet man die Entstehung von „Data Races“. Data Races sind nicht-deterministische Fehler, die häufig nur mit Sperrmechanismen (Locks) erfolgreich bekämpft werden können. Leider handelt man sich dabei verschiedenste Arten von ebenfalls nicht-deterministischen Dead Locks ein. Diese wiederum führen im ungünstigsten Fall zum Absturz des parallelen Programms. Das Verwenden von Locks (z.B. „critical section“, „reduction“ oder „atomic“- Pragmas innerhalb von OpenMP) sollte moderat geschehen. Die Programmsequenzen, die von Locks einklammert sind, sollten so kurz wie möglich sein. Es gibt im Web ernorm viele Ressourcen, mit denen man Strategien entwickeln kann, um den Einsatz von Locks zu minimieren bzw. auf ein Mindestmaß zu beschränken.
Daher ist es nötig, die Locks und ihre Auswirkungen zu verstehen. Intel Parallel Inspector zeigt nicht nur Data Races an, sondern weist auch auf die Dead Locks hin.
Tipp 5: Benutze skalierbare Memory-Allokatoren
Viele Programme benötigen eine dynamische Zuweisung von Speicher. Dies kann bei parallelen Programmen zu leistungsreduzierenden Nebeneffekten führen, vor allem wenn man „non threaded allocators“ verwendet (z.B. STL-Allokatoren). Dies liegt daran, dass jeder Thread um eine globale Sperre („Lock“) für jede Speicher- Allokation und -Deallokation von einem einzigen globalen Heap konkurriert. Diese Programme sind wegen des wachsenden Auftretens von gleichzeitigen Heap-Zugriffen mit steigender Prozessorkernzahl nicht skalierbar.
Es gibt übrigens noch einen weiteren zeitkritischen Speicherfehler-Zugriffsmechanismus, das „False Sharing“. Dies ist ein Cache-Speicherproblem, das mit dem Cache-Kohärenzprotokoll und der Tatsache zu tun hat, dass der Cache unabhängige Daten nebeneinander in 64 byte große Speicherblöcke ablegt. Dieses Problem ist um so signifikanter, je weiter der Cache vom Prozessorkern entfernt ist.
Für beide Probleme offeriert der Intel Composer bzw. die integrierte TBB skalierbare Lösungsansätze, die unabhängig von der Anzahl der Prozessoren sind. Sie sind in der Syntax ähnlich der STL-Allokatoren und lauten „scalable_allocator“ und „chache_aligned_allocator“.
Tipp 6: Erhöhe die Auslastung bzw. die Problemgröße des parallelen Programms
Amdahls Gesetz von 1967 besagt, dass der Leistungsgewinn eines parallelisierten Programms durch die seriellen, nicht parallelisierbaren Programmteile beschränkt ist und nicht beliebig skalierbar (steigerbar) ist, unabhängig wie viele Kerne man zusätzlich verwendet. Es dauerte mehr als 20 Jahre, bis Gustafsson ein verbessertes Parallelisierungskonzept vorschlug: Die parallelen Programmteile sollten eine höhere Arbeitslast erhalten. Dies ist ein Szenario, das man bei der Software- Weiterentwicklung häufiger findet. Es besagt, dass das Problem im Laufe der Zeit steigt. Falls man die Aufgabengröße seines Programms vergrößern kann, nutzt man die vorhandenen Kerne besser aus und erhöht den so genannten „Speedup Factor“ drastisch.