Mehrkern-Anwendungen In vier Schritten Embedded-Software auf Multicore-Systeme portieren

Autor / Redakteur: Adriaan Schmidt und Christian Prehofer * / Hendrik Härter

Wie lässt sich bestehende Software auf Multicore-Plattformen migrieren? Wir stellen Ihnen einen vierstufigen Ansatz aus Analyse, Refactoring, Parallelisierung und Optimierung vor.

Firmen zum Thema

Multicore-Systeme: Sie verarbeiten mehrere Aufgaben gleichzeitig. Auch eingebettete Systeme sollten davon profitieren.
Multicore-Systeme: Sie verarbeiten mehrere Aufgaben gleichzeitig. Auch eingebettete Systeme sollten davon profitieren.
(Bild: Processors / Ben Stassen / CC BY 2.0)

Bei der Einführung von Multicore-Systemen ist es aus Zeit- und Kostengründen meist nicht möglich, die gesamte Software neu zu schreiben. Eine Portierung des existierenden Legacy-Codes ist also wichtig, wird allerdings allgemein als sehr schwieriges Problem angesehen.

Die Forscher der Fraunhofer ESK entwickeln daher Methoden und Werkzeuge zur systematischen Migration von Embedded Anwendungen auf Multicore-Plattformen. Dabei entnehmen sie unter anderem Ansätze aus dem High-Performance-Computing, wo schon eine jahrzehntelange Erfahrung in der Entwicklung paralleler Anwendungen besteht. Das Fachwissen wird auf parallele eingebettete Systeme übertragen, um es für Embedded-Entwickler zugänglich zu machen.

Multicore-Plattformen: Ein 4-stufiger Ansatz zur Migration von Embedded Anwendungen
Multicore-Plattformen: Ein 4-stufiger Ansatz zur Migration von Embedded Anwendungen
(Bild: Fraunhofer ESK)

Bei der Portierung bestehender Anwendungen hat sich ein vierstufiger Ansatz aus Analyse, Refactoring, Parallelisierung und Optimierung bewährt. Durch dieses methodische und systematische Vorgehen werden Fehler vermieden und dadurch die Effizienz bei der Software-Entwicklung gesteigert.

1. Genaue Code-Analyse: statisch und dynamisch

Die Portierung der Legacy-Software auf Multicore-Plattformen kann in der Summe mit mehr Aufwand verbunden sein als eine Neuentwicklung. Bei der Migration müssen deshalb vorab die Struktur der Anwendung genau analysiert und die Fragen zu Chancen und Risiken einer Parallelisierung beantwortet werden:

  • An welchen Stellen im Programm kann sinnvoll parallelisiert werden und welcher Gewinn an Performance ist möglich?
  • Mit welchem Aufwand ist die Parallelisierung verbunden?

Zur Identifikation der Chancen ist eine dynamische Analyse des Programms hilfreich. Hier werden mit einem Profiler Daten zum Ausführungsverhalten der Anwendung gesammelt, um festzustellen, wo sich die kritischen Bereiche der Software befinden. Dadurch kann in den weiteren Portierungsschritten gezielt gearbeitet werden. Für die Abschätzung des Aufwands muss man den Quellcode analysieren und betrachten, wie die Software im Detail implementiert ist. Tools für diese statische Code-Analyse können beispielsweise aufdecken, an welcher Stelle von verschiedenen Funktionen auf gemeinsame Daten zugegriffen wird. Solche konkurrierenden Zugriffe benötigen bei der Parallelisierung besondere Beachtung.

2. Refactoring: Die Vorbereitung auf die Parallelisierung

Nach der Identifizierung der Stellen im Code, an denen eine Parallelisierung sinnvoll ist, muss der Quellcode vorbereitet werden. Dabei wird sichergestellt, dass eine parallele Ausführung verschiedener Programmteile oder mehrerer Instanzen desselben Codes gefahrlos möglich ist. Das kann im einfachen Fall erfordern, dass der Zugriff auf gemeinsam genutzte Daten durch eine Synchronisation abgesichert wird.

Es kann aber auch grundlegende Änderungen an der Struktur des Programms mit sich bringen. Vor allem die Parallelisierung von Software, die für eine schnelle Ausführung auf Singlecore-Systeme optimiert ist, ist besonders schwierig. Idealerweise wird beim Refactoring das Verhalten des Programms nicht beeinflusst. In dieser Migrationsphase findet keine Parallelisierung statt. Die Trennung dieses Arbeitsschrittes von der eigentlichen Parallelisierung reduziert die Komplexität der einzelnen Aufgaben. So lassen sich Fehler bei der Migration vermeiden.

3. Parallelisierung: lokal oder global?

Es gibt grundsätzlich zwei Ansätze der Parallelisierung:

  • Lokal: Parallelisierung einzelner Funktionen, ohne den Rest der Anwendung zu beeinflussen.
  • Global: Parallele Ausführung mehrerer, unabhängiger Aufgaben.

Lokales Parallelisierenung ist beispielsweise Optimieren von Schleifen unter Einsatz von Programmierschnittstellen wie OpenMP. Die Optimierung betrifft nur die jeweilige Funktion und ist außerhalb nicht sichtbar. Die lokale Vorgehensweise führt bei eingebetteten Systemen allerdings nicht immer zum Erfolg. Beispielsweise sind in der Steuerungstechnik oder Telekommunikation viele Operationen nicht rechenintensiv genug, um von einer Parallelisierung zu profitieren. In diesem Fall muss auf Ebene des Gesamtsystems nach Möglichkeiten einer parallelen Ausführung gesucht werden.

