Profilierung von Python-Code mit timeit und cProfile – KDnuggets

Profilierung von Python-Code mit timeit und cProfile – KDnuggets

Quellknoten: 2874649

Profilierung von Python-Code mit timeit und cProfile
Bild vom Autor
 

Als Softwareentwickler haben Sie das Zitat wahrscheinlich schon einmal gehört „Vorzeitige Optimierung ist die Wurzel allen Übels“– mehr als einmal – in Ihrer Karriere. Während die Optimierung bei kleinen Projekten möglicherweise nicht besonders hilfreich (oder absolut notwendig) ist, ist die Profilerstellung oft hilfreich. 

Nachdem Sie mit dem Codieren eines Moduls fertig sind, empfiehlt es sich, ein Profil Ihres Codes zu erstellen, um zu messen, wie lange die Ausführung der einzelnen Abschnitte dauert. Dies kann dabei helfen, Code-Gerüche zu identifizieren und Optimierungen zur Verbesserung der Codequalität anzuleiten. Profilieren Sie daher immer Ihren Code, bevor Sie ihn optimieren!

Um die ersten Schritte zu unternehmen, hilft Ihnen dieser Leitfaden beim Einstieg in die Profilerstellung in Python – mithilfe der integrierten Zeit und cProfil Module. Sie lernen, sowohl die Befehlszeilenschnittstelle als auch die entsprechenden aufrufbaren Elemente in Python-Skripten zu verwenden.

Das Modul timeit ist Teil der Python-Standardbibliothek und bietet einige praktische Funktionen, mit denen sich kurze Codeausschnitte zeitlich festlegen lassen.

Nehmen wir ein einfaches Beispiel für die Umkehrung einer Python-Liste. Wir messen die Ausführungszeiten für den Erhalt einer umgekehrten Kopie der Liste mithilfe von:

  • reversed() Funktion und
  • Listenschneiden. 
>>> nums=[6,9,2,3,7]
>>> list(reversed(nums))
[7, 3, 2, 9, 6]
>>> nums[::-1]
[7, 3, 2, 9, 6]

 

Läuft timeit in der Befehlszeile

Du kannst rennen timeit in der Befehlszeile mit der Syntax:

$ python -m timeit -s 'setup-code' -n 'number' -r 'repeat' 'stmt'

 

Sie müssen die Erklärung abgeben stmt deren Ausführungszeit gemessen werden soll. 

Sie können die specify setup Geben Sie bei Bedarf Code ein – mit der kurzen Option -s oder der langen Option –setup. Der Setup-Code wird nur einmal ausgeführt.

Das number wie oft die Anweisung ausgeführt werden soll: die kurze Option -n oder die lange Option –number ist optional. Und die Häufigkeit, mit der dieser Zyklus wiederholt werden soll: Die kurze Option -r oder die lange Option –repeat ist ebenfalls optional.

Sehen wir uns das oben Gesagte für unser Beispiel in Aktion an:

Hier erfolgt die Erstellung der Liste setup Code und Umkehren der Liste ist die Anweisung, die zeitlich festgelegt werden soll:

$ python -m timeit -s 'nums=[6,9,2,3,7]' 'list(reversed(nums))'
500000 loops, best of 5: 695 nsec per loop

 

Wenn Sie keine Werte angeben für repeat, wird der Standardwert 5 verwendet. Und wenn Sie es nicht angeben number, wird der Code so oft wie nötig ausgeführt, um eine Gesamtzeit von zu erreichen mindestens 0.2 Sekunden.

In diesem Beispiel wird explizit festgelegt, wie oft die Anweisung ausgeführt werden soll:

$ python -m timeit -s 'nums=[6,9,2,3,7]' -n 100Bu000 'list(reversed(nums))'
100000 loops, best of 5: 540 nsec per loop

 

Der Standardwert von repeat ist 5, aber wir können ihn auf jeden geeigneten Wert setzen:

$ python3 -m timeit -s 'nums=[6,9,2,3,7]' -r 3 'list(reversed(nums))'
500000 loops, best of 3: 663 nsec per loop

 

