Verwendung des Timeit-Moduls

351

Ich verstehe das Konzept dessen, was timeitfunktioniert, bin mir aber nicht sicher, wie ich es in meinem Code implementieren soll.

Wie kann ich vergleichen zwei Funktionen, sagen wir insertion_sortund tim_sortmit timeit?

Neemaximo
quelle

Antworten:

266

Die Funktionsweise von timeit besteht darin, den Setup-Code einmal auszuführen und dann eine Reihe von Anweisungen wiederholt aufzurufen . Wenn Sie also die Sortierung testen möchten, ist einige Sorgfalt erforderlich, damit ein Durchgang bei einer direkten Sortierung den nächsten Durchgang mit bereits sortierten Daten nicht beeinflusst (dies würde den Timsort natürlich wirklich zum Leuchten bringen, da er die beste Leistung erbringt wenn die Daten bereits teilweise bestellt sind).

Hier ist ein Beispiel für das Einrichten eines Sortiertests:

>>> 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 Durchgang eine neue Kopie der unsortierten Daten erstellt.

Beachten Sie auch die Timing-Technik, bei der die Messsuite sieben Mal ausgeführt wird und nur die beste Zeit eingehalten wird. Dies kann wirklich dazu beitragen, Messverzerrungen aufgrund anderer Prozesse auf Ihrem System zu reduzieren.

Das sind meine Tipps für den richtigen Umgang mit timeit. Hoffe das hilft :-)

Raymond Hettinger
quelle
8
Ja, es enthält die Listenkopie (die im Vergleich zur Sortierung selbst sehr schnell ist). Wenn Sie jedoch nicht kopieren, sortiert der erste Durchgang die Liste und der verbleibende Durchgang muss keine Arbeit erledigen. Wenn Sie die Zeit nur für die Sortierung wissen möchten, dann führen Sie die oben genannten mit und ohne timsort(a)und nehmen Sie den Unterschied :-)
Raymond Hettinger
Ich würde empfehlen, 7 Mal für jedes Setup zu wiederholen und dann zu mitteln. eher als umgekehrt. Auf diese Weise besteht bei jeder Spitze aufgrund anderer Prozesse eine gute Chance, dass sie vollständig ignoriert und nicht gemittelt wird.
Max
75
@max Verwenden Sie min () anstelle des Durchschnitts der Timings. Das ist eine Empfehlung von mir, von Tim Peters und von Guido van Rossum. Die schnellste Zeit stellt die beste Zeit dar, die ein Algorithmus ausführen kann, wenn die Caches geladen sind und das System nicht mit anderen Aufgaben beschäftigt ist. Alle Timings sind laut - die schnellste Zeit ist die am wenigsten laut. Es ist leicht zu zeigen, dass die schnellsten Timings am reproduzierbarsten und daher am nützlichsten sind, wenn zwei verschiedene Implementierungen zeitlich festgelegt werden.
Raymond Hettinger
4
Sie berechnen einen Durchschnitt (gut, insgesamt, aber er ist äquivalent) für 1000 Eingaben. dann 7 mal wiederholen und das Minimum nehmen . Sie benötigen die Mittelung über 1000 Eingaben, da Sie die durchschnittliche (nicht im besten Fall) Komplexität des Algorithmus wünschen. Sie brauchen das Minimum genau aus dem Grund, den Sie angegeben haben. Ich dachte, ich kann Ihren Ansatz verbessern, indem ich eine Eingabe auswähle, den Algorithmus siebenmal ausführe und das Minimum nehme. Wiederholen Sie es dann für 1000 verschiedene Eingaben und nehmen Sie den Durchschnitt. Was ich nicht realisiert habe ist, dass Sie dies .repeat(7,1000)bereits tun (indem Sie den gleichen Samen verwenden)! Ihre Lösung ist also IMO perfekt.
Max
5
Ich kann nur hinzufügen, dass die Zuweisung Ihres Budgets für 7000 Ausführungen (z. B. .repeat(7, 1000)vs. .repeat(2, 3500)vs .repeat(35, 200) davon abhängen sollte, wie der Fehler aufgrund der Systemlast mit dem Fehler aufgrund der Eingabevariabilität verglichen wird. Im Extremfall, wenn Ihr System immer stark ausgelastet ist und Sie links von der Verteilung der Ausführungszeit einen langen, dünnen Schwanz sehen (wenn Sie es in einem seltenen Leerlaufzustand abfangen), sind Sie möglicherweise sogar .repeat(7000,1)nützlicher als .repeat(7,1000)wenn Sie kann nicht mehr als 7000 Läufe budgetieren.
Max
277

Wenn Sie timeitin einer interaktiven Python-Sitzung verwenden möchten , gibt es zwei praktische Optionen:

  1. Verwenden Sie die IPython- Shell. Es verfügt über die praktische %timeitSonderfunktion:

    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
  2. 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 aus __main__der Setup-Anweisung importieren :

    >>> 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]
