timeit — Messen der Ausführungszeit kleiner Codeausschnitte

Quellcode: Lib/timeit.py


Dieses Modul bietet eine einfache Möglichkeit, kleine Python-Codeausschnitte zu zeitigen. Es verfügt sowohl über eine Befehlszeilenschnittstelle als auch über eine aufrufbare Schnittstelle. Es vermeidet eine Reihe häufiger Fallen bei der Messung von Ausführungszeiten. Siehe auch Tim Peters' Einführung in das Kapitel "Algorithmen" in der zweiten Auflage von Python Cookbook, herausgegeben von O'Reilly.

Grundlegende Beispiele

Das folgende Beispiel zeigt, wie die Befehlszeilenschnittstelle verwendet werden kann, um drei verschiedene Ausdrücke zu vergleichen

$ python -m timeit "'-'.join(str(n) for n in range(100))"
10000 loops, best of 5: 30.2 usec per loop
$ python -m timeit "'-'.join([str(n) for n in range(100)])"
10000 loops, best of 5: 27.5 usec per loop
$ python -m timeit "'-'.join(map(str, range(100)))"
10000 loops, best of 5: 23.2 usec per loop

Dies kann über die Python-Schnittstelle mit folgendem Code erreicht werden

>>> import timeit
>>> timeit.timeit('"-".join(str(n) for n in range(100))', number=10000)
0.3018611848820001
>>> timeit.timeit('"-".join([str(n) for n in range(100)])', number=10000)
0.2727368790656328
>>> timeit.timeit('"-".join(map(str, range(100)))', number=10000)
0.23702679807320237

Eine aufrufbare Funktion kann auch von der Python-Schnittstelle übergeben werden

>>> timeit.timeit(lambda: "-".join(map(str, range(100))), number=10000)
0.19665591977536678

Beachten Sie jedoch, dass timeit() die Anzahl der Wiederholungen nur automatisch bestimmt, wenn die Befehlszeilenschnittstelle verwendet wird. Im Abschnitt Beispiele finden Sie weiterführende Beispiele.

Python-Schnittstelle

Das Modul definiert drei Komfortfunktionen und eine öffentliche Klasse

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)

Erstellt eine Timer-Instanz mit der angegebenen Anweisung, dem setup-Code und der timer-Funktion und führt deren Methode timeit() mit number Ausführungen aus. Das optionale Argument globals gibt einen Namensraum an, in dem der Code ausgeführt wird.

Geändert in Version 3.5: Der optionale Parameter globals wurde hinzugefügt.

timeit.repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=5, number=1000000, globals=None)

Erstellt eine Timer-Instanz mit der angegebenen Anweisung, dem setup-Code und der timer-Funktion und führt deren Methode repeat() mit der angegebenen Anzahl von repeat-Wiederholungen und number Ausführungen aus. Das optionale Argument globals gibt einen Namensraum an, in dem der Code ausgeführt wird.

Geändert in Version 3.5: Der optionale Parameter globals wurde hinzugefügt.

Geändert in Version 3.7: Der Standardwert für repeat wurde von 3 auf 5 geändert.

timeit.default_timer()

Der Standard-Timer, der immer `time.perf_counter()` ist, gibt Sekunden als Float zurück. Eine Alternative, `time.perf_counter_ns`, gibt Nanosekunden als Integer zurück.

Geändert in Version 3.3: `time.perf_counter()` ist nun der Standard-Timer.

class timeit.Timer(stmt='pass', setup='pass', timer=<timer function>, globals=None)

Klasse zum Messen der Ausführungsgeschwindigkeit kleiner Codeausschnitte.

Der Konstruktor nimmt eine zu zeitende Anweisung, eine zusätzliche Anweisung für das Setup und eine Timer-Funktion entgegen. Beide Anweisungen sind standardmäßig 'pass'; die Timer-Funktion ist plattformabhängig (siehe Moduldokumentation). stmt und setup können auch mehrere durch ; oder Zeilenumbrüche getrennte Anweisungen enthalten, solange sie keine mehrzeiligen String-Literale enthalten. Die Anweisung wird standardmäßig im Namensraum von timeit ausgeführt; dieses Verhalten kann durch Übergabe eines Namensraums an globals gesteuert werden.

Verwenden Sie die Methode timeit(), um die Ausführungszeit der ersten Anweisung zu messen. Die Methoden repeat() und autorange() sind Komfortmethoden, um timeit() mehrmals aufzurufen.

Die Ausführungszeit von setup ist von der gesamten zeitlichen Ausführung ausgeschlossen.

Die Parameter stmt und setup können auch Objekte entgegennehmen, die ohne Argumente aufrufbar sind. Dies bettet Aufrufe an sie in eine Timer-Funktion ein, die dann von timeit() ausgeführt wird. Beachten Sie, dass der Zeitaufwand in diesem Fall aufgrund der zusätzlichen Funktionsaufrufe etwas höher ist.

Geändert in Version 3.5: Der optionale Parameter globals wurde hinzugefügt.

timeit(number=1000000)

