High-Performance-Computing

Trace-basiertes Debugging von Multi-Core-Systemen
Kommentare

Multi-Core-Prozessoren bieten zwar eine hohe Rechenleistung; allerdings erhöhen sie das Potenzial für Fehler bei der Softwareentwicklung. Viele dieser Fehler sind nur schwer reproduzierbar und gängige Debugging-Methoden sind bei ihrer Behebung unwirksam. Tracing, d. h. die Aufzeichnung des Programmverhaltens, kann hier Abhilfe schaffen. Im High-Performance-Computing ist es ebenso erfolgreich wie in eingebetteten Systemen.

Ob in Servern, Mobiltelefonen oder im Tablet: Multi-Core-Prozessoren sind heute nicht mehr hinwegzudenken. Auch in eingebettete Systeme, wie Autos, Telefonanlagen oder Maschinen, halten sie vermehrt Einzug. Eingebettete Systeme zeichnen sich häufig durch ihre Integration in physikalische Prozesse aus. Sie steuern z. B. die Benzineinspritzung oder ganze Roboter. Derartige Systeme müssen mit stark begrenzten Ressourcen auskommen und meist besonders energieeffizient arbeiten. Trotzdem steigen die Anforderungen an die Leistungsfähigkeit der Hardware und Software immer weiter. Die Entwicklung geht einerseits dahin, Funktionen von verschiedenen Geräten auf einem gemeinsamen Gerät zu integrieren, um dadurch Platz und Kosten zu sparen. Andererseits kommen immer aufwändigere Algorithmen zum Einsatz, um Qualität und Komfort zu steigern.

Beide Entwicklungen bedingen einen Bedarf an Rechenleistung, der nur durch Multi-Core-Prozessoren bedient werden kann. Die Entwicklung paralleler Softwaresysteme bringt aber auch Probleme mit sich. So werden besonders das Debugging und die Optimierung der Multi-Core-Systeme schwieriger. Deswegen haben sich die Wissenschaftler der Fraunhofer ESK mit geeigneten Tools und Methoden für drei Problemfelder beschäftigt:

  • Funktionale Fehler
  • Probleme der Leistungsfähigkeit
  • Probleme des Zeitverhaltens

Funktionale Fehler haben z. B. Race Conditions oder Deadlocks zur Folge, können aber auch im Zusammenhang mit Ressourcen auftreten, z. B. wenn ein Speicherbereich fälschlicherweise von verschiedenen Prozessen gleichzeitig genutzt wird. Diese Fehler entstehen bei der Migration älterer Software auf Multi-Core-Systeme und durch nicht bedachte Abhängigkeiten zwischen Anwendungen, Betriebssystem und Hardware. Charakteristisch ist, dass sie nicht von Eingabedaten abhängen, nichtdeterministisch und daher schwer reproduzierbar sind. Oft hilft nur, die Software lange genug zu testen, um einen einmal beobachteten Fehler zu reproduzieren. Die Praxis zeigt, dass es im schlimmsten Fall Tage oder sogar Wochen dauern kann, bis ein Fehler erneut auftritt.

Probleme der Leistungsfähigkeit äußern sich durch hohe Latenzzeiten oder einen zu niedrigen Datendurchsatz. In Extremfällen kann hierbei der Vorteil von Multi-Core-Prozessoren vollständig zerstört und ein parallelisiertes Programm sogar langsamer werden als seine sequenzielle Version. Die Ursachen von Performanzproblemen sind in der Regel Engpässe entweder in der Software oder in der Hardware. In der Software entstehen Probleme der Leistungsfähigkeit z. B. durch eine Synchronisierung an zu vielen Stellen und mit zu vielen beteiligten Threads, die sich dann gegenseitig blockieren. Der Anteil nutzbringender Arbeit sinkt. Hardwareengpässe lassen sich hingegen meist auf stark genutzte, gemeinsame Ressourcen zurückführen. Dies können Caches sein, aber auch Busse oder ganz allgemein Kommunikationsgeräte, die von mehreren Anwendungen gleichzeitig in Anspruch genommen werden. Es existieren bereits einige Werkzeuge, die derartige Engpässe identifizieren können. Diese Werkzeuge ermöglichen es, (Software-)Systeme zu modellieren und zu simulieren. Der Aufwand für einzelne Algorithmen ist überschaubar und Ergebnisse liegen schnell vor, vorausgesetzt, es ist bekannt, wie lange bestimmte Funktionen benötigen. Für komplexe Algorithmen können diese Modelle aber schnell unüberschaubare Dimensionen annehmen.

