Die Python-Profiler

Quellcode: Lib/profile.py und Lib/pstats.py


Einführung in die Profiler

cProfile und profile bieten eine *deterministische Profilerstellung* von Python-Programmen. Ein *Profil* ist eine Sammlung von Statistiken, die beschreibt, wie oft und wie lange verschiedene Teile des Programms ausgeführt wurden. Diese Statistiken können über das Modul pstats in Berichte formatiert werden.

Die Standardbibliothek von Python bietet zwei verschiedene Implementierungen derselben Profiler-Schnittstelle

  1. cProfile wird für die meisten Benutzer empfohlen; es ist eine C-Erweiterung mit geringem Overhead, die sich für die Profilerstellung von lang laufenden Programmen eignet. Basiert auf lsprof, beigesteuert von Brett Rosen und Ted Czotter.

  2. profile, ein reines Python-Modul, dessen Schnittstelle von cProfile nachgeahmt wird, das aber einen erheblichen Overhead für profilierte Programme hinzufügt. Wenn Sie versuchen, den Profiler auf irgendeine Weise zu erweitern, ist die Aufgabe mit diesem Modul möglicherweise einfacher. Ursprünglich von Jim Roskind entworfen und geschrieben.

Hinweis

Die Profiler-Module sollen ein Ausführungsprofil für ein gegebenes Programm bereitstellen, nicht zu Benchmark-Zwecken (dafür gibt es timeit für einigermaßen genaue Ergebnisse). Dies gilt insbesondere für das Benchmarking von Python-Code gegen C-Code: Die Profiler führen Overhead für Python-Code ein, aber nicht für Funktionen auf C-Ebene, sodass der C-Code schneller erscheint als jeder Python-Code.

Sofortiges Benutzerhandbuch

Dieser Abschnitt ist für Benutzer gedacht, die „das Handbuch nicht lesen wollen“. Er bietet einen sehr kurzen Überblick und ermöglicht es einem Benutzer, schnell mit der Profilerstellung einer vorhandenen Anwendung zu beginnen.

Um eine Funktion zu profilieren, die ein einzelnes Argument annimmt, können Sie Folgendes tun:

import cProfile
import re
cProfile.run('re.compile("foo|bar")')

(Verwenden Sie profile anstelle von cProfile, falls letzteres auf Ihrem System nicht verfügbar ist.)

Die obige Aktion würde re.compile() ausführen und Profilergebnisse wie die folgenden ausgeben:

      214 function calls (207 primitive calls) in 0.002 seconds

Ordered by: cumulative time

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000    0.002    0.002 {built-in method builtins.exec}
     1    0.000    0.000    0.001    0.001 <string>:1(<module>)
     1    0.000    0.000    0.001    0.001 __init__.py:250(compile)
     1    0.000    0.000    0.001    0.001 __init__.py:289(_compile)
     1    0.000    0.000    0.000    0.000 _compiler.py:759(compile)
     1    0.000    0.000    0.000    0.000 _parser.py:937(parse)
     1    0.000    0.000    0.000    0.000 _compiler.py:598(_code)
     1    0.000    0.000    0.000    0.000 _parser.py:435(_parse_sub)

Die erste Zeile zeigt an, dass 214 Aufrufe überwacht wurden. Davon waren 207 *primitive* Aufrufe, was bedeutet, dass der Aufruf nicht durch Rekursion ausgelöst wurde. Die nächste Zeile: Ordered by: cumulative time zeigt an, dass die Ausgabe nach den cumtime-Werten sortiert ist. Die Spaltenüberschriften umfassen:

ncalls

für die Anzahl der Aufrufe.

tottime

für die insgesamt in der gegebenen Funktion verbrachte Zeit (ohne die Zeit, die für Aufrufe von Unterfunktionen benötigt wurde)

percall

ist der Quotient aus tottime geteilt durch ncalls

cumtime

ist die kumulierte Zeit, die in dieser und allen Unterfunktionen verbracht wurde (von der Ausführung bis zum Ende). Diese Zahl ist *auch* für rekursive Funktionen korrekt.

percall

ist der Quotient aus cumtime geteilt durch primitive Aufrufe

filename:lineno(function)

liefert die jeweiligen Daten jeder Funktion

