Trace-Visualisierung Evaluierung der Leistung von Algorithmen-Varianten in Python

Von Mohammed Billoo

Anbieter zum Thema

Der Einsatz rekursiver oder auch iterativer Algorithmen wird Angesichts zunehmender Anwendungen des Maschinellen Lernens, etwa bei Edge-KI, immer wichtiger. Mit Python und visuellen Trace-Analyse-Tools lässt sich die Leistungsfähigkeit solcher Algorithmen mit geringem Mehraufwand schnell ermitteln.

(Bild: MAB Labs / Percepio)

Python ist eine universell einsetzbare, höhere Programmiersprache, die bei der Entwicklung von Embedded-Anwendungen immer mehr an Verbreitung gewinnt. Dies gilt insbesondere für Machine-Learning-Lösungen an der Edge, also an den Außengrenzen der Netzwerke. Python abstrahiert jedoch viele Details im Code, die die Leistungsfähigkeit einer Implementierung auf eine Art und Weise beeinflussen können, die den Entwicklern möglicherweise nicht klar ist.

Nehmen wir als Beispiel das Berechnen der Fibonacci-Folge. Hierfür bieten sich mindestens zwei Implementierungs-Varianten an, nämlich ein rekursiver und ein herkömmlicher, iterativer Algorithmus, die sich in ihrer Performance deutlich unterscheiden.

Einsatz des Open-Source-Tools LTTng mit visueller Trace-Analyse

Die Leistungsfähigkeit verschiedener Implementierungen oder Algorithmen lässt sich mit dem Tracealyzer-Tool bewerten. Dieses Visual-Trace-Diagnosetool von Percepio bietet Embedded-Software-Entwicklern Einblicke in das Verhalten des Codes zur Laufzeit, damit systemische Fehler einfacher behoben werden können und die Möglichkeit geschaffen wird, das Design und die Leistungsfähigkeit der Software zu verbessern.

Tracealyzer kann ergänzend zu traditionellen Debuggern wie den quelloffenen Eclipse-Tools eingesetzt werden und komplementiert die detaillierte Debugger-Darstellung durch mehrere zusätzliche Ansichten auf der System-Ebene. Dies macht es Entwicklern leichter, Echtzeitprobleme wirklich zu verstehen, während ein klassischer Debugger hier zu kurz greift.

In Kombination mit dem quelloffenen Tracing-Paket LTTng in einer Linux-Distribution kann Tracealyzer die unterschiedlichen Performance-Levels deutlich machen. Dies ist vom Prozessor unabhängig und wird nur vom gewählten Algorithmus bestimmt.

Zum Zweck der Evaluierung wird jede Implementierung der Fibonacci-Folge in einem eigenen Modul ausgeführt:

def recur_fibo(n):   if n <=1 n:      return n   else:      return(recur_fibo(n-1) + recur_fibo(n-2))def non_recur_fibo(n):   result = []   a,b = 0,1   while a < n:      result.append(a)      a,b = b, a+b   return result

Separate Python-Quelldateien rufen die beiden oben definierten Funktionen auf:

import lttngustimport loggingimport fibdef example():   logging.basicConfig()   logger = logging.getLogger(‘my-logger’)   logger.info(‘Start’)   fib.recur_fibo(10)   logger.info(‘Stop’)   logger.info(‘Start’)   fib.non_recur_fibo(10)   logger.info(‘Stop’)if __name__ == ‘__main__’:   example()

Die folgenden Anweisungen erfassen in LTTng ein Trace, das anschließend in Tracealyzer untersucht werden kann:

$> lttng create$> lttng enable-event --kernel sched_switch$> lttng enable-event --python my-logger$> lttng start$> python3 <example source file>.py$> lttng stop$> lttng destroy

Bild 1: Untersuchung des mit LTTng erfassten Trace in Percepio Tracealyzer.
Bild 1: Untersuchung des mit LTTng erfassten Trace in Percepio Tracealyzer.
(Bild: MAB Labs / Percepio)

Indem man den standardmäßigen Python-Logger durch einen solchen mit der Bezeichnung „my-logger“ ersetzt, kann Tracealyzer Ereignisse in seiner Trace-Ansicht darstellen. Da Tracealyzer in diesem Beispiel keinerlei Anwendungsdaten erfasst, muss die Software nicht zum Einlesen von Datenwerten konfiguriert werden. Benötigt wird stattdessen nur ein Custom Interval, das die Einstiegs- und Ausstiegspunkte dieser beiden Funktionen markiert.

In der oben gezeigten Trace-Ansicht ist bereits ein deutlicher Performance-Unterschied zu sehen, aber Tracealyzer kann sogar noch besser greifbare Performance-Werte vorlegen. Hierzu geht man in das Menü „Views“, klickt auf den Menüpunkt „Intervals and State Machines“ und erzeugt ein Custom Interval mithilfe der Start- und Stop-Strings, die mit den logger.info()-Aufrufen im Code eingefügt wurden und die Einstiegs- und Ausstiegspunkte der zum Vergleich angetretenen Funktionen markieren.

Auswertung des resultierenden Plots