Eingebettete Systeme sind oft eng mit technischen Abläufen, z. B. mit der Fertigung in einer Fabrik, verzahnt. Das Zeitverhalten spielt daher in diesen Systemen eine große Rolle. Ergebnisse müssen nicht nur korrekt sein, sie müssen auch zur richtigen Zeit vorliegen. Durch Engpässe im System kann die Dauer von Berechnungen oder die Reaktionszeit, z. B. eines Busses, unerwartet hoch ausfallen. Dies kann zum Fehlverhalten und im schlimmsten Fall zum Ausfall des Gesamtsystems führen. Dazu ein Beispiel aus der Praxis:

Auf einem Industrie-PC (IPC) läuft eine Maschinensteuerung mit hoher Priorität und eine Nutzerschnittstelle mit geringer Priorität. Die hohe Priorität führt dazu, dass die Maschinensteuerung immer dann ungehindert agieren kann, wenn sie tätig werden muss. Nun wurde der PC mit einem Dual-Core-Prozessor ausgestattet und jedem Kern eine der beiden Anwendungen fest zugewiesen. Tatsächlich läuft die Nutzerschnittstelle dreimal schneller als zuvor, da sie nun nicht mehr durch das Steuerungsprogramm unterbrochen wird. Die Maschinensteuerung versagt hingegen komplett, obwohl ihr die Ressourcen des einen CPU-Kerns exklusiv zur Verfügung stehen. Als Ursache kann der verwendete PCI-Bus bestimmt werden, auf den die Nutzerschnittstelle häufig zugreift und damit die zwar seltenen, aber sehr dringenden Steuerbefehle für die Maschine blockiert. Hier hat also eine Interaktion auf Hardwareebene dazu geführt, dass eine weniger wichtige Softwarekomponente das Gesamtsystem zum Stillstand bringt.

Multi-Core-Werkzeuge

Mit den bisherigen Methoden und Werkzeugen können einzelne Threads einer einzigen Anwendung angehalten und ihre Abläufe Schritt für Schritt verfolgt werden. Der Zustand des Programms kann überprüft und Fehler mittels Ursache-Effekt-Analyse zurückverfolgt werden. In seinem Buch „Why Programs Fail“ stellt Andreas Zeller diese und andere Techniken und Werkzeuge vor, um Fehler zu finden und auch zu reproduzieren. Die meisten sind aber auf sequentielle Software ausgelegt. Inzwischen gibt es eine Reihe von Werkzeugen zur Optimierung und auch zum Debuggen von parallelen Anwendungen, wie das Parallel Studio von Intel, mit denen sich z. B. Race Conditions aufdecken lassen. Der Einsatz dieser Werkzeuge beschränkt sich aber meist auf den Bereich der Desktop-, Server- und Workstation-Software. Für eingebettete Systeme und ihre Probleme, wie z. B. begrenzte Ressourcen oder Zeitanforderungen, sind diese Werkzeuge nur begrenzt einsetzbar.

Tracing als Alternative

Als Alternative zu den bisherigen Werkzeugen nutzen die ESK-Forscher Tracing. Damit werden alle genannten Problemkategorien abgedeckt. Zudem kann das konkrete Verhalten des gesamten Softwaresystems erfasst werden. Dazu gehören alle im System laufenden Anwendungen und das Betriebssystem. Trace-Daten können dann analysiert und visuell aufbereitet werden. Der Systemzustand kann überprüft und gegebenenfalls zurückverfolgt werden.