Zeit für number Ausführungen der Hauptanweisung. Dies führt die Setup-Anweisung einmal aus und gibt dann die Zeit zurück, die für die Ausführung der Hauptanweisung mehrmals benötigt wird. Der Standard-Timer gibt die Zeit in Sekunden als Float zurück. Das Argument ist die Anzahl der Schleifendurchläufe, standardmäßig eine Million. Die Hauptanweisung, die Setup-Anweisung und die zu verwendende Timer-Funktion werden dem Konstruktor übergeben.

Hinweis

Standardmäßig schaltet timeit() die Speicherbereinigung während der Zeitmessung vorübergehend aus. Der Vorteil dieses Ansatzes ist, dass unabhängige Zeitmessungen vergleichbarer werden. Der Nachteil ist, dass die Speicherbereinigung eine wichtige Komponente der Leistung der gemessenen Funktion sein kann. Wenn dies der Fall ist, kann die Speicherbereinigung als erste Anweisung im setup-String wieder aktiviert werden. Zum Beispiel

timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit()
autorange(callback=None)

Automatische Bestimmung, wie oft timeit() aufgerufen werden soll.

Dies ist eine Komfortfunktion, die timeit() wiederholt aufruft, damit die Gesamtzeit >= 0,2 Sekunden beträgt, und die endgültige (Anzahl der Schleifen, Zeit für diese Anzahl von Schleifen) zurückgibt. Sie ruft timeit() mit steigenden Zahlen aus der Sequenz 1, 2, 5, 10, 20, 50, ... auf, bis die benötigte Zeit mindestens 0,2 Sekunden beträgt.

Wenn callback angegeben ist und nicht None ist, wird es nach jedem Durchlauf mit zwei Argumenten aufgerufen: callback(number, time_taken).

Hinzugefügt in Version 3.6.

repeat(repeat=5, number=1000000)

Ruft timeit() mehrmals auf.

Dies ist eine Komfortfunktion, die timeit() wiederholt aufruft und eine Liste von Ergebnissen zurückgibt. Das erste Argument gibt an, wie oft timeit() aufgerufen werden soll. Das zweite Argument gibt das number-Argument für timeit() an.

Hinweis

Es ist verlockend, Mittelwert und Standardabweichung aus dem Ergebnisvektor zu berechnen und diese zu melden. Dies ist jedoch nicht sehr nützlich. In einem typischen Fall gibt der niedrigste Wert eine Untergrenze dafür an, wie schnell Ihre Maschine den gegebenen Codeausschnitt ausführen kann; höhere Werte im Ergebnisvektor werden normalerweise nicht durch Schwankungen in der Geschwindigkeit von Python verursacht, sondern durch andere Prozesse, die Ihre Zeitmessgenauigkeit beeinträchtigen. Der min() des Ergebnisses ist wahrscheinlich die einzige Zahl, die Sie interessieren sollte. Danach sollten Sie den gesamten Vektor betrachten und gesunden Menschenverstand anstelle von Statistiken anwenden.

Geändert in Version 3.7: Der Standardwert für repeat wurde von 3 auf 5 geändert.

print_exc(file=None)

Hilfsfunktion zum Drucken eines Tracebacks aus dem zeitgesteuerten Code.

Typische Verwendung

t = Timer(...)       # outside the try/except
try:
    t.timeit(...)    # or t.repeat(...)
except Exception:
    t.print_exc()

Der Vorteil gegenüber dem Standard-Traceback ist, dass Quellcodezeilen in der kompilierten Vorlage angezeigt werden. Das optionale Argument file gibt an, wohin der Traceback gesendet wird; es ist standardmäßig `sys.stderr`.

Befehlszeilenschnittstelle

Wenn es als Programm von der Befehlszeile aufgerufen wird, wird die folgende Form verwendet

python -m timeit [-n N] [-r N] [-u U] [-s S] [-p] [-v] [-h] [statement ...]

Wobei die folgenden Optionen verstanden werden

-n N, --number=N

wie oft 'statement' ausgeführt werden soll

-r N, --repeat=N

wie oft der Timer wiederholt werden soll (Standard ist 5)

-s S, --setup=S

Anweisung, die einmalig initial ausgeführt wird (Standard ist pass)

-p, --process

Prozesszeit messen, nicht Wanduhrzeit, unter Verwendung von `time.process_time()` anstelle von `time.perf_counter()`, was der Standard ist

Hinzugefügt in Version 3.3.

-u, --unit=U

eine Zeiteinheit für die Timer-Ausgabe angeben; kann nsec, usec, msec oder sec sein

Hinzugefügt in Version 3.5.

-v, --verbose

rohe Timing-Ergebnisse ausgeben; wiederholen für mehrstellige Genauigkeit

-h, --help

eine kurze Nutzungsmeldung ausgeben und beenden

Eine mehrzeilige Anweisung kann angegeben werden, indem jede Zeile als separate Anweisungsargument angegeben wird; eingerückte Zeilen sind möglich, indem ein Argument in Anführungszeichen eingeschlossen und führende Leerzeichen verwendet werden. Mehrere -s-Optionen werden ähnlich behandelt.

Wenn -n nicht angegeben ist, wird eine geeignete Anzahl von Schleifen berechnet, indem steigende Zahlen aus der Sequenz 1, 2, 5, 10, 20, 50, ... ausprobiert werden, bis die Gesamtzeit mindestens 0,2 Sekunden beträgt.

Messungen von default_timer() können durch andere Programme beeinflusst werden, die auf demselben Rechner laufen. Daher ist es am besten, die Zeitmessung mehrmals zu wiederholen und die beste Zeit zu verwenden, wenn genaue Messungen erforderlich sind. Die Option -r ist dafür gut geeignet; die Standardeinstellung von 5 Wiederholungen ist wahrscheinlich in den meisten Fällen ausreichend. Sie können `time.process_time()` verwenden, um die CPU-Zeit zu messen.

Hinweis

Es gibt einen gewissen Grundaufwand, der mit der Ausführung einer `pass`-Anweisung verbunden ist. Der Code hier versucht nicht, ihn zu verstecken, aber Sie sollten sich dessen bewusst sein. Der Grundaufwand kann durch Aufrufen des Programms ohne Argumente gemessen werden und kann sich zwischen Python-Versionen unterscheiden.

Beispiele

Es ist möglich, eine Setup-Anweisung bereitzustellen, die nur einmal am Anfang ausgeführt wird

$ python -m timeit -s "text = 'sample string'; char = 'g'" "char in text"
5000000 loops, best of 5: 0.0877 usec per loop
$ python -m timeit -s "text = 'sample string'; char = 'g'" "text.find(char)"
1000000 loops, best of 5: 0.342 usec per loop

In der Ausgabe gibt es drei Felder. Die Schleifenzahl, die angibt, wie oft der Anweisungskörper pro Timing-Schleifenwiederholung ausgeführt wurde. Die Wiederholungszahl ('best of 5'), die angibt, wie oft die Timing-Schleife wiederholt wurde, und schließlich die Zeit, die der Anweisungskörper im Durchschnitt während der besten Wiederholung der Timing-Schleife benötigte. Das heißt, die Zeit, die die schnellste Wiederholung benötigte, geteilt durch die Schleifenzahl.

>>> import timeit
>>> timeit.timeit('char in text', setup='text = "sample string"; char = "g"')
0.41440500499993504
>>> timeit.timeit('text.find(char)', setup='text = "sample string"; char = "g"')
1.7246671520006203

Dasselbe kann unter Verwendung der Klasse Timer und ihrer Methoden erfolgen

>>> import timeit
>>> t = timeit.Timer('char in text', setup='text = "sample string"; char = "g"')
>>> t.timeit()
0.3955516149999312
>>> t.repeat()
[0.40183617287970225, 0.37027556854118704, 0.38344867356679524, 0.3712595970846668, 0.37866875250654886]

Die folgenden Beispiele zeigen, wie Ausdrücke zeitlich erfasst werden, die mehrere Zeilen enthalten. Hier vergleichen wir die Kosten der Verwendung von `hasattr()` im Vergleich zu `try`/`except`, um fehlende und vorhandene Objektattribute zu testen

$ python -m timeit "try:" "  str.__bool__" "except AttributeError:" "  pass"
20000 loops, best of 5: 15.7 usec per loop
$ python -m timeit "if hasattr(str, '__bool__'): pass"
50000 loops, best of 5: 4.26 usec per loop

$ python -m timeit "try:" "  int.__bool__" "except AttributeError:" "  pass"
200000 loops, best of 5: 1.43 usec per loop
$ python -m timeit "if hasattr(int, '__bool__'): pass"
100000 loops, best of 5: 2.23 usec per loop
>>> import timeit
>>> # attribute is missing
>>> s = """\
... try:
...     str.__bool__
... except AttributeError:
...     pass
... """
>>> timeit.timeit(stmt=s, number=100000)
0.9138244460009446
>>> s = "if hasattr(str, '__bool__'): pass"
>>> timeit.timeit(stmt=s, number=100000)
0.5829014980008651
>>>
>>> # attribute is present
>>> s = """\
... try:
...     int.__bool__
... except AttributeError:
...     pass
... """
>>> timeit.timeit(stmt=s, number=100000)
0.04215312199994514
>>> s = "if hasattr(int, '__bool__'): pass"
>>> timeit.timeit(stmt=s, number=100000)
0.08588060699912603

Um dem `timeit`-Modul Zugriff auf von Ihnen definierte Funktionen zu geben, können Sie einen setup-Parameter übergeben, der eine `import`-Anweisung enthält

def test():
    """Stupid test function"""
    L = [i for i in range(100)]

if __name__ == '__main__':
    import timeit
    print(timeit.timeit("test()", setup="from __main__ import test"))

Eine andere Möglichkeit ist die Übergabe von `globals()` an den globals-Parameter, wodurch der Code in Ihrem aktuellen globalen Namensraum ausgeführt wird. Dies kann bequemer sein als die individuelle Angabe von Importen

def f(x):
    return x**2
def g(x):
    return x**4
def h(x):
    return x**8

import timeit
print(timeit.timeit('[func(42) for func in (f,g,h)]', globals=globals()))