Sven Marnach
quelle
97
+1 für die Darstellung der from __main__ import fTechnik. Ich denke nicht, dass dies so bekannt ist, wie es sein sollte. Dies ist in solchen Fällen hilfreich, in denen ein Funktions- oder Methodenaufruf zeitgesteuert wird. In anderen Fällen (zeitliche Abstimmung einer Reihe von Schritten) ist dies weniger hilfreich, da dadurch der Funktionsaufruf-Overhead eingeführt wird.
Raymond Hettinger
15
Sie können einfach tun%timeit f(x)
qed
Hinweis: Das Setup "import f" ermöglicht den Zugriff auf ein schnelles lokales Lesen - was nicht genau einen globalen Funktionsaufruf (mit kurzer schneller Funktion) im typischen normalen Code widerspiegelt. In Py3.5 + können echte Globals bereitgestellt werden: "In Version 3.5 geändert: Der optionale Globals-Parameter wurde hinzugefügt."; Bevor die Globalen des Timeit-Moduls unvermeidlich waren (was nicht viel Sinn macht). Möglicherweise sollten die Globals des aufrufenden Codes ( sys._getframe(N).f_globals) von Anfang an die Standardeinstellung gewesen sein.
kxr
140

Ich werde Ihnen ein Geheimnis verraten: Der beste Weg, dies zu tun, timeitist die Befehlszeile.

Führt in der Befehlszeile eine timeitordnungsgemäße statistische Analyse durch: Hier erfahren Sie, wie lange der kürzeste Lauf gedauert hat. Dies ist gut, da alle Zeitfehler positiv sind. 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!

Also die Kommandozeilenschnittstelle:

%~> python -m timeit "1 + 2"
10000000 loops, best of 3: 0.0468 usec per loop

Das ist ganz einfach, oder?

Sie können Folgendes einrichten:

%~> python -m timeit -s "x = range(10000)" "sum(x)"
1000 loops, best of 3: 543 usec per loop

das ist auch nützlich!

Wenn Sie mehrere Zeilen möchten, können Sie entweder die automatische Fortsetzung der Shell verwenden oder separate Argumente verwenden:

%~> 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 gibt ein Setup 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, in timeitein Python-Skript zu wechseln . Ich schlage vor, dies zu vermeiden, da die Analyse und das Timing in der Befehlszeile 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 keine große Sache.


Aber was , wenn Sie möchten , verwenden timeitin Ihrem Modul?

Nun, der einfache Weg ist:

def function(...):
    ...

timeit.Timer(function).timeit(number=NUMBER)

und das gibt Ihnen kumulative ( nicht minimale!) Zeit, um diese Anzahl von Malen auszuführen.

Verwenden Sie, um eine gute Analyse zu erhalten .repeat und nehmen Sie das Minimum, :

min(timeit.Timer(function).repeat(repeat=REPEATS, number=NUMBER))

Normalerweise sollten Sie dies mit kombinieren, functools.partialanstatt lambda: ...den Overhead zu senken. So könnte man so etwas haben wie:

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

Sie können auch:

timeit.timeit("...", setup="from __main__ import ...", number=NUMBER)

das würde Ihnen etwas näher an der Schnittstelle geben von der Kommandozeile geben, aber auf eine viel weniger coole Art und Weise. Mit "from __main__ import ..."können Sie Code aus Ihrem Hauptmodul in der von erstellten künstlichen Umgebung verwenden timeit.

Es ist erwähnenswert, dass dies ein Convenience-Wrapper für ist Timer(...).timeit(...) und daher nicht besonders gut im Timing ist. Ich persönlich bevorzuge es bei weitem, Timer(...).repeat(...)wie ich oben gezeigt habe.