Tracing wird bereits seit Langem sowohl im Bereich der eingebetteten Systeme als auch in dem des High Performance Computings (HPC) eingesetzt. Um einen Trace aufzuzeichnen, gibt es zwei grundlegende Techniken: softwarebasiert mittels Instrumentierung der Software, d. h. es werden vom Nutzer oder von Hilfsprogrammen zusätzliche Anweisungen in das Programm eingefügt, und hardwarebasiert über spezielle Hardwareerweiterungen. Instrumentierung wird hauptsächlich in der HPC-Domäne zur Optimierung eingesetzt. Lösungen existieren aber auch für Desktop- und Serversysteme z. B. mit dem Linux Trace Toolkit oder Event Tracing for Windows und für eingebettete Systeme mit der Timingtoolsuite T1 von Gliwa . Aufgezeichnet werden vor allem Funktionsaufrufe und die Kommunikation, aber auch Systemfunktionen, Scheduling oder Interrupts. Hardwarebasiertes Tracing andererseits wird fast ausschließlich im Bereich eingebetteter Systeme verwendet. Dort wird es sowohl zur Optimierung als auch zum Debugging eingesetzt. Durch die begrenzte Kapazität der derzeitigen Schnittstellen und teils sehr großer Datenmengen ist die Anzahl beobachtbarer CPU-Kerne limitiert. Aufgezeichnet werden können – abhängig vom Prozessormodell – die Adressen von ausgeführten Befehlen und deren Parameter, wie den Speicheradressen, auf die zugegriffen wird. In neueren Modellen kann aber auch zunehmend die Peripherie, z. B. Netzwerk-Controller, mit einbezogen werden.

Nachteile beim Tracing

Unabhängig von der Aufzeichnungstechnik werden Traces derzeit vom Entwickler häufig „manuell“ untersucht. Werkzeuge wie Vampir stellen die Trace-Daten auf einer Zeitachse dar. Auf Wunsch können Statistiken erstellt oder bestimmte Sachverhalte, z. B. die Kommunikation, hervorgehoben werden. Andere Werkzeuge, wie das oben genannte T1, sind schon weiter. Neben der Darstellung auf der Zeitachse (Abb. 1) bieten diese ihrem Einsatzzweck entsprechend Analysen der Trace-Daten an. Für viele der oben erwähnten Probleme fehlen aber immer noch computergestützte Analysen. Es liegt meist weiterhin beim Entwickler selbst, nach Problemen zu suchen. Die Herausforderungen bestehen bei solchen Analysen in der richtigen Aufbereitung der Daten, dem Erkennen der Zusammenhänge und ihrer für den Entwickler verständlichen Darstellung. Für jede der drei genannten Problemkategorien soll im Folgenden in einem Beispiel dargestellt werden, wo mögliche Probleme bei derzeitigen Analysen liegen und wie diese gelöst werden können.

Abb. 1: Trace Events auf der Zeitachse und einige dazugehörige Statistiken; beides dargestellt mit der Timingtoolsuite T1 von Gliwa

Abb. 1: Trace Events auf der Zeitachse und einige dazugehörige Statistiken; beides dargestellt mit der Timingtoolsuite T1 von Gliwa

Neue Ansätze

Ursache-Effekt-Analyse: Bei dieser grundlegenden Analyse geht es darum, die Ursachen eines beobachteten Fehlers mithilfe der aufgezeichneten Daten über die Programmausführung zu finden. Es werden dabei die auslösenden Ereignisse so lange „in die Vergangenheit“ verfolgt, bis die Ursache des Fehlers gefunden wird. Zeller beschreibt diese Analyse und stellt einige Werkzeuge vor. Diese sind aber meist auf sequentielle Programme beschränkt. Eine Verfolgung über mehrere Threads hinweg ist aber in Multi-Core-Software notwendig, um komplexe parallel verlaufende Zustandsänderungen nachvollziehen zu können.

