Profiling und Timing
1 Dauer der Code-Ausführung messen
Die Funktion time.time() gibt die aktuelle Zeit als Unix-Timestamp in Sekunden zurück. Sie ist allerdings nicht ideal für präzise Zeitmessungen, weil sie durch folgende Faktoren beeinflusst wird:
- Geringe Präzision auf manchen Systemen
time.time()basiert auf der Systemuhr, die je nach Betriebssystem nicht hochauflösend ist.- Bei Windows hat
time.time()oft nur eine Genauigkeit von etwa 15,6 Millisekunden.
- Sprünge durch Systemzeit-Änderungen
- Wenn die Systemuhr manuell geändert oder durch NTP (Network Time Protocol) synchronisiert wird, kann
time.time()springen oder rückwärtslaufen.
- Wenn die Systemuhr manuell geändert oder durch NTP (Network Time Protocol) synchronisiert wird, kann
- Nicht speziell für Zeitmessungen gedacht
time.time()ist hauptsächlich für das Abrufen der aktuellen Uhrzeit gedacht, nicht für Hochpräzisionsmessungen.
warum ist `time.perf_counter()` besser?
Die Funktion time.perf_counter() ist speziell für hochpräzise Zeitmessungen gedacht:
- Hohe Auflösung
- Nutzt die genaueste verfügbare Uhr des Systems.
- Auf modernen Systemen meist Nanosekunden- oder Mikrosekunden-genau.
- Monoton steigend
- Kann nicht durch Systemzeitänderungen beeinflusst werden.
- Die Werte steigen immer an, niemals rückwärts.
- Optimiert für Laufzeitmessungen
- Ideal für Benchmarking und Laufzeitanalysen.
import time
# Ungenau:
start = time.time() # Nur geeignet um aktuelle Zeit zu erhalten
time.sleep(1)
end = time.time()
print(end - start)
# Genau:
start = time.perf_counter()
time.sleep(1)
end = time.perf_counter()
print(end - start)
2 Unit Tests in Python
2.1 Grundlagen
- Python verwendet das
unittest-Modul (im Standardpaket enthalten). - Alternativen:
pytest,nose2(externe Pakete).
2.2 Einfaches Beispiel mit unittest
import unittest
# Beispiel-Funktion: Addiert zwei Zahlen
def add_numbers(a, b):
return a + b
# Testklasse
class TestAddNumbers(unittest.TestCase):
def test_positive_numbers(self):
self.assertEqual(add_numbers(2, 3), 5)
def test_negative_numbers(self):
self.assertEqual(add_numbers(-1, -1), -2)
def test_zero(self):
self.assertEqual(add_numbers(0, 0), 0)
# Hauptprogramm für Unit-Tests
if __name__ == '__main__':
unittest.main()
2.3 Wichtige Methoden von unittest.TestCase
assertEqual(a, b)assertNotEqual(a, b)assertTrue(x)assertFalse(x)assertIsNone(x)assertRaises(Exception, func, args...)
3 Profiling in Python
3.1 Ziel
Performance-Engpässe (Bottlenecks) im Code finden.
3.2 Tools im Überblick
cProfile(Standardmodul, robust)timeit(für kleinere Snippets)line_profiler(extern, detailliert)py-spy,snakeviz,yappi(externe Visualisierung)
3.3 Beispiel: cProfile
import cProfile
# Rechenintensive Beispiel-Funktion
def compute_heavy_task():
total = 0
for i in range(100000):
total += i ** 2
return total
# Führe Profiling durch
cProfile.run('compute_heavy_task()')
3.4 Beispiel: timeit
import timeit
# Zeitmessung für einfachen Ausdruck
code = "sum([i*i for i in range(1000)])"
duration = timeit.timeit(stmt=code, number=1000)
print(f"Durchschnittszeit: {duration:.4f} Sekunden")
3.5 line_profiler (externe Installation nötig)
pip install line_profiler
# sample.py
# Mit @profile markierte Funktion
@profile
def compute():
total = 0
for i in range(10000):
total += i ** 2
return total
# Ausführen mit line_profiler
kernprof -l sample.py
python -m line_profiler sample.py.lprof
4 Tipps
4.1 Zuerst Tests schreiben, dann den Code (Test-Driven Development, TDD)
Was es bedeutet:
- TDD ist ein Entwicklungsansatz, bei dem man zuerst Tests schreibt, bevor man den eigentlichen Code implementiert.
- Das Ziel ist, nur so viel Code zu schreiben, wie nötig ist, um den Test bestehen zu lassen.
Ablauf:
- Einen Test schreiben, der fehlschlägt (weil es die Funktion noch nicht gibt).
- Den minimalen Code implementieren, der den Test bestehen lässt.
- Refaktorisiere, falls nötig, und sicherstellen, dass der Test weiterhin grün ist.
- Den Zyklus wiederholen für jede neue Funktionalität.
Vorteile:
- Klar definierte Anforderungen.
- Höhere Code-Qualität.
- Tests sind immer aktuell.
- Bessere Wartbarkeit.
4.2 pytest für eleganteres Testing (nutzt assert statt Methoden)
Was pytest auszeichnet:
-
Sehr beliebt wegen seiner einfachen Syntax.
-
Statt wie in
unittestMethoden wieassertEqual(a, b)zu verwenden, nutzt man einfach:assert a == b
Beispiel mit pytest:
# test_math.py
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0
Warum es eleganter ist:
- Weniger Boilerplate-Code.
- Fehlermeldungen bei Fehlschlägen sind ausdrucksstärker (zeigt automatisch Werte an).
- Automatische Erkennung von Tests.
- Integration mit Plugins wie
pytest-cov,hypothesisuvm.
4.3 Profiling immer bei realistischen Datenmengen
Warum das wichtig ist:
- Kleine Testdaten täuschen oft über die Performance hinweg.
- Code, der bei 100 Einträgen schnell ist, kann bei 1 Million Einträgen ineffizient skalieren.
- Realitätsnahe Tests helfen, echte Engpässe zu erkennen.
Praxis-Tipp:
Wenn man z. B. ein Programm für Logfile-Analyse entwickelt:
- Profiling mit 10 Zeilen bringt kaum Erkenntnisse.
- Simuliere echte Logdateien (z. B. 100 MB oder mehr).
Tools wie cProfile, timeit, line_profiler sollten mit realistischen Inputs verwendet werden, um aussagekräftige Ergebnisse zu liefern.
4.4 Tests und Profiling kombinieren für Performance-Regressionstests
Was das bedeutet:
- Nicht nur funktionale Richtigkeit testen, sondern auch sicherstellen, dass neue Änderungen den Code nicht verlangsamen.
Wie man es macht:
- Einen Test schreiben, der nicht nur prüft, ob das Ergebnis korrekt ist, sondern auch, ob die Ausführung innerhalb einer Zeitgrenze bleibt.
Beispiel mit pytest:
import time
def test_performance():
start = time.time()
result = sum(i*i for i in range(100000))
duration = time.time() - start
assert duration < 0.5 # z. B. Maximal 0.5 Sekunden erlaubt
Vorteile:
- Du erkennst Leistungsregressionen sofort, z. B. wenn jemand unabsichtlich einen ineffizienten Algorithmus einbaut.
- Ideal für kritische Funktionen in Web-Backends, Datenverarbeitung, Simulationen etc.
5 Arbeiten in VS Code
5.1 Tests ausführen mit unittest oder pytest in VS Code
- Sicherstellen, dass der Python-Interpreter ausgewählt wurde (unten links oder
Ctrl+Shift+P→ “Python: Interpreter auswählen”). - Kommando-Palette öffnen:
Ctrl+Shift+P - Auswählen:
Python: Discover Tests unittest,pytestodernoseals Framework auswählen.- Danach:
Python: Run All TestsoderRun Testdirekt über dem Test mit dem kleinen „Play“-Icon.
[!INFO] VS Code erkennt
pytestautomatisch, wenn die Datei mittest_*.pybeginnt undassert-Statements enthält.
5.2 Profiling mit Erweiterung: “Python Profile”
- Optional: Erweiterung “Python Profiler” installieren.
- Datei öffnen
- Einen Breakpoint setzen oder über
Run → Start Debuggingausführen. - Man kann
cProfile-Ausgaben direkt im Terminal oder über Plugins visualisieren lassen.
5.3 Kurzbefehle
| Aktion | Shortcut |
|---|---|
| Testdatei ausführen | Ctrl+F5 oder ▶ oben |
| Terminal öffnen | `Ctrl+`` |
| Befehlspalette öffnen | Ctrl+Shift+P |
| Tests entdecken | Python: Discover Tests |
| Nur einen Test ausführen | Rechtsklick > Run Test |