Wie der Interval Plot (Bild 2) zeigt, besteht zwischen dem zuerst ausgeführten rekursiven Algorithmus und dem als zweites verarbeiteten iterativen Algorithmus ein Performance-Unterschied um den Faktor 20. Im vorliegenden Beispiel werden mit jedem Algorithmus lediglich 10 Fibonacci-Zahlen berechnet. Ohne die Hilfestellung von Tracealyzer müsste man wahrscheinlich deutlich mehr Iterationen ausführen, um aussagefähige Ergebnisse zu erhalten, was aber aus zwei Gründen problematisch wäre.

Bild 2: Interval Plot.
Bild 2: Interval Plot.
(Bild: MAB Labs)

Erstens würde Python beim Ausführen des rekursiven Fibonacci-Algorithmus bis 1.000 (oder auch nur bis 100) scheinbar nicht mehr reagieren und man könnte nicht wissen, ob diese Reaktionslosigkeit auf einen Implementierungsfehler oder andere Dinge zurückzuführen wäre. In der vorliegenden Situation liegt der Grund zwar nahe, aber bei komplexeren Problemen wäre ein erheblicher Logging-Aufwand nötig, um den Engpass tatsächlich einzukreisen.

Zweitens könnten etwaige andere Anwendungen, die auf einem Embedded-System laufen, die Ausführung der jeweils interessierenden Applikation unterbrechen und dadurch die Verarbeitungszeit des Algorithmus bzw. der Funktion verlängern, was ohne Tracing nicht ohne weiteres festzustellen wäre.

Die Kombination von LTTng in Python und Tracealyzer macht dagegen deutlich, dass die Ursache des Problems in der grundlegenden Charakteristik des gewählten Algorithmus liegt, was bei der Entwicklung komplexerer Algorithmen von unschätzbarem Wert ist. Die hier gewählte exemplarische Implementierung soll lediglich illustrieren, wie die Leistungsfähigkeit künftiger Algorithmen-Implementierungen evaluiert werden kann. Beim Programmieren ist es grundsätzlich sinnvoll, die zentralen Funktionen als separate Python-Module zu implementieren, zumal sich hierdurch auch das Tracing bestimmter Funktionen vereinfacht.

Jetzt Newsletter abonnieren

Verpassen Sie nicht unsere besten Inhalte

Mit Klick auf „Newsletter abonnieren“ erkläre ich mich mit der Verarbeitung und Nutzung meiner Daten gemäß Einwilligungserklärung (bitte aufklappen für Details) einverstanden und akzeptiere die Nutzungsbedingungen. Weitere Informationen finde ich in unserer Datenschutzerklärung.

Aufklappen für Details zu Ihrer Einwilligung

Da für das Tracing nur ein vernachlässigbar geringer Mehraufwand entsteht, können Tracepoints beim Testen im vorgesehenen Embedded-System verbleiben und sogar in der Produktion in der Applikation belassen werden, sodass mit dem Tracealyzer-Tool auch Performance-Werte aus der Produktions-Codebasis extrahiert werden können. Dies erweist sich als äußerst nützlich zur Durchführung regelmäßiger Systemtests und gestattet die Verwendung einer unveränderten Codebasis, um sicherzustellen, dass die Applikation mit nur minimalen Änderungen funktional korrekt und performant ist.

Fazit: Effizientes Tracing mit minimalem Leistungsaufwand

Durch den Einsatz von Tracealyzer und LTTng zum Erfassen von Performance-Werten in einer Python-Applikation steht eine unschätzbar wertvolle Methode zur Verfügung, die Implementierung eines Algorithmus zu analysieren.

Dank des minimalen Mehraufwands, der mit diesem Ansatz einhergeht, kann der Code für den Einsatz im vorgesehenen Embedded-System beibehalten werden. Dies wiederum erlaubt eine umfangreichere Überwachung der Ziel-Applikation und kommt auch der Analyse der Interaktionen mit anderen Anwendungen sowie dem Betriebssystem zugute. Zum Beispiel kann es sein, dass ein anderer Prozess oder Thread die Verarbeitung der Ziel-Applikation unterbricht und damit die Leistungsfähigkeit beeinflusst. Mit der Kombination aus Tracealyzer und LTTng lassen sich die Ursachen solcher Anomalien identifizieren, was die Entwickler wiederum dazu befähigt, die Implementierung weiter zu verfeinern und sich damit gegen weitere Probleme zu wappnen.

Obwohl die hier als Beispiel gewählte Berechnung der Fibonacci-Folge relativ unspektakulär ist, verdeutlicht sie doch einen zentralen Wesenszug der Python-Sprache und kann damit hilfreich für die Entwicklung komplexerer Implementierungen sein.

Ebenso wird an diesem Beispiel deutlich, wie sinnvoll die Unterteilung eines Designs in mehrere separate Module ist. Mit dem Tracing kann die Leistungsfähigkeit der wichtigen Funktionen, die in diesen Modulen implementiert werden, ohne großen Aufwand gemessen und validiert werden, noch bevor die gesamte System-Implementierung betrachtet wird. Hierdurch lässt sich bei nur geringfügigen Modifikationen an der Zielumgebung leichter der Nachweis erbringen, dass eine Applikation funktional korrekt und performant ist.

* Mohammed Billoo ist Gründer und Geschäftsführer von MAB Labs.

(ID:48350903)