Traces können diese Daten liefern, sofern schreibende Speicherzugriffe erfasst werden. Mit hardwarebasiertem Tracing stehen diese Informationen im Allgemeinen zur Verfügung. Eine Instrumentierung der Software mit ähnlichem Detailgrad ist nur mit zusätzlichen Werkzeugen möglich und der Mehraufwand zur Laufzeit des Programms erheblich (Kasten: „Tracing: software- oder hardwarebasiert“). Besteht ein Verdacht seitens des Entwicklers, kann dieser Aufwand stark reduziert werden, indem die Instrumentierung auf den fraglichen Teil des Programms begrenzt wird. Zur Aufbereitung der Daten muss der Entwickler zunächst die beobachtete Auswirkung benennen, also jenes Ereignis im Trace angeben, in dem er den Fehler erkennt. Dieser äußert sich durch einen fehlerhaften Zustand, der wiederum von einigen wenigen Variablen beeinflusst wird. Durch die zurückliegenden Berechnungen sind diese Variablen miteinander verknüpft. Mithilfe des Quellcodes kann die Wirkkette schließlich bis zum eigentlichen Fehler zurückverfolgt werden, auch über verschiedene Threads oder Anwendungen hinweg. Die vollständige Analyse kann interaktiv erfolgen. Dabei gibt der Entwickler vor, welchem Teil der Wirkkette gefolgt wird.

Suche von Flaschenhälsen: Für dieses Beispiel werden Engpässe betrachtet, die durch Synchronisierung entstehen. Dies kann z. B. ein Mutex sein, den man sich als Sperre vorstellen kann. Mit dieser Sperre ist es möglich, bestimmte Codeteile zu jedem Zeitpunkt von maximal einem Thread durchlaufen zu lassen. Gibt es mehr als einen Thread, kann ein Mutex zum Gegenstand gesteigerten Wettbewerbs werden. In einem solchen Fall warten Threads lange darauf, selbst die Sperre setzen zu können. Grund dafür kann eine zu grobe Granularität beim Einsatz des Mutex sein, d. h. andere Threads werden länger ausgesperrt als notwendig wäre. Ursache kann aber auch sein, dass dieselbe Sperre von vielen Threads und/oder an vielen Stellen im Programm verwendet werden muss.

Werkzeuge zur Analyse von Sperren sind zwar verfügbar, helfen aber meist nur in eingeschränkten Szenarien. Das Valgrind-basierte Werkzeug DRD z. B. ist auf POSIX Threads beschränkt und misst lediglich Wartezeiten einzelner Versuche, den Mutex zu ergreifen. Ob Meldung gemacht wird, hängt dann von Grenzwerten ab, die der Entwickler dem Werkzeug nennt. Dies führt nicht selten zu einer hohen Anzahl von Meldungen. Der Entwickler hat zum einen kaum die Möglichkeit zu überblicken, welcher Mutex wie stark frequentiert ist. Eine Auslese über höhere Grenzwerte kann andererseits auch wesentliche Informationen vor dem Entwickler verbergen. Eine auf Tracing basierende Analyse erfordert lediglich die Instrumentierung der lock- bzw. unlock-Funktionen des Mutex. Dies kann beim Aufrufer erfolgen und macht die Analyse so unabhängig von einer spezifischen Bibliothek. Die Analyse kann basierend auf diesen Daten für jeden Mutex und jeden Aufrufort Nutzungsstatistiken berechnen, wie z. B. die Wartezeit oder die Anzahl wartender Threads. Flaschenhälse können so schnell und unabhängig von Schätzungen identifiziert werden.

Interaktionen zwischen Anwendung und System: Die letzte der drei Problemkategorien behandelt Engpässe in zeitabhängigen Systemen, die besonders kritisch sein können. In diesen Systemen kommt parallele Software wie im vorherigen Beispiel nur selten vor. Stattdessen besteht das Gesamtsystem aus vielen unabhängigen, sequenziell arbeitenden Komponenten. Parallelität entsteht erst durch deren Integration. Engpässe entstehen hier dennoch, verursacht durch mangelnde Anzahl oder Kapazität der Hardware wie z. B. der CPU, von Caches und Bussen oder Netzwerken. Um diese Probleme zu analysieren und diese Engpässe zu identifizieren, eignen sich nur wenige Werkzeuge. Eines davon ist das bereits erwähnte T1. Dieses kann zusätzlich zum Aufzeichnen von Trace-Daten das Zeitverhalten auch während der Messung überprüfen. Die Hauptschwierigkeit bei diesen Analysen besteht jedoch darin, dass die verschiedenen Ebenen des Systems in Bezug zueinander gesetzt werden müssen. War es in den Beispielen zuvor ausreichend, die Anwendung allein zu betrachten, müssen nun das Betriebssystem und andere Anwendungen mit einbezogen werden. Tracing kann dies leisten. Dabei sind verschiedene Ansätze denkbar, die nötigen Daten zu sammeln.