Wenn in der ersten Spalte zwei Zahlen stehen (z. B. 3/1), bedeutet dies, dass die Funktion rekursiv war. Der zweite Wert ist die Anzahl der primitiven Aufrufe und der erste Wert ist die Gesamtzahl der Aufrufe. Beachten Sie, dass diese beiden Werte gleich sind, wenn die Funktion nicht rekursiv ist, und nur die einzelne Zahl ausgegeben wird.

Anstatt die Ausgabe am Ende des Profilerstellungslaufs auszugeben, können Sie die Ergebnisse in einer Datei speichern, indem Sie der Funktion run() einen Dateinamen übergeben:

import cProfile
import re
cProfile.run('re.compile("foo|bar")', 'restats')

Die Klasse pstats.Stats liest Profilergebnisse aus einer Datei und formatiert sie auf verschiedene Weise.

Die Profiler-Module cProfile und profile können auch als Skripte aufgerufen werden, um andere Skripte zu profilieren. Zum Beispiel:

python -m cProfile [-o output_file] [-s sort_order] (-m module | myscript.py)
-o <output_file>

Schreibt die Profilergebnisse in eine Datei anstatt in stdout.

-s <sort_order>

Gibt eine der Sortierwerte von sort_stats() an, um die Ausgabe zu sortieren. Dies gilt nur, wenn -o nicht angegeben ist.

-m <module>

Gibt an, dass ein Modul anstelle eines Skripts profiliert wird.

Hinzugefügt in Version 3.7: Die Option -m wurde zu cProfile hinzugefügt.

Hinzugefügt in Version 3.8: Die Option -m wurde zu profile hinzugefügt.

Die Klasse Stats des Moduls pstats verfügt über verschiedene Methoden zur Manipulation und Ausgabe der in einer Profilergebnisdatei gespeicherten Daten.

import pstats
from pstats import SortKey
p = pstats.Stats('restats')
p.strip_dirs().sort_stats(-1).print_stats()

Die Methode strip_dirs() entfernt den überflüssigen Pfad aus allen Modulnamen. Die Methode sort_stats() sortiert alle Einträge entsprechend dem Standard-String aus Modul/Zeile/Name, der ausgegeben wird. Die Methode print_stats() gibt alle Statistiken aus. Möglicherweise versuchen Sie die folgenden Sortieraufrufe:

p.sort_stats(SortKey.NAME)
p.print_stats()

Der erste Aufruf sortiert die Liste tatsächlich nach Funktionsnamen, und der zweite Aufruf gibt die Statistiken aus. Hier sind einige interessante Aufrufe zum Experimentieren:

p.sort_stats(SortKey.CUMULATIVE).print_stats(10)

Dies sortiert das Profil nach kumulierter Zeit in einer Funktion und gibt dann nur die zehn wichtigsten Zeilen aus. Wenn Sie verstehen möchten, welche Algorithmen Zeit verbrauchen, ist die obige Zeile das, was Sie verwenden würden.

Wenn Sie sehen möchten, welche Funktionen viele Schleifen hatten und viel Zeit verbrauchten, würden Sie Folgendes tun:

p.sort_stats(SortKey.TIME).print_stats(10)

um nach der in jeder Funktion verbrachten Zeit zu sortieren und dann die Statistiken für die Top-Ten-Funktionen auszugeben.

Sie könnten auch versuchen:

p.sort_stats(SortKey.FILENAME).print_stats('__init__')

Dies sortiert alle Statistiken nach Dateinamen und gibt dann nur Statistiken für die Klassen-Init-Methoden aus (da sie mit __init__ darin geschrieben sind). Als letztes Beispiel könnten Sie versuchen:

p.sort_stats(SortKey.TIME, SortKey.CUMULATIVE).print_stats(.5, 'init')

Diese Zeile sortiert Statistiken mit dem Primärschlüssel Zeit und dem Sekundärschlüssel kumulative Zeit und gibt dann einige der Statistiken aus. Genauer gesagt, die Liste wird zunächst auf 50 % (bezüglich .5) ihrer ursprünglichen Größe reduziert, dann werden nur Zeilen beibehalten, die init enthalten, und diese Unter-Unterliste wird ausgegeben.

