Wie verwende ich timeit
um die Leistung meiner eigenen Funktionen wie " insertion_sort
" und " tim_sort
"?
Antworten
Zu viele Anzeigen?Wenn Sie Folgendes verwenden möchten timeit
in einer interaktiven Python-Sitzung zu verwenden, gibt es zwei praktische Optionen:
-
Verwenden Sie die IPython Schale. Sie verfügt über die praktische
%timeit
besondere Funktion:In [1]: def f(x): ...: return x*x ...: In [2]: %timeit for x in range(100): f(x) 100000 loops, best of 3: 20.3 us per loop
-
In einem Standard-Python-Interpreter können Sie auf Funktionen und andere Namen zugreifen, die Sie zuvor während der interaktiven Sitzung definiert haben, indem Sie sie von
__main__
in der Setup-Anweisung:>>> def f(x): ... return x * x ... >>> import timeit >>> timeit.repeat("for x in range(100): f(x)", "from __main__ import f", number=100000) [2.0640320777893066, 2.0876040458679199, 2.0520210266113281]
Der Weg timeit besteht darin, den Einrichtungscode einmal auszuführen und dann wiederholt eine Reihe von Anweisungen aufzurufen. Wenn Sie also die Sortierung testen wollen, müssen Sie darauf achten, dass ein Durchlauf einer In-Place-Sortierung nicht den nächsten Durchlauf mit bereits sortierten Daten beeinflusst (das würde natürlich die Timsort wirklich glänzen, weil es am besten funktioniert, wenn die Daten bereits teilweise geordnet sind).
Das folgende Beispiel zeigt, wie man einen Test für die Sortierung einrichtet:
>>> import timeit
>>> setup = '''
import random
random.seed('slartibartfast')
s = [random.random() for i in range(1000)]
timsort = list.sort
'''
>>> print min(timeit.Timer('a=s[:]; timsort(a)', setup=setup).repeat(7, 1000))
0.334147930145
Beachten Sie, dass die Anweisungsreihe bei jedem Durchlauf eine neue Kopie der unsortierten Daten erstellt.
Beachten Sie auch die Zeitmessungstechnik, bei der die Messsuite siebenmal ausgeführt wird und nur die beste Zeit gespeichert wird - dies kann wirklich dazu beitragen, Messverzerrungen aufgrund anderer auf Ihrem System laufender Prozesse zu reduzieren.
Das sind meine Tipps für die richtige Verwendung von timeit. Ich hoffe, das hilft :-)
Ich verrate Ihnen ein Geheimnis: Der beste Weg, die timeit
in der Befehlszeile steht.
In der Befehlszeile, timeit
macht eine ordentliche statistische Analyse: Sie sagt Ihnen, wie lange der kürzeste Lauf gedauert hat. Das ist gut, denn todo Fehler bei der Zeitmessung positiv ist. Die kürzeste Zeit hat also den geringsten Fehler. Es gibt keine Möglichkeit, einen negativen Fehler zu erhalten, da ein Computer niemals schneller rechnen kann als er rechnen kann!
Also, die Befehlszeilenschnittstelle:
%~> python -m timeit "1 + 2"
10000000 loops, best of 3: 0.0468 usec per loop
Das ist doch ganz einfach, oder?
Sie können Dinge einrichten:
%~> python -m timeit -s "x = range(10000)" "sum(x)"
1000 loops, best of 3: 543 usec per loop
was auch nützlich ist!
Wenn Sie mehrere Zeilen benötigen, können Sie entweder die automatische Fortsetzung der Shell verwenden oder separate Argumente angeben:
%~> python -m timeit -s "x = range(10000)" -s "y = range(100)" "sum(x)" "min(y)"
1000 loops, best of 3: 554 usec per loop
Das ergibt eine Aufstellung von
x = range(1000)
y = range(100)
und Zeiten
sum(x)
min(y)
Wenn Sie längere Skripte haben möchten, könnten Sie versucht sein, zu timeit
innerhalb eines Python-Skripts. Ich schlage vor, dies zu vermeiden, da die Analyse und das Timing in der Kommandozeile einfach besser sind. Stattdessen neige ich dazu, Shell-Skripte zu erstellen:
SETUP="
... # lots of stuff
"
echo Minmod arr1
python -m timeit -s "$SETUP" "Minmod(arr1)"
echo pure_minmod arr1
python -m timeit -s "$SETUP" "pure_minmod(arr1)"
echo better_minmod arr1
python -m timeit -s "$SETUP" "better_minmod(arr1)"
... etc
Dies kann aufgrund der mehrfachen Initialisierungen etwas länger dauern, aber normalerweise ist das kein großes Problem.
Was aber, wenn Sie wollen zu verwenden timeit
innerhalb Ihres Moduls?
Nun, der einfachste Weg ist zu tun:
def function(...):
...
timeit.Timer(function).timeit(number=NUMBER)
und das ergibt kumulativ ( pas Minimum!) Zeit für diese Anzahl von Durchläufen.
Um eine gute Analyse zu erhalten, verwenden Sie .repeat
und nehmen Sie das Minimum:
min(timeit.Timer(function).repeat(repeat=REPEATS, number=NUMBER))
Normalerweise sollten Sie dies kombinieren mit functools.partial
anstelle von lambda: ...
um die Gemeinkosten zu senken. Man könnte also etwas wie folgt haben:
from functools import partial
def to_time(items):
...
test_items = [1, 2, 3] * 100
times = timeit.Timer(partial(to_time, test_items)).repeat(3, 1000)
# Divide by the number of repeats
time_taken = min(times) / 1000
Das können Sie auch tun:
timeit.timeit("...", setup="from __main__ import ...", number=NUMBER)
das würde Ihnen etwas geben, das näher am Schnittstelle von der Kommandozeile aus, aber auf eine viel weniger coole Art und Weise. Die "from __main__ import ..."
können Sie Code aus Ihrem Hauptmodul in der künstlichen Umgebung verwenden, die von timeit
.
Es ist erwähnenswert, dass dies ein bequemer Wrapper für Timer(...).timeit(...)
und ist daher nicht besonders gut im Timing. Ich persönlich bevorzuge bei weitem die Verwendung von Timer(...).repeat(...)
wie ich oben gezeigt habe.
Warnungen
Es gibt ein paar Vorbehalte bei timeit
die überall gelten.
-
Gemeinkosten werden nicht berücksichtigt. Angenommen, Sie wollen die Zeit
x += 1
, um herauszufinden, wie lange die Addition dauert:>>> python -m timeit -s "x = 0" "x += 1" 10000000 loops, best of 3: 0.0476 usec per loop
Nun, es ist pas 0,0476 µs. Sie wissen nur, dass es sich um weniger als das. Jeder Fehler ist positiv.
Versuchen Sie also zu finden rein Overhead:
>>> python -m timeit -s "x = 0" "" 100000000 loops, best of 3: 0.014 usec per loop
Das ist eine gute 30% Overhead nur vom Timing her! Dies kann die relativen Zeitangaben massiv verzögern. Aber Sie interessierten sich nur für die Hinzufügen von Timings; die Look-up-Timings für
x
müssen ebenfalls in die Gemeinkosten einbezogen werden:>>> python -m timeit -s "x = 0" "x" 100000000 loops, best of 3: 0.0166 usec per loop
Der Unterschied ist nicht viel größer, aber er ist da.
-
Mutierende Methoden sind gefährlich.
>>> python -m timeit -s "x = [0]*100000" "while x: x.pop()" 10000000 loops, best of 3: 0.0436 usec per loop
Aber das ist völlig falsch!
x
ist die leere Liste nach der ersten Iteration. Sie müssen sie neu initialisieren:>>> python -m timeit "x = [0]*100000" "while x: x.pop()" 100 loops, best of 3: 9.79 msec per loop
Aber dann haben Sie eine Menge Aufwand. Das müssen Sie gesondert berücksichtigen.
>>> python -m timeit "x = [0]*100000" 1000 loops, best of 3: 261 usec per loop
Beachten Sie, dass der Abzug der Gemeinkosten hier sinnvoll ist nur weil der Overhead macht nur einen Bruchteil der Zeit aus.
Für Ihr Beispiel sei angemerkt, dass beide Einfügungssortierung und Zeitsortierung haben völlig unüblich Timing-Verhalten für bereits sortierte Listen. Dies bedeutet, dass Sie eine
random.shuffle
zwischen den einzelnen Sorten, wenn Sie Ihre Zeitplanung nicht durcheinander bringen wollen.
- See previous answers
- Weitere Antworten anzeigen