Lassen Sie uns auch den List-Slicing-Ansatz zeitlich festlegen:

$ python3 -m timeit -s 'nums=[6,9,2,3,7]' 'nums[::-1]'
1000000 loops, best of 5: 142 nsec per loop

 

Der List-Slicing-Ansatz scheint schneller zu sein (alle Beispiele sind in Python 3.10 unter Ubuntu 22.04).

Timeit in einem Python-Skript ausführen

Hier ist das Äquivalent zur Ausführung von timeit im Python-Skript:

import timeit setup = 'nums=[9,2,3,7,6]'
number = 100000
stmt1 = 'list(reversed(nums))'
stmt2 = 'nums[::-1]' t1 = timeit.timeit(setup=setup,stmt=stmt1,number=number)
t2 = timeit.timeit(setup=setup,stmt=stmt2,number=number) print(f"Using reversed() fn.: {t1}")
print(f"Using list slicing: {t2}")

 

Das timeit() Callable gibt die Ausführungszeit von zurück stmt für number der Zeiten. Beachten Sie, dass wir explizit angeben können, wie oft ausgeführt oder erstellt werden soll number Nehmen Sie den Standardwert 1000000.

Output >>
Using reversed() fn.: 0.08982690000000002
Using list slicing: 0.015550800000000004

 

Dadurch wird die Anweisung – ohne Wiederholung der Timer-Funktion – für die angegebene Zeit ausgeführt number der Zeiten und gibt die Ausführungszeit zurück. Es wird auch recht häufig verwendet time.repeat() und nehmen Sie sich die angegebene Mindestzeit:

import timeit setup = 'nums=[9,2,3,7,6]'
number = 100000
stmt1 = 'list(reversed(nums))'
stmt2 = 'nums[::-1]' t1 = min(timeit.repeat(setup=setup,stmt=stmt1,number=number))
t2 = min(timeit.repeat(setup=setup,stmt=stmt2,number=number)) print(f"Using reversed() fn.: {t1}")
print(f"Using list slicing: {t2}")

 

Dadurch wird der Vorgang der Ausführung des Codes wiederholt number von Zeiten repeat Häufigkeit und gibt die minimale Ausführungszeit zurück. Hier haben wir 5 Wiederholungen à 100000 Mal.

Output >>
Using reversed() fn.: 0.055375300000000016
Using list slicing: 0.015101400000000043

Wir haben gesehen, wie timeit verwendet werden kann, um die Ausführungszeiten kurzer Code-Snippets zu messen. In der Praxis ist es jedoch hilfreicher, ein Profil eines gesamten Python-Skripts zu erstellen. 

Dadurch erhalten wir die Ausführungszeiten aller Funktionen und Methodenaufrufe – einschließlich integrierter Funktionen und Methoden. So können wir uns einen besseren Überblick über die teureren Funktionsaufrufe verschaffen und Optimierungsmöglichkeiten identifizieren. Beispiel: Möglicherweise liegt ein API-Aufruf vor, der zu langsam ist. Oder eine Funktion verfügt möglicherweise über eine Schleife, die durch einen pythonischeren Ausdruck ersetzt werden kann. 

Erfahren Sie, wie Sie mit dem cProfile-Modul (ebenfalls Teil der Python-Standardbibliothek) ein Profil für Python-Skripte erstellen. 

Betrachten Sie das folgende Python-Skript:

# main.py
import time def func(num): for i in range(num): print(i) def another_func(num): time.sleep(num) print(f"Slept for {num} seconds") def useful_func(nums, target): if target in nums: return nums.index(target) if __name__ == "__main__": func(1000) another_func(20) useful_func([2, 8, 12, 4], 12)

 

Hier haben wir drei Funktionen:

  • func() das eine Reihe von Zahlen durchläuft und diese ausgibt.
  • another func() das einen Aufruf an die enthält sleep() Funktion.
  • useful_func() Das gibt den Index einer Zielnummer in der Liste zurück (sofern das Ziel in der Liste vorhanden ist).