Wenn Sie sich gefragt haben, welche Funktionen die obigen Funktionen aufgerufen haben, könnten Sie jetzt (p ist immer noch nach dem letzten Kriterium sortiert) Folgendes tun:

p.print_callers(.5, 'init')

und Sie würden eine Liste von Aufrufern für jede der aufgeführten Funktionen erhalten.

Wenn Sie mehr Funktionalität wünschen, müssen Sie das Handbuch lesen oder erraten, was die folgenden Funktionen tun:

p.print_callees()
p.add('restats')

Als Skript aufgerufen, ist das Modul pstats ein Statistikbrowser zum Lesen und Untersuchen von Profil-Dumps. Es verfügt über eine einfache zeilenorientierte Schnittstelle (implementiert mit cmd) und interaktive Hilfe.

Referenz der Module profile und cProfile

Sowohl die Module profile als auch cProfile stellen die folgenden Funktionen bereit:

profile.run(command, filename=None, sort=-1)

Diese Funktion nimmt ein einzelnes Argument entgegen, das an die Funktion exec() übergeben werden kann, und einen optionalen Dateinamen. In allen Fällen führt diese Routine Folgendes aus:

exec(command, __main__.__dict__, __main__.__dict__)

und sammelt Profilstatistiken aus der Ausführung. Wenn kein Dateiname vorhanden ist, erstellt diese Funktion automatisch eine Instanz von Stats und gibt einen einfachen Profilbericht aus. Wenn der Sortierwert angegeben ist, wird er an diese Instanz von Stats übergeben, um zu steuern, wie die Ergebnisse sortiert werden.

profile.runctx(command, globals, locals, filename=None, sort=-1)

Diese Funktion ist ähnlich wie run(), mit zusätzlichen Argumenten zur Angabe der globalen und lokalen Mappings für den *command*-String. Diese Routine führt Folgendes aus:

exec(command, globals, locals)

und sammelt Profilstatistiken wie in der Funktion run() oben.

class profile.Profile(timer=None, timeunit=0.0, subcalls=True, builtins=True)

Diese Klasse wird normalerweise nur verwendet, wenn eine präzisere Steuerung der Profilerstellung erforderlich ist, als sie die Funktion cProfile.run() bietet.

Ein benutzerdefinierter Timer kann über das Argument *timer* zum Messen der Ausführungszeit von Code bereitgestellt werden. Dies muss eine Funktion sein, die eine einzelne Zahl zurückgibt, die die aktuelle Zeit darstellt. Wenn die Zahl eine Ganzzahl ist, gibt *timeunit* einen Multiplikator an, der die Dauer jeder Zeiteinheit festlegt. Wenn der Timer beispielsweise Zeiten in Tausendstelsekunden zurückgibt, wäre die Zeiteinheit .001.

Die direkte Verwendung der Klasse Profile ermöglicht die Formatierung von Profilergebnissen, ohne die Profildaten in eine Datei zu schreiben:

import cProfile, pstats, io
from pstats import SortKey
pr = cProfile.Profile()
pr.enable()
# ... do something ...
pr.disable()
s = io.StringIO()
sortby = SortKey.CUMULATIVE
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
ps.print_stats()
print(s.getvalue())

Die Klasse Profile kann auch als Kontextmanager verwendet werden (nur im Modul cProfile unterstützt. siehe Context Manager Types)

import cProfile

with cProfile.Profile() as pr:
    # ... do something ...

    pr.print_stats()

Geändert in Version 3.8: Kontextmanager-Unterstützung hinzugefügt.

enable()

Beginnt mit der Erfassung von Profildaten. Nur in cProfile.

disable()

Stoppt die Erfassung von Profildaten. Nur in cProfile.

create_stats()

Stoppt die Erfassung von Profildaten und zeichnet die Ergebnisse intern als aktuelles Profil auf.

print_stats(sort=-1)

Erstellt ein Stats-Objekt basierend auf dem aktuellen Profil und gibt die Ergebnisse auf stdout aus.

Der Parameter *sort* gibt die Sortierreihenfolge der angezeigten Statistiken an. Er akzeptiert einen einzelnen Schlüssel oder ein Tupel von Schlüsseln für die mehrstufige Sortierung, wie in Stats.sort_stats.