Warnungen

Es gibt ein paar Einschränkungen timeit, die überall gelten.

  • Gemeinkosten werden nicht berücksichtigt. Angenommen, Sie möchten Zeit x += 1, um herauszufinden, wie lange das Hinzufügen dauert:

    >>> python -m timeit -s "x = 0" "x += 1"
    10000000 loops, best of 3: 0.0476 usec per loop

    Nun, es ist nicht 0,0476 µs. Sie wissen nur, dass es weniger ist . Alle Fehler sind positiv.

    Versuchen Sie also, reinen Overhead zu finden:

    >>> python -m timeit -s "x = 0" ""      
    100000000 loops, best of 3: 0.014 usec per loop

    Das sind gut 30% Overhead, nur vom Timing! Dies kann die relativen Timings massiv verzerren. Aber Sie haben sich nur wirklich um das Hinzufügen von Timings gekümmert ; Die Nachschlagezeiten für müssen xebenfalls im Overhead enthalten sein:

    >>> 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.

  • Mutationsmethoden 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! xist die leere Liste nach der ersten Iteration. Sie müssen 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 viel Aufwand. Berücksichtigen Sie dies separat.

    >>> python -m timeit "x = [0]*100000"                   
    1000 loops, best of 3: 261 usec per loop

    Beachten Sie, dass das Subtrahieren des Overheads hier nur deshalb sinnvoll ist der Overhead nur einen kleinen Bruchteil der Zeit ausmacht.

    In Ihrem Beispiel ist anzumerken, dass sowohl Insertion Sort als auch Tim Sort ein völlig ungewöhnliches Timing-Verhalten für bereits sortierte Listen aufweisen. Dies bedeutet, dass Sie eine random.shuffleZwischensortierung benötigen, wenn Sie vermeiden möchten, dass Ihre Timings zerstört werden.

Veedrac
quelle
1
was bedeutet usec ist es Mikrosekunden?
Hasan Iqbal
2
@ HasanIqbalAnik Ja.
Veedrac
@StefanPochmann Weil nicht versucht wird, mehrmals zu probieren.
Veedrac
Leser dieser Antwort könnten auch daran interessiert sein, Pythons timeitaus einem Programm zu verwenden, aber genauso wie die Befehlszeile zu funktionieren? .
Graham
@Veedrac Wenn Sie Ihre Anweisung zum Subtrahieren des reinen Timing-Overheads berücksichtigen, timeitwird eine passAnweisung ausgeführt, wenn keine Argumente angegeben werden, was natürlich einige Zeit in Anspruch nimmt. Wenn Argumente angegeben werden, passwerden diese nicht ausgeführt, daher 0.014wäre es falsch , einige Usecs von jedem Timing zu subtrahieren .
Arne
99

Wenn Sie zwei Codeblöcke / Funktionen schnell vergleichen möchten, können Sie Folgendes tun:

import timeit

start_time = timeit.default_timer()
func1()
print(timeit.default_timer() - start_time)

start_time = timeit.default_timer()
func2()
print(timeit.default_timer() - start_time)
zzart
quelle
43

Ich finde, die einfachste Möglichkeit, timeit zu verwenden, ist über die Befehlszeile:

Gegeben test.py :

def InsertionSort(): ...
def TimSort(): ...

Führen Sie die Zeit so aus:

% python -mtimeit -s'import test' 'test.InsertionSort()'
% python -mtimeit -s'import test' 'test.TimSort()'
unutbu
quelle
18

Für mich ist dies der schnellste Weg:

import timeit
def foo():
    print("here is my code to time...")


timeit.timeit(stmt=foo, number=1234567)
Rodrigo Laguna
quelle
12
# Генерация целых чисел

def gen_prime(x):
    multiples = []
    results = []
    for i in range(2, x+1):
        if i not in multiples:
            results.append(i)
            for j in range(i*i, x+1, i):
                multiples.append(j)

    return results


import timeit

# Засекаем время

start_time = timeit.default_timer()
gen_prime(3000)
print(timeit.default_timer() - start_time)

# start_time = timeit.default_timer()
# gen_prime(1001)
# print(timeit.default_timer() - start_time)
David Webb
quelle
7

Das funktioniert super:

  python -m timeit -c "$(cat file_name.py)"
