Multicore: Optimierungspotential sichtbar machen

27. Juli 2007, 11:36 Uhr | Bill Graham
Diesen Artikel anhören

Fortsetzung des Artikels von Teil 1

Übermäßige Migration der Threads reduzieren

Manche Betriebssysteme unterstützen die so genannte Prozessor-Affinität mit dem Ziel, die Performance von Multicore-Systemen zu optimieren. In dieser Betriebsart versucht der Scheduler stets, einen Thread auf den Core zu legen, auf dem er zuletzt gelaufen ist. Auf diese Weise kann sich der Core die Befehle des Threads direkt aus dem L1-Cache holen, anstatt sie aus dem L2-Cache oder dem Hauptspeicher laden zu müssen. In manchen Fällen jedoch wandern die Threads von Core zu Core und überschreiben gegenseitig ihre im Cache gespeicherten Befehle. Dadurch muss der L1-Cache ständig neu geladen werden. Der Performance-Vorteil des im L1-Cache vorhandenen Codes geht verloren.

passend zum Thema

Unnötige Migrationen von Threads entstehen auch dadurch, dass Threads mit höherer Priorität die Ausführung von Threads mit niedrigerer Priorität unterbrechen. Dabei besteht jedes Mal die Möglichkeit, dass das Betriebssystem das Handling des Threads auf einem anderen Core einplant. Je mehr Cores auf einem Chip sind, desto wahrscheinlicher ist dieser Fall.

Ein System Profiler liefert Statistiken über die Anzahl der Corezu- Core-Migrationen für einen bestimmten Thread (Bild 6). Durch die grafische Darstellung erhält der Entwickler eine systemumfassende Perspektive. Es ist für ihn einfacher, sich einen potentiellen Problembereich heranzuholen. Dieses Beispiel zeigt 280 Thread- Migrationen für Thread 2 im beobachteten Zeitfenster von 8,979 Sekunden. Diese Zahl ist nicht besonders hoch, kann aber als zu hoch für diese Applikation angesehen werden. Gebündeltes Multiprocessing, kurz BMP, hilft, die Anzahl der Migrationen zu reduzieren. Mit BMP hat QNX eine neue Form des Multiprocessing eingeführt, die Threads während der Laufzeit mit einem einfachen Funktionsaufruf an einen bestimmten Core oder an mehrere Cores bindet.

Fest an einen Core gebundene Threads und BMP sollten nur dann genutzt werden, wenn es absolut notwendig ist. Schließlich ist die Migration von Threads sehr nützlich, da der Scheduler mit ihr alle verfügbaren CPU-Cores maximal ausnutzen kann. Prinzipiell notwendig ist BMP bei Applikationen mit eng gekoppelten Prozessen sowie bei Threads, die auf einem einzelnen Core mehr Leistung bringen.

Bild 6. Eine Darstellung der Thread-Migrationen zeigt, wie oft Threads den Prozessor-Core wechselten.

Ein umfassender Ansatz

Die richtigen Tools vereinfachen zwar die Problemanalyse und Optimierung von Multicore-Designs, funktionieren aber nicht isoliert. Auch ein korrekt aufgebautes Betriebssystem verringert die Komplexität beim Einsatz von Software auf Multicore-Chips. Dies trifft vor allem dann zu, wenn das Betriebssystem die Zuordnung gemeinsamer Hardware- Ressourcen auf einem Multicore-Chip transparent verwalten kann. Zudem hat ein für Multicore entwickeltes Betriebssystem die Flexibilität, um die von den Tools vorgeschlagene Optimierungsstrategie umzusetzen. Das Echtzeit-Betriebssystem QNX Neutrino zum Beispiel liefert mit gebündeltem Multiprocessing die transparente Ressourcenverwaltung von traditionellem SMP, während der Entwickler gleichzeitig jeden Prozess auf einen bestimmten Core festlegen kann. Dadurch lassen sich Engpässe, wie übermäßiges Messaging zwischen Cores, deutlich einfacher beheben. Joachim Kroll

Bill Graham studierte Elektrotechnik an der Carleton University in Ottawa, Kanada. Seit 18 Jahren ist er in der Software-Industrie tätig und verfügt unter anderem über Erfahrung in der Entwicklung von Embedded- und Echtzeit-Systemen, UML-Modellierung sowie objektorientierter Entwicklung. Er arbeitet zurzeit als Product Line Manager bei QNX Software Systems, vorher war er in der Software-Entwicklung, im Consulting und im Produktmanagement bei IBM, Rational, Klocwork und ObjecTime beschäftigt.
info@qnx.de

Ein gutes System-Tracing-Tool hilft, Ressourcenkonflikte zu entdecken, indem es Prozesse markiert, die zwar ausführbereit, aber blockiert sind. Es kann aufgrund von Ressourcenkonflikten blockierte Threads statistisch erfassen und Core-zu-Core-Messaging grafisch abbilden.

Hilfreich sind zudem Features wie Suchmöglichkeiten für spezielle Systemereignisse oder die grafische Darstellung des Ausführungsablaufs mit exakten Zeitstempeln. Anhand dieser Informationen kann der Entwickler entscheiden, wie er das System am besten optimiert. Beispielsweise, indem er die Applikation in mehrere Threads splittet, um die Parallelverarbeitung auszubauen, oder indem er die Quelle des Konfliktes entfernt und die Ressource für die Cores verdoppelt.