Hinzugefügt in Version 3.13: print_stats() akzeptiert jetzt ein Tupel von Schlüsseln.

dump_stats(filename)

Schreibt die Ergebnisse des aktuellen Profils in die Datei *filename*.

run(cmd)

Profiliert den Befehl *cmd* über exec().

runctx(cmd, globals, locals)

Profiliert den Befehl *cmd* über exec() mit der angegebenen globalen und lokalen Umgebung.

runcall(func, /, *args, **kwargs)

Profiliert func(*args, **kwargs)

Beachten Sie, dass die Profilerstellung nur funktioniert, wenn der aufgerufene Befehl/die Funktion tatsächlich zurückkehrt. Wenn der Interpreter beendet wird (z. B. durch einen Aufruf von sys.exit() während der Ausführung des aufgerufenen Befehls/der Funktion), werden keine Profilergebnisse ausgegeben.

Die Klasse Stats

Die Analyse der Profildaten erfolgt mit der Klasse Stats.

class pstats.Stats(*filenames or profile, stream=sys.stdout)

Dieser Konstruktor der Klasse erstellt eine Instanz eines „Statistikobjekts“ aus einem *Dateinamen* (oder einer Liste von Dateinamen) oder aus einer Instanz von Profile. Die Ausgabe wird in den von *stream* angegebenen Stream geschrieben.

Die durch den obigen Konstruktor ausgewählte Datei muss von der entsprechenden Version von profile oder cProfile erstellt worden sein. Genauer gesagt gibt es *keine* Garantie für Dateikompatibilität mit zukünftigen Versionen dieses Profilers, und es gibt keine Kompatibilität mit Dateien, die von anderen Profilern oder demselben Profiler, der unter einem anderen Betriebssystem ausgeführt wurde, erzeugt wurden. Wenn mehrere Dateien angegeben werden, werden alle Statistiken für identische Funktionen zusammengeführt, sodass eine Gesamtansicht mehrerer Prozesse in einem einzigen Bericht betrachtet werden kann. Wenn zusätzliche Dateien mit Daten in einem vorhandenen Stats-Objekt kombiniert werden müssen, kann die Methode add() verwendet werden.

Anstatt die Profildaten aus einer Datei zu lesen, kann ein Objekt von cProfile.Profile oder profile.Profile als Quelle der Profildaten verwendet werden.

Stats-Objekte haben die folgenden Methoden:

strip_dirs()

Diese Methode für die Klasse Stats entfernt alle führenden Pfadinformationen aus Dateinamen. Sie ist sehr nützlich, um die Größe des Ausdrucks zu reduzieren, sodass er innerhalb von (nahezu) 80 Spalten passt. Diese Methode modifiziert das Objekt, und die entfernten Informationen gehen verloren. Nach der Durchführung einer Strip-Operation wird das Objekt als in „zufälliger“ Reihenfolge befindlich betrachtet, so wie es unmittelbar nach der Objektinitialisierung und dem Laden war. Wenn strip_dirs() dazu führt, dass zwei Funktionsnamen nicht mehr unterscheidbar sind (sie befinden sich in derselben Zeile derselben Datei und haben denselben Funktionsnamen), dann werden die Statistiken für diese beiden Einträge zu einem einzigen Eintrag zusammengeführt.

add(*filenames)

Diese Methode der Klasse Stats akkumuliert zusätzliche Profildaten in das aktuelle Profiling-Objekt. Ihre Argumente sollten sich auf von der entsprechenden Version von profile.run() oder cProfile.run() erstellte Dateinamen beziehen. Statistiken für identisch benannte Funktionen (bezüglich Datei, Zeile, Name) werden automatisch zu einzelnen Funktionsstatistiken akkumuliert.

dump_stats(filename)

Speichert die in das Stats-Objekt geladenen Daten in einer Datei namens *filename*. Die Datei wird erstellt, wenn sie nicht existiert, und überschrieben, wenn sie bereits existiert. Dies ist äquivalent zur gleichnamigen Methode der Klassen profile.Profile und cProfile.Profile.

sort_stats(*keys)