Ohad Rubin
quelle
Was wäre das Windows-Äquivalent?
Shailen
2
Wie übergeben Sie Parameter, wenn das Skript welche benötigt?
Juuso Ohtonen
3

Lassen Sie uns in jedem der folgenden Wörter das gleiche Wörterbuch einrichten und die Ausführungszeit testen.

Das Setup-Argument richtet im Grunde das Wörterbuch ein

Nummer ist, den Code 1000000 mal auszuführen. Nicht das Setup, sondern das stmt

Wenn Sie dies ausführen, können Sie sehen, dass der Index viel schneller ist als get. Sie können es mehrmals ausführen, um zu sehen.

Der Code versucht grundsätzlich, den Wert von c im Wörterbuch abzurufen.

import timeit

print('Getting value of C by index:', timeit.timeit(stmt="mydict['c']", setup="mydict={'a':5, 'b':6, 'c':7}", number=1000000))
print('Getting value of C by get:', timeit.timeit(stmt="mydict.get('c')", setup="mydict={'a':5, 'b':6, 'c':7}", number=1000000))

Hier sind meine Ergebnisse, Ihre werden sich unterscheiden.

nach Index: 0,20900007452246427

von get: 0.54841166886888

Stryker
quelle
Welche Python-Version verwenden Sie?
Eduardo
3

Übergeben Sie einfach Ihren gesamten Code als Argument der Zeit:

import timeit

print(timeit.timeit(

"""   
limit = 10000
prime_list = [i for i in range(2, limit+1)]

for prime in prime_list:
    for elem in range(prime*2, max(prime_list)+1, prime):
        if elem in prime_list:
            prime_list.remove(elem)
"""   
, number=10))
Sébastien Wieckowski
quelle
1
import timeit


def oct(x):
   return x*x


timeit.Timer("for x in range(100): oct(x)", "gc.enable()").timeit()
Yagmur SAHIN
quelle
Was ist gc.enable()?
Robin Andrews
0

Das integrierte Timeit-Modul funktioniert am besten über die IPython-Befehlszeile.

So steuern Sie Funktionen innerhalb eines Moduls:

from timeit import default_timer as timer
import sys

def timefunc(func, *args, **kwargs):
    """Time a function. 

    args:
        iterations=3

    Usage example:
        timeit(myfunc, 1, b=2)
    """
    try:
        iterations = kwargs.pop('iterations')
    except KeyError:
        iterations = 3
    elapsed = sys.maxsize
    for _ in range(iterations):
        start = timer()
        result = func(*args, **kwargs)
        elapsed = min(timer() - start, elapsed)
    print(('Best of {} {}(): {:.9f}'.format(iterations, func.__name__, elapsed)))
    return result
ChaimG
quelle
0

Beispiel für die Verwendung des Python REPL-Interpreters mit einer Funktion, die Parameter akzeptiert.

>>> import timeit                                                                                         

>>> def naive_func(x):                                                                                    
...     a = 0                                                                                             
...     for i in range(a):                                                                                
...         a += i                                                                                        
...     return a                                                                                          

>>> def wrapper(func, *args, **kwargs):                                                                   
...     def wrapper():                                                                                    
...         return func(*args, **kwargs)                                                                  
...     return wrapper                                                                                    

>>> wrapped = wrapper(naive_func, 1_000)                                                                  

>>> timeit.timeit(wrapped, number=1_000_000)                                                              
0.4458435332577161                                                                                        
Vlad Bezden
quelle
0

Sie würden zwei Funktionen erstellen und dann etwas Ähnliches ausführen. Beachten Sie, dass Sie die gleiche Anzahl von Ausführungen / Ausführungen auswählen möchten, um Apfel mit Apfel zu vergleichen.
Dies wurde unter Python 3.7 getestet.

Geben Sie hier die Bildbeschreibung ein Hier ist der Code zum einfachen Kopieren

!/usr/local/bin/python3
import timeit

def fibonacci(n):
    """
    Returns the n-th Fibonacci number.
    """
    if(n == 0):
        result = 0
    elif(n == 1):
        result = 1
    else:
        result = fibonacci(n-1) + fibonacci(n-2)
    return result

if __name__ == '__main__':
    import timeit
    t1 = timeit.Timer("fibonacci(13)", "from __main__ import fibonacci")
    print("fibonacci ran:",t1.timeit(number=1000), "milliseconds")
grepit
quelle