Aufwärmen · Aufgabe 1 von 7
// A4.1 · ca. 30 Min. · Vertiefung
Messen mit timeit
Nach dieser Lektion messen Sie Code mit timeit.timeit und timeit.repeat, geben das Minimum pro Aufruf statt eines verrauschten Werts an und vergleichen zwei Versionen fair: gleiche Eingabe, gleiches Ergebnis, ein Verhältnis.
Lektion 1 von 5 in A4 Leistung und Zahlen
Danach können Sie
- Eine Anweisung oder ein Callable mit timeit.timeit und timeit.repeat messen, mit setup, number und globals
- Erklären, warum eine einzelne Messung täuscht, und das Minimum der Wiederholungen pro Aufruf angeben
- Zwei Versionen fair vergleichen: Ergebnisse prüfen, gleich messen, das Verhältnis beurteilen
Vorhersagen · Aufgabe 2 von 7
Sagen Sie es vorher, bevor Sie weiterlesen: work zählt, wie oft es aufgerufen wird. Was gibt das aus?
import timeit calls = 0 def work(): global calls calls += 1 timeit.timeit(work, number=1000) first = calls times = timeit.repeat(work, number=1000, repeat=5) print(first, calls, len(times))Üben · Aufgabe 3 von 7
Setzen Sie das Schlüsselwort ein, damit die Liste einmal gebaut wird, außerhalb der gemessenen Anweisung.
import timeit setup_runs = [] timeit.timeit("data.sort()", ____="data = [3, 1, 2]; setup_runs.append(1)", number=100, globals=globals())="data = [3, 1, 2]; setup_runs.append(1)"Üben · Aufgabe 4 von 7
timeit.timeit(stmt) mit dem Standardwert für number lieferte 0.84. Wie lange dauert ein Durchlauf von stmt?
Üben · Aufgabe 5 von 7
Ordnen Sie jedem Teil eines timeit-Aufrufs zu, was er bewirkt.
Denksport · Aufgabe 6 von 7
Knobelaufgabe. Das Lambda notiert, ob der Garbage Collector an ist, während es gemessen wird. Was gibt das aus?
import gc import timeit flags = [] timeit.timeit(lambda: flags.append(gc.isenabled()), number=3) print(flags, gc.isenabled())Anwenden · Aufgabe 7 von 7
Kleine Aufgabe. Schreiben Sie zwei Funktionen, die den Text "0,1,2,…,999" bauen: eine mit ",".join, eine mit += in einer Schleife. Geben Sie zuerst aus, ob beide denselben Text liefern. Messen Sie dann jede mit timeit.repeat (number=200, repeat=5) und geben Sie das Minimum pro Aufruf in Mikrosekunden und das Verhältnis aus. Starten Sie es dreimal: Welche Werte ändern sich, welche Schlussfolgerung bleibt?
Prüfen Sie Ihr Ergebnis anhand dieser Liste
Selbst programmieren
Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.
Ausgearbeitetes Beispiel
Zwei Versionen fair gemessen
Zwei Funktionen addieren die Zahlen unter n: eine mit einer Schleife, eine mit einer Formel. Das Programm prüft zuerst, dass beide übereinstimmen, misst dann jede mit timeit.repeat und behält die schnellste Messung pro Aufruf. Die Zeiten selbst ändern sich bei jedem Lauf und auf jedem Rechner, darum gibt das Programm nur aus, was gleich bleibt: ob die Ergebnisse übereinstimmen und ob das Verhältnis eine Schwelle überschreitet. Geben Sie slow und fast zusätzlich aus, um die schwankenden Rohwerte zu sehen.
main.py
import timeit
from collections.abc import Callable
def total_loop(n: int) -> int:
total = 0
for i in range(n):
total += i
return total
def total_formula(n: int) -> int:
return n * (n - 1) // 2
def best_per_call(func: Callable[[], object], number: int, repeat: int = 5) -> float:
"""The fastest of `repeat` timings, divided by the calls in each timing."""
times = timeit.repeat(func, number=number, repeat=repeat)
return min(times) / number
n = 100_000
# 1. Same input, same answer: otherwise the timing compares different work.
print("same result:", total_loop(n) == total_formula(n))
# 2. Time both the same way. The seconds differ on every run.
slow = best_per_call(lambda: total_loop(n), number=20)
fast = best_per_call(lambda: total_formula(n), number=20)
# 3. Print what does not vary: the ratio against a threshold.
print("formula at least 100x faster:", slow / fast >= 100)
print("faster version:", "formula" if fast < slow else "loop")
Ausführen mit
python main.pyAusgabe
same result: True
formula at least 100x faster: True
faster version: formula- Die Prüfung auf Richtigkeit kommt zuerst: Zwei Versionen, die sich widersprechen, erledigen verschiedene Arbeit.
- best_per_call teilt durch number, die Werte gelten also pro Aufruf, egal welche Schleifenzahl Sie wählen.
- Das Verhältnis liegt in den Tausendern, die Schwelle 100 hält also auf jedem Rechner; die Sekunden nicht.
- Jedes Lambda reicht n durch, ohne Vorbereitung mitzumessen: timeit ruft das Callable auf.
Ändern und ausführen
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Übungen
Übung 1 von 2
Die beste Zeit pro Aufruf
Vervollständigen Sie best_per_call(func, number=1000, repeat=5). Die Funktion ruft timeit.repeat mit der Funktion, number und repeat auf (schreiben Sie import timeit und timeit.repeat, damit die Tests es ersetzen können) und gibt das Minimum der Messungen geteilt durch number zurück. Die Tests zählen Aufrufe und geben erfundene Messwerte vor; sie hängen nie davon ab, wie schnell dieser Rechner ist.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Hinweise
Hinweis 1
timeit.repeat(func, number=number, repeat=repeat) liefert eine Liste mit einer Summe pro Messung.
Hinweis 2
Jede Summe umfasst number Aufrufe, die Zeit pro Aufruf ist also min(times) / number.
Hinweis 3
Der ganze Rumpf passt in zwei Zeilen: times = timeit.repeat(...), dann return min(times) / number.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
import timeit
from collections.abc import Callable
def best_per_call(func: Callable[[], object], number: int = 1000, repeat: int = 5) -> float:
times = timeit.repeat(func, number=number, repeat=repeat)
return min(times) / number
Auf dem eigenen Computer ausführen
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.py
import timeit
from collections.abc import Callable
def best_per_call(func: Callable[[], object], number: int = 1000, repeat: int = 5) -> float:
# One timing is not enough: take `repeat` timings of `number` calls each,
# and return the fastest one divided by `number`.
return timeit.timeit(func, number=number)
test_main.py
import math
import timeit
from unittest import mock
from main import best_per_call
def test_calls():
"""number=10 und repeat=3 rufen die Funktion 30-mal auf"""
calls = []
best_per_call(lambda: calls.append(1), number=10, repeat=3)
assert len(calls) == 30, f"die Funktion wurde {len(calls)}-mal aufgerufen, erwartet 30 (10 Aufrufe in jeder von 3 Messungen)"
def test_minimum_per_call():
"""Messungen von 0,5, 0,2 und 0,4 s für 100 Aufrufe ergeben 0,002 s pro Aufruf"""
with mock.patch("timeit.repeat", return_value=[0.5, 0.2, 0.4]) as fake:
got = best_per_call(lambda: None, number=100, repeat=3)
assert fake.called, "best_per_call hat timeit.repeat nicht aufgerufen"
assert math.isclose(got, 0.002), f"Ergebnis {got!r}, erwartet 0.002: das Minimum 0.2 geteilt durch number=100"
def test_defaults():
"""Ohne Argumente: 5 Messungen mit je 1000 Aufrufen"""
calls = []
best_per_call(lambda: calls.append(1))
assert len(calls) == 5000, f"die Funktion wurde {len(calls)}-mal aufgerufen, erwartet 5000"
def test_returns_float():
"""Ein echter Lauf liefert einen positiven float"""
got = best_per_call(lambda: sum(range(10)), number=50)
assert isinstance(got, float) and got > 0, f"Ergebnis {got!r}, erwartet ein positiver float"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenÜbung 2 von 2
Ein fairer Vergleich
Vervollständigen Sie compare(slow, fast, arg, number=200). Prüfen Sie zuerst, ob slow(arg) und fast(arg) gleich sind, und lösen Sie sonst ValueError aus, bevor Sie irgendetwas messen. Messen Sie dann lambda: slow(arg) und lambda: fast(arg) mit timeit.repeat (number=number, repeat=5) und geben Sie das Minimum der langsamen Messungen geteilt durch das Minimum der schnellen zurück. 3.0 heißt: Die schnelle Version ist dreimal schneller.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Hinweise
Hinweis 1
Beginnen Sie mit if slow(arg) != fast(arg): raise ValueError(...). Erst danach wird gemessen.
Hinweis 2
timeit.repeat(lambda: slow(arg), number=number, repeat=5) liefert fünf Summen; behalten Sie deren min().
Hinweis 3
Das Verhältnis ist langsam durch schnell: Eine größere Zahl bedeutet einen größeren Gewinn der schnellen Version.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
import timeit
from collections.abc import Callable
def compare(slow: Callable[[int], object], fast: Callable[[int], object], arg: int, number: int = 200) -> float:
if slow(arg) != fast(arg):
raise ValueError("the two versions give different results")
slow_best = min(timeit.repeat(lambda: slow(arg), number=number, repeat=5))
fast_best = min(timeit.repeat(lambda: fast(arg), number=number, repeat=5))
return slow_best / fast_best
Auf dem eigenen Computer ausführen
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.py
import timeit
from collections.abc import Callable
def compare(slow: Callable[[int], object], fast: Callable[[int], object], arg: int, number: int = 200) -> float:
# 1. Raise ValueError if slow(arg) and fast(arg) differ.
# 2. Time both with timeit.repeat and return min(slow) / min(fast).
slow_time = timeit.timeit(lambda: slow(arg), number=number)
fast_time = timeit.timeit(lambda: fast(arg), number=number)
return fast_time / slow_time
test_main.py
import math
from unittest import mock
from main import compare
who = []
def slow(n):
who.append("slow")
return n * 2
def fast(n):
who.append("fast")
return n + n
def wrong(n):
who.append("wrong")
return n
def fake_repeat(stmt, number=1000000, repeat=5, **kwargs):
"""Ruft stmt einmal auf und liefert erfundene Messwerte für die Version, die lief."""
stmt()
return [0.9, 0.6, 0.8] if who[-1] == "slow" else [0.3, 0.2, 0.25]
def test_ratio():
"""Bestzeiten von 0,6 s und 0,2 s ergeben das Verhältnis 3,0"""
with mock.patch("timeit.repeat", side_effect=fake_repeat):
got = compare(slow, fast, 21)
assert math.isclose(got, 3.0), f"Ergebnis {got!r}, erwartet 3.0: Minimum der langsamen / Minimum der schnellen Messungen"
def test_different_results():
"""Versionen mit verschiedenen Ergebnissen lösen ValueError aus"""
try:
compare(slow, wrong, 21)
except ValueError:
return
raise AssertionError("compare(slow, wrong, 21) hat keinen ValueError ausgelöst, obwohl 42 != 21")
def test_checks_before_timing():
"""Verschiedene Ergebnisse fallen auf, bevor irgendetwas gemessen wird"""
with mock.patch("timeit.repeat", side_effect=fake_repeat) as fake:
try:
compare(slow, wrong, 21)
except ValueError:
pass
assert fake.call_count == 0, f"timeit.repeat wurde vor der Prüfung {fake.call_count}-mal aufgerufen: vergleichen Sie zuerst die Ergebnisse"
def test_real_run():
"""Ein echter Lauf liefert einen positiven float"""
got = compare(slow, fast, 5, number=20)
assert isinstance(got, float) and got > 0, f"Ergebnis {got!r}, erwartet ein positiver float"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenHäufige Fehler
Eine String-Anweisung sieht Ihre Funktion nicht
import timeit
def square(n):
return n * n
print(timeit.timeit("square(3)", number=10))
Was Python ausgibt
NameError: name 'square' is not definedWarum, und die Lösung
Eine String-Anweisung läuft im eigenen Namensraum von timeit, in dem Ihre Funktionen fehlen. Übergeben Sie globals=globals(), importieren Sie die Funktion in setup ("from __main__ import square") oder übergeben Sie ein Callable: timeit.timeit(lambda: square(3), number=10).
Das Ergebnis statt der Funktion übergeben
import timeit
def work():
sum(range(100))
print(timeit.timeit(work(), number=10))
Was Python ausgibt
ValueError: stmt is neither a string nor callableWarum, und die Lösung
work() ruft die Funktion einmal auf, bevor timeit beginnt, und übergibt ihr Ergebnis, None. Übergeben Sie die Funktion selbst, timeit.timeit(work, number=10), oder ein Lambda, wenn sie Argumente braucht.
Die Liste von repeat wie eine Zahl behandeln
import timeit
times = timeit.repeat("sum(range(100))", number=1000, repeat=5)
print(times / 1000)
Was Python ausgibt
TypeError: unsupported operand type(s) for /: 'list' and 'int'Warum, und die Lösung
repeat liefert eine Liste mit einer Summe pro Messung. Wählen Sie zuerst einen Wert, das Minimum, und teilen Sie dann durch number: min(times) / 1000 ist die beste Zeit pro Aufruf.
Python im Browser: Pyodide 314.0.7, MPL-2.0. Lizenz und Quellcode
Abschlussquiz
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.