Diese Methode modifiziert das Stats-Objekt, indem sie es nach den angegebenen Kriterien sortiert. Das Argument kann entweder ein String oder ein SortKey-Enum sein, das die Basis einer Sortierung identifiziert (Beispiel: 'time', 'name', SortKey.TIME oder SortKey.NAME). Das Argument SortKey-Enums hat gegenüber dem String-Argument den Vorteil, dass es robuster und weniger fehleranfällig ist.

Wenn mehr als ein Schlüssel angegeben wird, werden zusätzliche Schlüssel als sekundäre Kriterien verwendet, wenn bei allen vorher ausgewählten Schlüsseln Gleichheit besteht. Zum Beispiel sortiert sort_stats(SortKey.NAME, SortKey.FILE) alle Einträge nach ihrem Funktionsnamen und löst alle Gleichstände (identische Funktionsnamen) durch Sortierung nach Dateinamen auf.

Für das String-Argument können Abkürzungen für beliebige Schlüsselnamen verwendet werden, solange die Abkürzung eindeutig ist.

Die folgenden sind die gültigen Zeichenketten- und SortKey-Argumente:

Gültiges String-Argument

Gültiges Enum-Argument

Bedeutung

'calls'

SortKey.CALLS

Anzahl der Aufrufe

'cumulative'

SortKey.CUMULATIVE

kumulierte Zeit

'cumtime'

N/A

kumulierte Zeit

'file'

N/A

file name

'filename'

SortKey.FILENAME

file name

'module'

N/A

file name

'ncalls'

N/A

Anzahl der Aufrufe

'pcalls'

SortKey.PCALLS

Anzahl primitiver Aufrufe

'line'

SortKey.LINE

Zeilennummer

'name'

SortKey.NAME

Funktionsname

'nfl'

SortKey.NFL

name/file/line

'stdname'

SortKey.STDNAME

Standardname

'time'

SortKey.TIME

Interne Zeit

'tottime'

N/A

Interne Zeit

Beachten Sie, dass alle Sortierungen nach Statistiken in absteigender Reihenfolge erfolgen (die zeitaufwändigsten Elemente zuerst), während Namens-, Datei- und Zeilennummernsuchen in aufsteigender Reihenfolge (alphabetisch) erfolgen. Der feine Unterschied zwischen SortKey.NFL und SortKey.STDNAME besteht darin, dass der Standardname eine Sortierung des gedruckten Namens ist, was bedeutet, dass die eingebetteten Zeilennummern auf eine seltsame Weise verglichen werden. Zum Beispiel würden die Zeilen 3, 20 und 40 (wenn die Dateinamen gleich wären) in der Zeichenreihenfolge 20, 3 und 40 erscheinen. Im Gegensatz dazu führt SortKey.NFL einen numerischen Vergleich der Zeilennummern durch. Tatsächlich ist sort_stats(SortKey.NFL) dasselbe wie sort_stats(SortKey.NAME, SortKey.FILENAME, SortKey.LINE).

Aus Gründen der Abwärtskompatibilität sind die numerischen Argumente -1, 0, 1 und 2 zulässig. Sie werden als 'stdname', 'calls', 'time' und 'cumulative' interpretiert. Wenn dieses alte Format (numerisch) verwendet wird, wird nur ein Sortierschlüssel (der numerische Schlüssel) verwendet und zusätzliche Argumente werden stillschweigend ignoriert.

Hinzugefügt in Version 3.7: Die SortKey-Enum wurde hinzugefügt.

reverse_order()

Diese Methode für die Klasse Stats kehrt die Reihenfolge der Basisliste innerhalb des Objekts um. Beachten Sie, dass standardmäßig die aufsteigende vs. absteigende Reihenfolge basierend auf dem gewählten Sortierschlüssel richtig ausgewählt wird.

print_stats(*restrictions)

Diese Methode für die Klasse Stats gibt einen Bericht aus, wie in der Definition von profile.run() beschrieben.

Die Reihenfolge der Ausgabe basiert auf der letzten sort_stats()-Operation, die auf dem Objekt ausgeführt wurde (vorbehaltlich der Vorbehalte in add() und strip_dirs()).

Die angegebenen Argumente (falls vorhanden) können verwendet werden, um die Liste auf die signifikanten Einträge zu beschränken. Anfangs wird die Liste als die vollständige Menge der profilierten Funktionen betrachtet. Jede Einschränkung ist entweder eine Ganzzahl (um eine Anzahl von Zeilen auszuwählen), ein Dezimalbruch zwischen 0,0 und 1,0 (einschließlich, um einen Prozentsatz von Zeilen auszuwählen) oder eine Zeichenkette, die als regulärer Ausdruck interpretiert wird (um den Standardnamen abzugleichen, der gedruckt wird). Wenn mehrere Einschränkungen angegeben sind, werden sie nacheinander angewendet. Zum Beispiel

print_stats(.1, 'foo:')

würde zunächst die Ausgabe auf die ersten 10% der Liste beschränken und dann nur Funktionen drucken, die Teil des Dateinamens .*foo: waren. Im Gegensatz dazu würde der Befehl

print_stats('foo:', .1)

die Liste auf alle Funktionen mit Dateinamen .*foo: beschränken und dann nur die ersten 10% davon drucken.

print_callers(*restrictions)

Diese Methode für die Klasse Stats gibt eine Liste aller Funktionen aus, die jede Funktion in der profilierten Datenbank aufgerufen haben. Die Reihenfolge ist identisch mit der von print_stats(), und die Definition des einschränkenden Arguments ist ebenfalls identisch. Jeder Aufrufer wird auf seiner eigenen Zeile gemeldet. Das Format unterscheidet sich geringfügig je nach dem Profiler, der die Statistiken erstellt hat

  • Mit profile wird nach jedem Aufrufer eine Zahl in Klammern angezeigt, die angibt, wie oft dieser spezifische Aufruf gemacht wurde. Der Einfachheit halber wiederholt eine zweite, nicht in Klammern gesetzte Zahl die kumulative Zeit, die in der Funktion verbracht wurde, auf der rechten Seite.

  • Mit cProfile wird jedem Aufrufer eine Zahl vorangestellt: die Anzahl der Male, die dieser spezifische Aufruf getätigt wurde, sowie die Gesamt- und kumulative Zeit, die in der aktuellen Funktion verbracht wurde, während sie von diesem spezifischen Aufrufer aufgerufen wurde.

print_callees(*restrictions)

Diese Methode für die Klasse Stats gibt eine Liste aller Funktionen aus, die von der angegebenen Funktion aufgerufen wurden. Abgesehen von dieser Umkehrung der Aufrufsrichtung (bezüglich aufgerufen vs. wurde aufgerufen von) sind die Argumente und die Reihenfolge identisch mit der Methode print_callers().

get_stats_profile()

Diese Methode gibt eine Instanz von StatsProfile zurück, die eine Zuordnung von Funktionsnamen zu Instanzen von FunctionProfile enthält. Jede FunctionProfile-Instanz enthält Informationen im Zusammenhang mit dem Profil der Funktion, z. B. wie lange die Funktion lief, wie oft sie aufgerufen wurde usw.

Hinzugefügt in Version 3.9: Die folgenden Dataclasses wurden hinzugefügt: StatsProfile, FunctionProfile. Die folgende Funktion wurde hinzugefügt: get_stats_profile.

Was ist deterministische Profilerstellung?

Deterministische Profilerstellung soll die Tatsache widerspiegeln, dass alle Funktionsaufruf-, Funktionsrückgabe- und Ausnahme-Ereignisse überwacht werden und genaue Zeitmessungen für die Intervalle zwischen diesen Ereignissen vorgenommen werden (während der Code des Benutzers ausgeführt wird). Im Gegensatz dazu werden bei der statistischen Profilerstellung (die von diesem Modul nicht durchgeführt wird) zufällig der effektive Befehlszeiger abgetastet und Rückschlüsse darauf gezogen, wo die Zeit verbracht wird. Letztere Technik hat traditionell weniger Overhead (da der Code nicht instrumentiert werden muss), liefert aber nur relative Hinweise darauf, wo Zeit verbracht wird.

In Python, da während der Ausführung ein Interpreter aktiv ist, ist die Anwesenheit von instrumentiertem Code nicht erforderlich, um deterministische Profilerstellung durchzuführen. Python stellt automatisch einen Hook (optionalen Callback) für jedes Ereignis bereit. Darüber hinaus neigt die interpretierte Natur von Python dazu, so viel Overhead bei der Ausführung zu verursachen, dass die deterministische Profilerstellung bei typischen Anwendungen nur einen geringen Verarbeitungs-Overhead hinzufügt. Das Ergebnis ist, dass die deterministische Profilerstellung nicht sehr teuer ist und dennoch umfassende Laufzeitstatistiken über die Ausführung eines Python-Programms liefert.

Aufrufanzahlstatistiken können verwendet werden, um Fehler im Code zu identifizieren (überraschende Anzahlen) und mögliche Inlining-Erweiterungspunkte zu identifizieren (hohe Aufrufanzahlen). Interne Zeitstatistiken können verwendet werden, um "heiße Schleifen" zu identifizieren, die sorgfältig optimiert werden sollten. Kumulative Zeitstatistiken sollten verwendet werden, um übergreifende Fehler bei der Auswahl von Algorithmen zu identifizieren. Beachten Sie, dass die ungewöhnliche Behandlung kumulativer Zeiten in diesem Profiler es ermöglicht, Statistiken für rekursive Implementierungen von Algorithmen direkt mit iterativen Implementierungen zu vergleichen.

Einschränkungen

Eine Einschränkung betrifft die Genauigkeit der Zeitinformationen. Es gibt ein grundlegendes Problem mit deterministischen Profilern, das die Genauigkeit betrifft. Die offensichtlichste Einschränkung ist, dass die zugrundeliegende "Uhr" nur mit einer Rate (typischerweise) von etwa 0,001 Sekunden tickt. Daher werden keine Messungen genauer sein als die zugrundeliegende Uhr. Wenn genügend Messungen durchgeführt werden, wird sich der "Fehler" tendenziell ausmitteln. Leider induziert das Entfernen dieses ersten Fehlers eine zweite Fehlerquelle.

Das zweite Problem ist, dass es "eine Weile dauert", von dem Zeitpunkt, an dem ein Ereignis ausgelöst wird, bis der Aufruf des Profilers zur Ermittlung der Zeit tatsächlich den Zustand der Uhr *abruft*. Ebenso gibt es eine gewisse Verzögerung beim Verlassen des Event-Handlers des Profilers vom Zeitpunkt der Erfassung (und anschließenden Speicherung) des Wertes der Uhr bis zur erneuten Ausführung des Codes des Benutzers. Infolgedessen werden Funktionen, die oft aufgerufen werden oder viele Funktionen aufrufen, diesen Fehler typischerweise akkumulieren. Der Fehler, der sich auf diese Weise ansammelt, ist typischerweise kleiner als die Genauigkeit der Uhr (weniger als ein Taktzyklus), aber er *kann* sich ansammeln und sehr bedeutsam werden.

Das Problem ist bei profile wichtiger als bei dem geringeren Overhead von cProfile. Aus diesem Grund bietet profile eine Möglichkeit, sich für eine bestimmte Plattform zu kalibrieren, so dass dieser Fehler probabilistisch (im Durchschnitt) entfernt werden kann. Nachdem der Profiler kalibriert ist, ist er genauer (im Sinne der kleinsten Quadrate), aber er wird manchmal negative Zahlen ergeben (wenn die Aufrufanzahlen außergewöhnlich niedrig sind und die Götter der Wahrscheinlichkeit gegen Sie arbeiten :-)).) Seien Sie nicht beunruhigt von negativen Zahlen im Profil. Sie sollten *nur* erscheinen, wenn Sie Ihren Profiler kalibriert haben und die Ergebnisse tatsächlich besser sind als ohne Kalibrierung.

Kalibrierung

Der Profiler des Moduls profile subtrahiert eine Konstante von jeder Ereignisbehandlungszeit, um den Overhead für den Aufruf der Zeitfunktion und die Speicherung der Ergebnisse zu kompensieren. Standardmäßig ist die Konstante 0. Das folgende Verfahren kann verwendet werden, um eine bessere Konstante für eine bestimmte Plattform zu erhalten (siehe Einschränkungen).

import profile
pr = profile.Profile()
for i in range(5):
    print(pr.calibrate(10000))