Hardwarebasiertes Tracing zeichnet alle Befehle auf, unabhängig davon, zu welchem Teil der Software diese gehören. Eine nachfolgende Analyse kann diese Daten entsprechend des betrachteten Szenarios filtern und für den Entwickler aufbereiten. Ein ähnliches Ergebnis kann man mit softwarebasiertem Tracing erreichen. Hier ist eine getrennte Instrumentierung der relevanten Applikationen und des Betriebssystems nötig, gefolgt vom Zusammenführen der Traces. Eine Instrumentierung des Betriebssystems steht sowohl für Linux als auch für Windows zur Verfügung (Linux Trace Toolkit bzw. Event Tracing for Windows). Eine einfache Möglichkeit der Darstellung zeigt Abbildung 2 unter Nutzung von Vampir. Das Beispiel zeigt aufgezeichnete Traces für zwei Anwendungen sowie das Betriebssystem. Die verschiedenen Dateien müssen nach der Aufzeichnung zunächst in ein einheitliches Format überführt und synchronisiert werden. Erst danach kann die Visualisierung wie gezeigt erfolgen. Im gezeigten Beispiel benötigt die Funktion RT_transmit der Anwendung Soft-RT Task sehr viel länger als normal. Dies führt zur Verletzung einer Zeitschranke, was im Trace aufgezeichnet und dem Entwickler hier in Form eines Dreiecks präsentiert wird. Erst die Darstellung des Gesamtsystems kann die Ursache aufzeigen. Die Anwendung wurde durch einen Interrupt und dessen Behandlung unterbrochen, in der Grafik als dunkelgraue Flächen im Trace Core 1 markiert. Außerdem in Abbildung 2 zu sehen: Die Anwendung Soft-RT Task erfährt eine Verletzung ihrer Zeitbeschränkung (durch blaues Dreieck markiert); die Ursache ist erst durch die Darstellung des gesamten Systems ersichtlich: ein Interrupt und dessen Behandlung (dunkelgraue Flächen) haben die Anwendung unterbrochen.

Abb. 2: Darstellung von 4 Traces aus einem Softwaresystem mit zwei Anwendungen mittels Vampir

Abb. 2: Darstellung von 4 Traces aus einem Softwaresystem mit zwei Anwendungen mittels Vampir

Viele weitere Szenarien sind denkbar. Besonders interessant ist dabei die Programmbeobachtung mittels Tracing beim Debugging, wie im ersten und dritten Beispiel gezeigt. Dedizierte Tracing-Bibliotheken sind den meisten Logging-Techniken überlegen. Diese erzeugen zwar menschenlesbare Nachrichten, verlangsamen aber die Ausführung zusätzlich und bieten nicht die Möglichkeit der grafischen Darstellung. Weiteres Potenzial bieten andere Betriebsmodi. So kann in einer Art Flight-Recorder-Modus eine begrenzte Anzahl von aktuellen Ereignissen gespeichert werden. Ist der Puffer voll, werden früher aufgezeichnete Ereignisse verworfen und durch neue überschrieben. Diese Informationen können das sonst übliche Speicherabbild um eine Liste der letzten Befehle erweitern.

Fazit

