Schwerpunkte

Software-Entwicklung

Embedded-Linux-Systeme tracen – Teil 5

01. Juni 2021, 08:30 Uhr   |  Tobias Schlichtmeier


Fortsetzung des Artikels von Teil 1 .

Fortsetzung: Embedded-Linux-Systeme tracen – Teil 5

Zu viele Operationen

Welche Compiler-Optionen wurden in das Makefile im Kontext von Gleitkomma-Operationen eingebracht und weshalb haben sich diese auf die Performance ausgewirkt? Um die Fragen zu beantworten, muss man sich vergegenwärtigen, dass die Architektur einer standardmäßigen CPU (zum Beispiel eines ARM-Prozessors) für die effiziente Ausführung von Ganzzahl (Integer-)Operationen ausgelegt ist. Ein effizientes Verarbeiten von Gleitkomma-Operationen ist dagegen nicht relevant. Was passiert also, wenn in einem Programm Gleitkomma-Operationen vorkommen? Im ersten Makefile, in dem keine Compiler-Optionen verwendet wurden, wandelte der Compiler die Gleitkomma-Anweisungen, aus denen die Sinus-Operation zusammengesetzt war, in eine Reihe von Integer-Operationen um. Es liegt auf der Hand, dass die CPU hierdurch deutlich mehr Befehle ausführen muss. Das wiederum erhöht die Wahrscheinlichkeit dafür, dass ein anderer Task im System die Sinus-Berechnungen anhält.

Trace-Ansicht
© Percepio

Bild 5. In der Trace-Ansicht von Tracealyzer ist deutlich zu sehen, dass der Prozess, der die Sinus-Berechnungen ausführt, für rund 900 µs von anderen Prozessen unterbrochen wird.

Den Beweis dafür, dass genau das passiert, liefert ein Blick in die Trace-Ansicht von Tracealyzer (Bild 5). Es ist klar zu erkennen, dass der für die Sinus-Berechnungen zuständige Prozess durch andere Prozesse unterbrochen ist. Ebenso ist zu sehen, dass der betreffende Prozess an einer Stelle für eine Dauer von 900 µs unterbrochen ist.

Mit der Option »-mfloat-abi=hard« im zweiten Makefile wurde der Compiler instruiert, eine Reihe von Anweisungen zu verwenden, die eigens für Gleitkomma-Operationen vorgesehen sind. Wie zu sehen, hatte das jedoch keinen wirklichen Einfluss auf das Resultat, denn die Unstetigkeit war in ähnlicher Form immer noch vorhanden. Tatsächlich ist in der Trace-Ansicht des zweiten Trace zu sehen, dass die Sinus-Task auch hier für eine Dauer von etwa 900 µs angehalten wurde (Bild 6).

Compiler-Option
© Percepio

Bild 6. Auch das Einfügen der Compiler-Option »-mfloat-abi=hard« bringt keine wirkliche Besserung.

Weshalb hat sich die Situation nicht gebessert? In dem mit der Option freigegebenen Satz an Gleitkomma-Anweisungen befinden sich bestimmte Erweiterungen, die eine separate, stark optimierte Gleitkommaeinheit (Floating-Point Unit, FPU) im Prozessor selbst aktivieren. Unterlässt man es allerdings, ebenfalls die Compiler-Option »fpu« zu benutzen, wird von diesen Erweiterungen kein Gebrauch gemacht, sodass die Gleitkomma-Anweisungen nach wie vor mithilfe der standardmäßigen Ganzzahl-Instruktionen emuliert werden.

Verarbeitungszeit minimieren

Deshalb wurde im dritten Anlauf zusätzlich die Option »-mfpu=neon« benutzt. Sie weist den Compiler zum Aktivieren bestimmter Erweiterungen für die hier vorhandene FPU (NEON) an. Da der Großteil der Berechnungen dann von einem separaten Coprozessor verarbeitet wird, hatten andere Prozesse kaum Gelegenheit, die Sinus-Berechnungen zu unterbrechen und die Unstetigkeit im User Event Signal Plot fiel somit deutlich geringer aus.

Hiermit aber nicht genug: Mithilfe von Custom Intervals lässt sich in Tracealyzer ebenfalls visualisieren, wieviel Zeit die einzelnen Sinus-Berechnungen beanspruchen. Zu dem Zweck wurde der Aufruf von tracef mit den berechneten Sinuswerten herausgenommen und stattdessen das Tracing der User Events »Start« und »Stop« eingefügt (ganz ähnlich wie im vorigen Beitrag):

.

.

.

    for (t = 0; t < 1000; t++)

    {

        tracef("Start”);

        sample = sin((2*M_PI*freq*t)/sample_freq);                                    

        tracef("Stop”);

    }

.

.

Zeitleisten-Ansicht
© Percepio

Bild 7. Die Zeitleisten-Ansicht der Berechnung offenbart neben der Tatsache, dass die Verarbeitungszeit meist im zweistelligen Mikrosekundenbereich liegt, ebenso einige Ausreißer.

Als nächstes wurde die Anwendung mit den gerade beschriebenen Änderungen (ohne die Compiler-Option »float_abi=hard«) kompiliert und ausgeführt, um die Trace-Daten zu generieren. Anschließend waren die Trace-Daten in Tracealyzer darstellbar, um nach dem Konfigurieren eines Custom Interval die Ansicht »Interval Timeline« zu öffnen (Bild 7). Es ist zu erkennen, dass die Ausführungsdauer der Funktion in den allermeisten Fällen im zweistelligen Mikrosekundenbereich liegt. Es gibt jedoch ebenso ein paar Ausreißer: In einem Fall dauerte die Verarbeitung ungefähr 200 µs, in einem anderen sogar ca. 1,05 ms.

hard-ABI-Option
© Percepio

Bild 8. Mit der hard-ABI-Option verändern sich die Ergebnisse nicht spürbar.

Wurde anschließend wieder die hard-ABI-Option eingefügt (allerdings ohne die NEON-Erweiterungen), ergab sich die in Bild 8 gezeigte Interval-Timeline-Ansicht. Das Resultat ist ähnlich wie mit soft ABI. Die Dauer der meisten Ausführungen liegt im zweistelligen Mikrosekundenbereich und es gibt neben einigen Ausreißern mit Werten zwischen 100 und 200 µs auch einen Aufruf, der beinahe 1,1 ms dauert.

Wie sich das Verhalten änderte, nachdem neben der hard-ABI-Option ebenso die NEON-Erweiterungen wieder aktiviert wurden, ist in Bild 9 zu sehen. Tatsächlich sind aber auch hier noch einige Ausreißer auszumachen, jedoch beträgt die maximale Verarbeitungszeit lediglich noch knapp 240 µs. Das bestätigt die bereits weiter oben beschriebene Beobachtung, dass das Hinzufügen der NEON-Erweiterungen die größte Verzögerung zwischen den Berechnungspunkten ganz erheblich verringert.

NEON-Erweiterungen
© Percepio

Bild 9. Werden ebenfalls die NEON-Erweiterungen wieder aktiviert, reduziert sich die längste Verarbeitungszeit auf knapp 240 µs.

Tracealyzer frühzeitig nutzen

Mit der LTTng-Bibliothek und Tracealyzer ließ sich also mit wenig Zeitaufwand deutlich machen, wie sich bestimmte Compiler-Optionen auf die Performance von Userspace-Anwendungen, in denen Gleitkomma-Berechnungen vorkommen, auswirken. Üblicherweise nimmt man solche Analysen erst nachträglich vor, wenn man mit der erreichten Performance nicht zufrieden ist. Der Zeitaufwand ist dann größer. Besser ist es, Tracealyzer bereits während dem Entwickeln zu nutzen, um das Timing der Software zu verifizieren. In dem Fall hätte sich die Situation von vornherein vermeiden lassen.

Das ist der fünfte und vorletzte Artikel einer Serie von Beiträgen über das Verwenden von Tracealyzer zum Erfassen und Analysieren von Traces von Embedded-Linux-Systemen. Lesen Sie auch, wie Tracealyzer die Leistungsfähigkeit eines IRQ Handlers gewährleistet.

Mohammed Billoo
© Percepio

Mohammed Billoo ist Gründer von MAB Labs, LLC, einem Anbieter kundenspezifischer Embedded-Linux-Anwendungen.

Der Autor

Mohammed Billoo ist Gründer von MAB Labs, LLC, einem Anbieter kundenspezifischer Embedded-Linux-Anwendungen für eine Vielzahl von Hardware-Plattformen. Mohammed verfügt über einen Master in Elektrotechnik und mehr als 12 Jahre Erfahrung in Design, Implementierung und Test von Embedded-Software. Er trägt aktiv zum Linux-Kernel sowie zahlreichen Open-Source-Bemühungen bei und unterrichtet zudem als außerordentlicher Professor für Elektrotechnik an der Cooper Union for the Advancement of Science and Art.

Seite 2 von 2

1. Embedded-Linux-Systeme tracen – Teil 5
2. Fortsetzung: Embedded-Linux-Systeme tracen – Teil 5

Auf Facebook teilen Auf Twitter teilen Auf Linkedin teilen Via Mail teilen

Das könnte Sie auch interessieren

Verwandte Artikel

Percepio AB