Die Methode führt die durch das Argument gegebene Anzahl von Python-Aufrufen aus, direkt und wieder unter dem Profiler, und misst die Zeit für beide. Sie berechnet dann den versteckten Overhead pro Profilereignis und gibt diesen als Float zurück. Zum Beispiel ergibt sich auf einem 1,8-GHz-Intel-Core-i5-Prozessor unter macOS und unter Verwendung der Prozesszeitfunktion von Python (`time.process_time()`) als Timer die magische Zahl von etwa 4,04e-6.

Ziel dieser Übung ist es, ein relativ konsistentes Ergebnis zu erzielen. Wenn Ihr Computer *sehr* schnell ist oder Ihre Timer-Funktion eine schlechte Auflösung hat, müssen Sie möglicherweise 100000 oder sogar 1000000 übergeben, um konsistente Ergebnisse zu erzielen.

Wenn Sie eine konsistente Antwort haben, gibt es drei Möglichkeiten, sie zu nutzen

import profile

# 1. Apply computed bias to all Profile instances created hereafter.
profile.Profile.bias = your_computed_bias

# 2. Apply computed bias to a specific Profile instance.
pr = profile.Profile()
pr.bias = your_computed_bias

# 3. Specify computed bias in instance constructor.
pr = profile.Profile(bias=your_computed_bias)

Wenn Sie die Wahl haben, ist es besser, eine kleinere Konstante zu wählen, und Ihre Ergebnisse werden dann seltener als negativ in den Profilstatistiken angezeigt.

Verwendung eines benutzerdefinierten Timers

Wenn Sie ändern möchten, wie die aktuelle Zeit ermittelt wird (z. B. um die Verwendung der Wanduhrzeit oder der verstrichenen Prozesszeit zu erzwingen), übergeben Sie die gewünschte Timing-Funktion dem Konstruktor der Klasse Profile

pr = profile.Profile(your_time_func)

Der resultierende Profiler ruft dann your_time_func auf. Je nachdem, ob Sie profile.Profile oder cProfile.Profile verwenden, wird der Rückgabewert von your_time_func unterschiedlich interpretiert.

profile.Profile

your_time_func sollte eine einzelne Zahl oder eine Liste von Zahlen zurückgeben, deren Summe die aktuelle Zeit ist (wie bei os.times()). Wenn die Funktion eine einzelne Zeitnummer zurückgibt oder die Liste der zurückgegebenen Nummern die Länge 2 hat, erhalten Sie eine besonders schnelle Version der Dispatch-Routine.

Beachten Sie, dass Sie die Profilerklasse für die gewählte Timer-Funktion kalibrieren sollten (siehe Kalibrierung). Für die meisten Maschinen liefert ein Timer, der einen einzelnen Ganzzahlwert zurückgibt, die besten Ergebnisse in Bezug auf geringen Overhead während der Profilerstellung. ( os.times() ist *ziemlich* schlecht, da er ein Tupel von Gleitkommawerten zurückgibt.) Wenn Sie einen besseren Timer auf die sauberste Weise ersetzen möchten, leiten Sie eine Klasse ab und überschreiben Sie hartcodiert eine Dispatch-Methode, die Ihren Timer-Aufruf am besten verarbeitet, zusammen mit der entsprechenden Kalibrierungskonstante.

cProfile.Profile

your_time_func sollte eine einzelne Zahl zurückgeben. Wenn sie Ganzzahlen zurückgibt, können Sie den Klassenkonstruktor auch mit einem zweiten Argument aufrufen, das die reale Dauer einer Zeiteinheit angibt. Wenn beispielsweise your_integer_time_func Zeiten in Tausendstelsekunden zurückgibt, würden Sie die Profile Instanz wie folgt konstruieren

pr = cProfile.Profile(your_integer_time_func, 0.001)

Da die Klasse cProfile.Profile nicht kalibriert werden kann, sollten benutzerdefinierte Timer-Funktionen mit Vorsicht verwendet werden und so schnell wie möglich sein. Für die besten Ergebnisse mit einem benutzerdefinierten Timer kann es notwendig sein, ihn im C-Quellcode des internen Moduls _lsprof fest zu codieren.

Python 3.3 fügt mehrere neue Funktionen in time hinzu, die zur präzisen Messung von Prozess- oder Wanduhrzeit verwendet werden können. Siehe beispielsweise time.perf_counter().