Die oben aufgeführten Funktionen werden jedes Mal aufgerufen, wenn Sie das Skript main.py ausführen.

cProfile über die Befehlszeile ausführen

Führen Sie cProfile in der Befehlszeile aus mit:

python3 -m file-name.py

 

Hier haben wir die Datei main.py benannt:

python3 -m main.py

 

Wenn Sie dies ausführen, sollten Sie die folgende Ausgabe erhalten:

 Output >> 0 ... 999 Slept for 20 seconds

 

Und das folgende Profil:

 

Profilierung von Python-Code mit timeit und cProfile
 

Hier ncalls bezieht sich auf die Anzahl der Aufrufe der Funktion und percall bezieht sich auf die Zeit pro Funktionsaufruf. Wenn der Wert von ncalls ist also größer als eins percall ist die durchschnittliche Zeit aller Anrufe.

Die Ausführungszeit des Skripts wird dominiert von another_func das nutzt das eingebaute sleep Funktionsaufruf (schläft 20 Sekunden lang). Wir sehen das print Funktionsaufrufe sind auch ziemlich teuer. 

Verwendung von cProfile im Python-Skript

Während die Ausführung von cProfile in der Befehlszeile einwandfrei funktioniert, können Sie die Profilerstellungsfunktionalität auch zum Python-Skript hinzufügen. Sie können cProfile in Verbindung mit dem verwenden pstats-Modul zur Profilerstellung und zum Zugriff auf Statistiken.

Um die Einrichtung und Demontage von Ressourcen besser zu handhaben, empfiehlt es sich, die with-Anweisung zu verwenden und ein Profilobjekt zu erstellen, das als Kontextmanager verwendet wird:

# main.py
import pstats
import time
import cProfile def func(num): for i in range(num): print(i) def another_func(num): time.sleep(num) print(f"Slept for {num} seconds") def useful_func(nums, target): if target in nums: return nums.index(target) if __name__ == "__main__": with cProfile.Profile() as profile: func(1000) another_func(20) useful_func([2, 8, 12, 4], 12) profile_result = pstats.Stats(profile) profile_result.print_stats()

 

Schauen wir uns das generierte Ausgabeprofil genauer an:

 

Profilierung von Python-Code mit timeit und cProfile
 

Wenn Sie ein großes Skript profilieren, ist dies hilfreich Sortieren Sie die Ergebnisse nach Ausführungszeit. Dazu können Sie anrufen sort_stats auf dem Profilobjekt und sortieren Sie nach der Ausführungszeit: 

...
if __name__ == "__main__": with cProfile.Profile() as profile: func(1000) another_func(20) useful_func([2, 8, 12, 4], 12) profile_result = pstats.Stats(profile) profile_result.sort_stats(pstats.SortKey.TIME) profile_result.print_stats()

 

Wenn Sie nun das Skript ausführen, sollten Sie die Ergebnisse nach Zeit sortiert sehen können:

 

Profilierung von Python-Code mit timeit und cProfile

Ich hoffe, dieser Leitfaden hilft Ihnen beim Einstieg in die Profilerstellung in Python. Denken Sie immer daran, dass Optimierungen niemals auf Kosten der Lesbarkeit gehen sollten. Wenn Sie mehr über andere Profiler, einschließlich Python-Pakete von Drittanbietern, erfahren möchten, schauen Sie sich dies an Artikel über Python-Profiler.
 
 
Bala Priya C ist ein Entwickler und technischer Redakteur aus Indien. Sie arbeitet gerne an der Schnittstelle von Mathematik, Programmierung, Datenwissenschaft und Inhaltserstellung. Zu ihren Interessen- und Fachgebieten gehören DevOps, Datenwissenschaft und Verarbeitung natürlicher Sprache. Sie liebt es zu lesen, zu schreiben, zu programmieren und Kaffee zu trinken! Derzeit arbeitet sie daran, zu lernen und ihr Wissen mit der Entwickler-Community zu teilen, indem sie Tutorials, Anleitungen, Meinungsbeiträge und mehr verfasst.
 

Zeitstempel:

Mehr von KDnuggets