Als Beispiel möge eine Applikation dienen, bei der vier Threads auf ein großes Array gemeinsamer Daten zugreifen und dieses bedienen. Auf einem Uniprozessorsystem würde das Echtzeit-Betriebssystem die Gleichzeitigkeit der Threads lediglich simulieren. Auf einem Multicore-System laufen die Threads tatsächlich zeitgleich. In einem Uniprozessorsystem benötigt die Applikation nur eine einzige Mutex, um die gemeinsamen Daten ausreichend zu schützen. In einem Multicore-System jedoch kann eine einzelne Mutex zu erheblichen Konflikten führen.

7030804_tm_03.jpg
Bild 4. System Trace eines Systems mit vier Cores. Gesamtausführungszeit des Algorithmus: 2,046 Sekunden. Die Ablaufverfolgung zeigt mit lilafarbenen Streifen einen deutlichen Ressourcenkonflikt in der Thread-Ausführung. Er entsteht, da mehrere Threa

Bild 4 zeigt, was passiert, wenn diese Applikation mit einer einzigen Mutex auf einem System mit vier Cores läuft. Alle vier Threads laufen parallel und benötigen für die Ausführung 2,046 Sekunden. Offensichtlich verbringen sie viel Zeit im Wettkampf um die Mutex, die dieses Array schützt. Die lilafarbenen Streifen verdeutlichen diesen Konflikt. Sie zeigen an, dass ein Thread laufbereit ist, aber durch die Mutex blockiert wird. Weitere Mutexe für den Zugriff auf dieses Array verringern diesen Konflikt. Da jede neue Mutex eine bestimmte Region schützt, kann jeder Thread auch nur auf einen bestimmten Bereich des Arrays zugreifen. Bild 5 zeigt, dass mit 16 Mutexen die Gesamtverarbeitungszeit von 2,046 Sekunden auf 800 Millisekunden sinkt. Die geringere Menge an lilafarbenen Streifen zeigt, dass sich die Konkurrenzsituation entspannt hat.

Interessanterweise brachte der Einsatz von noch mehr Mutexen keine weiteren Fortschritte. Im Gegenteil: Die Gesamtverarbeitungszeit stieg, sobald 40 Mutexe im Spiel waren. Mit anderen Worten: Zu viele Mutexe können die Performance verringern. Der springende Punkt ist aber, dass Software eben nur selten für Multicore- Umgebungen optimiert ist. Entwickler benötigen daher Visualisierungswerkzeuge, um das Systemverhalten zu verstehen und den besten Ansatz zur Verbesserung des vorhandenen Codes zu finden.

7030805_tm_03.jpg
Bild 5. System Trace des Algorithmus wie in Bild 4, aber mit 16 Mutexen. Zusätzliche Mutexe senken die Ausführungszeit auf 800,328 ms und erzielen eine Verbesserung um 255 %. Die Konkurrenz um Mutexe ist sichtlich geringer.

Parallelisierbaren Code finden

Um die Performance von Applikationen auf Multicore-Prozessoren zu maximieren, müssen Entwickler die von diesen Chipsets angebotene Hardware-Parallelverarbeitung voll nutzen. Dabei stellt sich jedoch als erstes die Frage, wo die Parallelverarbeitung den meisten Gewinn bringt. Mit einem System- Tracing-Tool können Entwickler genau die Prozesse oder Threads in Prozessen herausgreifen, die große Mengen an CPUZeit verbrauchen.

Anschließend können sie mit einem Application Profiler die Funktionsleistung einzelner Prozesse analysieren und so herausfinden, welcher Code in einem Prozess oder Thread die meiste CPU-Zeit verbraucht.

Bild 1 zeigt mit fill_array() eine rechenintensive Funktion, die ein großes zweidimensionales Array bedient. Der Screenshot des QNX Momentics Application Profiler (Bild 2) macht deutlich, dass fill_array() in der Applikation die meiste CPU-Zeit verbraucht. Da fill_array() ein einzelner Thread ist, würde sich die Performance auch dann nicht verbessern, wenn er von einem Uniprozessorsystem auf ein Multicore- System verschoben würde. Wird der Algorithmus in verschiedene Threads gesplittet (Bild 3), kann das Betriebssystem die Rechenlast auf alle verfügbaren Prozessoren verteilen und so die Rechenzeit wesentlich verkürzen. Dies ist ein wichtiger Vorteil der auf symmetrischem Multiprocessing, kurz SMP, basierenden Echtzeit-Betriebssysteme wie dem QNX Neutrino.

7030801_tm_04.jpg
Bild 1. Von Uniprozessorsystemen portierte Algorithmen sind in der Regel Einzel- Threads. Bei der Umstellung auf Multicore tritt ein Performance- Gewinn erst dann ein, wenn die Threads parallel verarbeitet werden. Bild 2. Der Application Pro
7030802_tm_04.jpg
Bild 1. Von Uniprozessorsystemen portierte Algorithmen sind in der Regel Einzel- Threads. Bei der Umstellung auf Multicore tritt ein Performance- Gewinn erst dann ein, wenn die Threads parallel verarbeitet werden. Bild 2. Der Application Pro
7030803_tm_04.jpg
Bild 1. Von Uniprozessorsystemen portierte Algorithmen sind in der Regel Einzel- Threads. Bei der Umstellung auf Multicore tritt ein Performance- Gewinn erst dann ein, wenn die Threads parallel verarbeitet werden. Bild 2. Der Application Pro

  1. Multicore: Optimierungspotential sichtbar machen
  2. Übermäßige Migration der Threads reduzieren
  3. Multicore: Optimierungspotential sichtbar machen

Jetzt kostenfreie Newsletter bestellen!