Debugging von Software gehört zu den schwierigen Aufgaben der Softwareentwicklung. Die zunehmende Nutzung von Multi-Core-Prozessoren selbst im Bereich eingebetteter Systeme und der Einsatz von Software speziell für diese Systeme hat die Situation noch verschärft. Die meisten der heute verfügbaren Debugging-Werkzeuge sind allenfalls unzureichend auf die hohe Komplexität paralleler Systeme vorbereitet. Eine bereits länger bekannte Technik kann Abhilfe schaffen: Tracing, d. h. die Aufzeichnung des tatsächlichen Programmverhaltens. Gerade bei Multi-Core-Systemen, mit ihren komplexen Abhängigkeiten zwischen verschiedenen Softwaremodulen und auch zwischen Software und Hardware, kann diese Aufzeichnung helfen, Probleme zu lösen. Verfügbare Werkzeuge stellen hier den ersten Schritt dar. Der zweite Schritt besteht in der Entwicklung von Analyse- und Visualisierungstechniken, um Probleme schneller finden zu können.

Tracing: software- oder hardwarebasiert
Es gibt zwei grundlegende Tracing-Techniken: hardwarebasiert und softwarebasiert. Hardwarebasiertes Tracing kommt aus dem Bereich der eingebetteten Systeme und wird dort sehr oft zum Debugging verwendet. Die aufgezeichneten Trace Events werden dabei von der Hardware des Prozessors direkt generiert und über eine spezielle Schnittstelle herausgeschrieben. Die Art dieser Events ist vom Prozessormodell abhängig. Im Allgemeinen wird zumindest der aktuelle Wert des Befehlszählers aufgezeichnet, häufig werden aber auch die Adressen und Inhalte von Speicherzugriffen angezeigt. Softwarebasiertes Tracing kommt hingegen aus dem Bereich des High-Performance-Computing. Es wird fast ausschließlich zur Optimierung eingesetzt. Diese auf der Instrumentierung der Software basierende Methode ist von spezifischer Hardware unabhängig und damit auf jedem System durchführbar. Es muss lediglich eine Möglichkeit gefunden werden, die anfallenden Daten zu speichern. Die Instrumentierung ist im Vergleich zum hardwarebasiertem Tracing flexibel und erlaubt die Anpassung an die jeweiligen Erfordernisse des Entwicklers. So können z. B. zusammen mit einem Ereignis beliebige Daten erfasst werden.
Auswirkung auf das Laufzeitverhalten
Beim hardwarebasierten Tracing werden die Trace Events direkt von der Hardware erzeugt und an das System des Entwicklers geschickt. Für die Target-Software ist diese Art des Tracings daher vollständig transparent und ihr Laufzeitverhalten wird nicht verändert. Im Gegensatz dazu erzeugt beim softwarebasierten Tracing die Software selbst die Trace Events und ist ebenfalls für deren Speicherung verantwortlich. Beides bedeutet Mehraufwand zur Laufzeit und kann in zeitbeschränkten Systemen dazu führen, dass Zeitschranken verletzt werden. Da der Mehraufwand über die Menge und Qualität der Instrumentierung steuerbar ist, wird eine auf die Anwendung abgestimmte Messmethodik notwendig.
Detailgrad und Datenmenge
Hardwarebasiertes Tracing bietet den höchstmöglichen Detailgrad, da Trace Events für jeden Befehl erzeugt werden. Einerseits ermöglicht dies sehr detaillierte Analysen wie z. B. der Pfadabdeckung oder der Suche nach Race Conditions. Andererseits fallen schnell sehr große Datenmengen an. Ein MPC5554 von Freescale, ein Single-Core-Microcontroller, z. B. erzeugt bei 132 MHz Taktfrequenz mehr als 4,6 GBit pro Sekunde an Trace-Daten. Der Umfang der Instrumentierung ist bei softwarebasiertem Tracing hingegen frei wählbar und kann den Erfordernissen angepasst werden. Ein vergleichbarer Detailgrad wie bei hardwarebasiertem Tracing ist aber kaum möglich. Der Aufwand zur Erzeugung der Trace-Daten, d. h. zur Laufzeit der Software, wäre zu groß. Dennoch sind die meisten Analysen, wenn auch mit verringerter Auflösung, durchführbar.

Aufmacherbild: Debugging or Debug Software Code and Logic von Shutterstock / Urheberrecht: kentoh

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -