Verwendung von cProfile zum Profilieren von Python-Code

Python ist vielleicht nicht die schnellste Sprache, aber es ist oft schnell genug. Und Python ist ideal, wenn die Programmierzeit wichtiger ist als die CPU-Zeit.

Das heißt, wenn eine bestimmte Python-App verzögert ist, müssen Sie sie nicht einfach aufsaugen. Die in der Standardinstallation des Python-Interpreters enthaltenen Tools können Ihnen detailliertes Feedback darüber geben, welche Teile Ihres Programms langsam sind, und einige Hinweise dazu geben, wie Sie sie beschleunigen können.

Verwendung von cProfile

Das cProfileModul sammelt Statistiken über die Ausführungszeit eines Python-Programms. Es kann über alles von der gesamten App bis zu einer einzelnen Anweisung oder einem Ausdruck berichten.

Hier ist ein Spielzeugbeispiel für die Verwendung cProfile:

def add (x, y): x + = str (y) return x def add_2 (x, y): wenn y% 20000 == 0: z = [] für q im Bereich (0,400000): z.append ( q) def main (): a = [] für n im Bereich (0,200000): addiere (a, n) add_2 (a, n) wenn __name__ == '__main__': importiere cProfile cProfile.run ('main ( ) ') 

In diesem Beispiel wird die main()Funktion der Anwendung ausgeführt und die Leistung main()aller main()Aufrufe analysiert . Es ist auch möglich, nur einen Teil eines Programms zu analysieren.  Am häufigsten wird jedoch zunächst das Profil des gesamten Programms verwendet.

Führen Sie das obige Beispiel aus, und Sie werden mit der folgenden Ausgabe begrüßt:

Was hier gezeigt wird, ist eine Liste aller vom Programm durchgeführten Funktionsaufrufe zusammen mit Statistiken zu jedem:

  • Oben (erste Zeile in Blau) sehen wir die Gesamtzahl der im Profilprogramm getätigten Aufrufe und die Gesamtausführungszeit. Möglicherweise sehen Sie auch eine Zahl für "primitive Aufrufe", dh nicht rekursive Aufrufe, oder Aufrufe direkt an eine Funktion, die sich nicht weiter unten im Aufrufstapel aufruft.
  • ncalls : Anzahl der getätigten Anrufe. Wenn Sie zwei durch einen Schrägstrich getrennte Zahlen sehen, ist die zweite Zahl die Anzahl der primitiven Aufrufe für diese Funktion.
  • tottime : Gesamtzeit, die in der Funktion verbracht wurde, ohne Aufrufe anderer Funktionen.
  • Percall : Durchschnittliche Zeit pro Anruf für Tottime , abgeleitet aus Tottime und Division durch ncalls .
  • cumtime : Gesamtzeit, die in der Funktion verbracht wurde, einschließlich Aufrufen anderer Funktionen.
  • percall (# 2): Durchschnittliche Zeit pro Anruf für cumtime ( cumtime geteilt durch ncalls ).
  • Dateiname: lineno : Der Dateiname, die Zeilennummer und der Funktionsname für den betreffenden Anruf.

So ändern Sie cProfile-Berichte

Standardmäßig wird cProfiledie Ausgabe nach "Standardname" sortiert, dh nach dem Text in der rechten Spalte (Dateiname, Zeilennummer usw.).

Das Standardformat ist nützlich, wenn Sie einen allgemeinen Top-Down-Bericht über jeden einzelnen Funktionsaufruf als Referenz wünschen. Wenn Sie jedoch versuchen, einem Engpass auf den Grund zu gehen, möchten Sie wahrscheinlich die zeitaufwändigsten Teile des Programms zuerst auflisten.

Wir können diese Ergebnisse erzielen, indem wir cProfile etwas anders aufrufen  . Beachten Sie, wie der untere Teil des obigen Programms überarbeitet werden kann, um die Statistiken nach einer anderen Spalte zu sortieren (in diesem Fall ncalls):

if __name__ == '__main__': cProfile importieren, pstats profiler = cProfile.Profile () profiler.enable () main () profiler.disable () stats = pstats.Stats (profiler) .sort_stats ('ncalls') stats.print_stats () 

Die Ergebnisse sehen ungefähr so ​​aus:

So funktioniert das alles:

  • Anstatt einen Befehl haft auszuführen cProfile.run(), die nicht sehr flexibel ist, schaffen wir ein Profilierungs Objekt , profiler.
  • Wenn wir eine Aktion profilieren möchten, rufen wir zuerst .enable()die Profiler-Objektinstanz auf, führen dann die Aktion aus und rufen dann auf .disable(). (Dies ist eine Möglichkeit, nur einen Teil eines Programms zu profilieren.)
  • Das pstatsModul wird verwendet, um die vom Profiler-Objekt gesammelten Ergebnisse zu bearbeiten und diese Ergebnisse zu drucken.

Durch das Kombinieren eines Profiler-Objekts pstatskönnen wir die erfassten Profildaten bearbeiten, um beispielsweise die generierten Statistiken anders zu sortieren. In diesem Beispiel werden .sort_stats('ncalls')die Statistiken mithilfe von nach ncallsSpalte sortiert . Andere Sortieroptionen sind verfügbar.

Verwendung von cProfile-Ergebnissen zur Optimierung

Die für die cProfile Ausgabe verfügbaren Sortieroptionen ermöglichen es uns, potenzielle Leistungsengpässe in einem Programm herauszufiltern.

ncalls

Die erste und wichtigste Information, mit der Sie aufdecken können, cProfileist, welche Funktionen über die ncallsSpalte am häufigsten aufgerufen werden .

In Python verursacht das bloße Ausführen eines Funktionsaufrufs einen relativ hohen Overhead. Wenn eine Funktion wiederholt in einer engen Schleife aufgerufen wird, auch wenn es sich nicht um eine Funktion mit langer Laufzeit handelt, wirkt sich dies garantiert auf die Leistung aus.

Im obigen Beispiel wird die Funktion add(und die Funktion add_2) wiederholt in einer Schleife aufgerufen. Das Verschieben der Schleife in die addFunktion selbst oder das addvollständige Inlinen der Funktion würde dieses Problem beheben.

tottime

Weitere nützliche statistische Details, welche Funktionen das Programm über die tottimeSpalte am meisten ausführt .

Im obigen Beispiel verwendet die add_2Funktion eine Schleife, um eine teure Berechnung zu simulieren, die ihre tottimePunktzahl nach oben schiebt . Jede Funktion mit einer hohen tottimePunktzahl verdient einen genauen Blick, insbesondere wenn sie mehrmals oder in einer engen Schleife aufgerufen wird.

Beachten Sie, dass Sie immer den Kontext berücksichtigen müssen,  in dem die Funktion verwendet wird. Wenn eine Funktion ein High hat, tottimeaber nur einmal aufgerufen wird - zum Beispiel nur beim Starten des Programms -, ist es weniger wahrscheinlich, dass es sich um einen Engpass handelt. Wenn Sie jedoch versuchen, die Startzeit zu verkürzen, möchten Sie wissen, ob eine beim Start aufgerufene Funktion alles andere warten lässt.

So exportieren Sie cProfile-Daten

Wenn Sie die cProfilegenerierten Statistiken auf erweiterte Weise verwenden möchten , können Sie sie in eine Datendatei exportieren:

stats = pstats.Stats (Profiler) stats.dump_stats ('/ path / to / stats_file.dat') 

Diese Datei kann mithilfe des pstatsModuls zurückgelesen und dann sortiert oder mit angezeigt werden pstats. Die Daten können auch von anderen Programmen wiederverwendet werden. Zwei Beispiele:

  • pyprof2calltreeRendert detaillierte Visualisierungen des Aufrufdiagramms und der Nutzungsstatistiken des Programms aus Profildaten. Dieser Artikel enthält ein detailliertes Beispiel aus der Praxis.
  • snakevizgeneriert auch Visualisierungen aus cProfileDaten, verwendet jedoch eine andere Darstellung für die Daten - einen „Sunburst“ anstelle des „Flammen“ -Diagramms von pyprof2calltree.

Über cProfile für Python-Profiling hinaus

cProfileist kaum die einzige Möglichkeit, eine Python-Anwendung zu profilieren. cProfileist sicherlich eine der bequemsten Möglichkeiten, da es mit Python gebündelt ist. Aber andere verdienen Aufmerksamkeit.

Ein Projekt erstellt py-spyein Profil für eine Python-Anwendung, indem es deren Aufrufaktivität abtastet. py-spykann verwendet werden, um eine laufende Python-App zu untersuchen, ohne sie stoppen und neu starten zu müssen und ohne ihre Codebasis ändern zu müssen, sodass sie zum Profilieren bereitgestellter Anwendungen verwendet werden kann. py-spygeneriert auch einige Statistiken über den Overhead, der durch die Python-Laufzeit entsteht (z. B. Garbage Collection-Overhead), was cProfilenicht der Fall ist.