Eine solche globale Parallelisierung kann jedoch zu grundlegenden Veränderungen der Software-Architektur führen. Für viele der häufig auftretenden Probleme gibt es Lösungen in Form von Entwurfsmustern (Design Patterns), die auch speziell die parallele Ausführung berücksichtigen. Da der Entwurf einer parallelen Software-Architektur sehr komplex ist, sollte man diese bereits bestehenden, erprobten und dokumentierten Entwurfsmuster verwenden. Dadurch lassen sich Fehler, die durch die Synchronisation paralleler Abläufe entstehen, bereits beim Design vermeiden

4. Optimierung: Bessere Performance?

Liegt nach der Parallelisierung ein korrekt arbeitendes Programm vor, wird dieses in einem letzten Schritt optimiert. So wird sichergestellt, dass der Gewinn an Performance durch die Parallelisierung tatsächlich höher ist als der zusätzliche Aufwand, der zur Laufzeit durch die Synchronisation der parallelen Programmteile entsteht.

Ob eine Performance-Steigerung erzielt wurde, lässt sich durch die Messung der Ausführungszeit bestimmen. Sollte das Verhalten nicht den Erwartungen entsprechen, müssen die möglichen Ursachen untersucht werden. Hier helfen Werkzeuge zur dynamischen Analyse wie Profiler, aber auch Tools zur Visualisierung der Abläufe paralleler Programme.

Es besteht ein hoher Bedarf an neuen Tools

Für jeden der vier Schritte existieren bereits grundlegende Werkzeuge, die jedoch für einen effizienten Einsatz in der Software-Entwicklung für eingebettete Multicore-Systeme optimiert werden müssen. Für die dynamische Analyse, die in zwei Phasen der Migration zum Einsatz kommt, gibt es Profiler als Teil der Werkzeugkette und spezielle Tools zur Visualisierung des Verhaltens paralleler Anwendungen.

Diese Visualisierungs-Tools werden im Bereich des High Performance Computing und von Prozessor-Herstellern, wie Intel und AMD, angeboten. Die Auswahl an Werkzeugen, die den zusätzlichen Anforderungen eingebetteter Systeme genügen, ist gering, weshalb oftmals das Verhalten eines Systems aus mehreren Anwendungen optimiert werden muss. Gerade für eingebettete Systeme benötigt man Werkzeuge, die Interaktionen verschiedener Anwendungen in einem System erfassen.

Ein weiteres Problem ist, dass der Einsatz von Analysetools häufig mit einem Overhead verbunden ist, der das Zeitverhalten des zu untersuchenden Programms beeinflusst. Besonders wenn es um Echtzeit-Anwendungen geht, ist der Nutzen dieser Tools begrenzt. Hier benötigt man Werkzeuge, die mit Hardwareunterstützung – beispielsweise über die JTAG-Schnittstelle – Laufzeitinformationen des Systems ermitteln, ohne es zu beeinflussen.

Programmierfehler aufdecken und Abhängigkeiten aufspüren

Die Tools zur statischen Codeanalyse konzentrieren sich auf das Aufspüren von Programmierfehlern. Sie können aber auch eingesetzt werden, um Abhängigkeiten innerhalb des Programms aufzudecken, die zu Problemen bei der Parallelisierung führen. Auch eine Abschätzung des Aufwands einer bevorstehenden Migration ist mit Hilfe von Codeanalysen möglich. Die hierfür benötigten Tools werden aber bisher nicht angeboten.

Für das Refactoring werden in Entwicklungsumgebungen Funktionen angeboten, die den Quellcode aber nur oberflächlich bearbeiten. Tiefergehende Änderungen, wie sie für eine Parallelisierung erforderlich sind, werden nicht ausreichend unterstützt. Für die eigentliche Parallelisierung gibt es eine große Auswahl an Möglichkeiten. Angefangen bei der direkten Verwendung von Threads reichen die Optionen bis zu Template-Bibliotheken und zu neuen Programmiersprachen, die eine leichtere parallele Ausführung versprechen. Allerdings ist die Auswahl der Werkzeuge in der Embedded-Entwicklung oft sehr eingeschränkt, zum Beispiel durch die fehlende Verfügbarkeit auf der Zielplattform.

Zusammengefasst sind die vorhandenen Werkzeuge in zwei Kategorien anzuordnen:

  • Tools, die sich in der Embedded-Entwicklung bewährt haben, allerdings erweitert werden müssen, um parallele Architekturen zu unterstützen.
  • Tools, die sich in der Entwicklung paralleler Anwendungen bewährt haben, allerdings an die Anforderungen der Embedded-Entwicklung angepasst werden müssen.

Damit Programmierer in Zukunft Software für parallele eingebettete Systeme problemlos entwickeln können, arbeiten die Forscher der Fraunhofer ESK an Tools, die sowohl den Kriterien der Embedded-Entwicklung, als auch den Ansprüchen von Multicore-Systemen gerecht werden.

* * Adriaan Schmidt ist wissenschaftlicher Mitarbeiter am Fraunhofer ESK und Prof. Dr. habil. Christian Prehofer ist Kompetenzfeldleiter Adaptive Communication Systems Fraunhofer ESK in München.

Artikelfiles und Artikellinks

(ID:26838580)