Jak korzystać z modułu timeit


351

Rozumiem pojęcie tego, co timeitrobi, ale nie jestem pewien, jak zaimplementować to w moim kodzie.

Jak mogę porównać dwie funkcje, powiedzmy insertion_sorti tim_sort, z timeit?

Odpowiedzi:


266

Sposób działania timeit polega na jednokrotnym uruchomieniu kodu instalacyjnego, a następnie wielokrotnym wywoływaniu szeregu instrukcji. Tak więc, jeśli chcesz przetestować sortowanie, należy zachować ostrożność, aby jedno przejście w sortowaniu lokalnym nie wpłynęło na następne przejście z już posortowanymi danymi (co oczywiście sprawiłoby, że Timsort naprawdę zabłysnął, ponieważ działa najlepiej gdy dane są już częściowo zamówione).

Oto przykład, jak skonfigurować test do sortowania:

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

Zauważ, że seria instrukcji tworzy nową kopię nieposortowanych danych przy każdym przejściu.

Zwróć także uwagę na technikę pomiaru czasu, która polega na uruchomieniu pakietu pomiarowego siedem razy i zachowaniu najlepszego czasu - może to naprawdę pomóc w zmniejszeniu zniekształceń pomiaru spowodowanych innymi procesami działającymi w systemie.

To są moje wskazówki, jak prawidłowo używać timeit. Mam nadzieję że to pomoże :-)


8
Tak, zawiera kopię listy (która jest bardzo szybka w porównaniu do samego sortowania). Jeśli jednak nie skopiujesz, pierwszy przebieg posortuje listę, a pozostałe zaliczone nie będą wymagały żadnej pracy. Jeśli chcesz poznać czas tylko dla tego rodzaju, uruchom powyższe zi bez timsort(a)i weź różnicę :-)
Raymond Hettinger,

Polecam powtórzyć 7 razy dla każdej konfiguracji, a następnie uśrednić; zamiast na odwrót. W ten sposób, jeśli każdy skok spowodowany innymi procesami ma dużą szansę zostać całkowicie zignorowany, a nie uśredniony.
maks.

75
@max Użyj min () zamiast średniej taktowania. To jest zalecenie ode mnie, Tima Petersa i Guido van Rossuma. Najszybszy czas reprezentuje najlepsze, co algorytm może wykonać, gdy pamięci podręczne są załadowane, a system nie jest zajęty innymi zadaniami. Wszystkie czasy są hałaśliwe - najszybszy czas jest najmniej hałaśliwy. Łatwo jest wykazać, że najszybsze taktowania są najbardziej powtarzalne, a zatem najbardziej przydatne w przypadku dwóch różnych implementacji.
Raymond Hettinger

4
Obliczasz średnią (cóż, całkowitą, ale równoważną) dla 1000 danych wejściowych; następnie powtórz 7 razy i weź minimum . Potrzebujesz uśrednienia ponad 1000 danych wejściowych, ponieważ potrzebujesz średniej (nie w najlepszym przypadku) złożoności algorytmu. Potrzebujesz minimum z dokładnie tego, co podałeś. Pomyślałem, że mogę ulepszyć twoje podejście, wybierając jedno wejście, uruchamiając algorytm 7 razy, biorąc minimum; następnie powtarzając to dla 1000 różnych danych wejściowych i biorąc średnią. Nie zdawałem sobie sprawy, że .repeat(7,1000)już to robisz (używając tego samego ziarna)! Więc twoje rozwiązanie jest idealne IMO.
maks.

5
Mogę tylko dodać, że sposób alokacji budżetu 7000 egzekucji (np. Vs .repeat(7, 1000)vs. ) powinien zależeć od tego, jak błąd spowodowany obciążeniem systemu porównuje się z błędem wynikającym ze zmienności danych wejściowych. W skrajnym przypadku, jeśli twój system jest zawsze obciążony i widzisz długi cienki ogon po lewej stronie rozkładu czasu wykonania (gdy złapiesz go w rzadkim stanie bezczynności), możesz nawet okazać się bardziej przydatny niż gdybyś nie może przeznaczyć więcej niż 7000 uruchomień .repeat(2, 3500).repeat(35, 200.repeat(7000,1).repeat(7,1000)
maks.

277

Jeśli chcesz korzystać timeitz interaktywnej sesji Pythona, istnieją dwie wygodne opcje:

  1. Użyj powłoki IPython . Posiada wygodną %timeitfunkcję specjalną:

    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. W standardowym interpretatorze Pythona można uzyskać dostęp do funkcji i innych nazw zdefiniowanych wcześniej podczas sesji interaktywnej, importując je z __main__instrukcji konfiguracji:

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

97
+1 za pokazanie from __main__ import ftechniki. Nie sądzę, że jest to tak powszechnie znane, jak powinno być. Przydaje się w takich przypadkach, w których odmierzana jest funkcja lub wywołanie metody. W innych przypadkach (odmierzanie serii kroków) jest to mniej pomocne, ponieważ wprowadza narzut wywołania funkcji.
Raymond Hettinger

15
Możesz po prostu zrobić%timeit f(x)
qed

Uwaga: konfiguracja „import f” umożliwia dostęp do szybkiego odczytu lokalnego FA, który nie odzwierciedla dokładnie wywołania funkcji globalnej (krótkiej funkcji szybkiego) w typowym normalnym kodzie. W Py3.5 + można podać prawdziwe globały: „Zmieniono w wersji 3.5: Dodano opcjonalny parametr globals.”; Przed globałami modułu timeit były nieuniknione (co nie ma większego sensu). Być może globały kodu wywołującego ( sys._getframe(N).f_globals) powinny być od początku domyślne.
kxr

140

Powiem ci sekret: najlepszy sposób użycia timeitto w wierszu poleceń.

W wierszu poleceń timeitwykonuje odpowiednią analizę statystyczną: pokazuje, jak długo trwał najkrótszy bieg. Jest to dobre, ponieważ każdy błąd w pomiarze czasu jest dodatni. Zatem najkrótszy czas zawiera najmniej błędów. Nie ma sposobu, aby uzyskać błąd ujemny, ponieważ komputer nigdy nie może wykonać obliczeń szybciej niż potrafi!

Interfejs wiersza poleceń:

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

To całkiem proste, prawda?

Możesz skonfigurować różne rzeczy:

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

co też jest przydatne!

Jeśli chcesz mieć wiele linii, możesz albo użyć automatycznej kontynuacji powłoki, albo użyć osobnych argumentów:

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

To daje konfigurację

x = range(1000)
y = range(100)

i czasy

sum(x)
min(y)

Jeśli chcesz mieć dłuższe skrypty, możesz ulec pokusie przejścia do timeitwnętrza skryptu Python. Sugeruję unikanie tego, ponieważ analiza i synchronizacja są po prostu lepsze w wierszu poleceń. Zamiast tego robię skrypty powłoki:

 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

Może to potrwać nieco dłużej z powodu wielu inicjalizacji, ale zwykle nie jest to wielka sprawa.


Ale co jeśli chcesz używać timeitw swoim module?

Cóż, prosty sposób to zrobić:

def function(...):
    ...

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

a to daje łączny ( nie minimalny!) czas na uruchomienie tej liczby razy.

Aby uzyskać dobrą analizę, użyj .repeati weź minimum:

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

Zwykle powinieneś połączyć to z functools.partialzamiast lambda: ...obniżać koszty ogólne. W ten sposób możesz mieć coś takiego:

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

Możesz także:

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

co dałoby ci coś bliższego interfejsu z wiersza poleceń, ale w znacznie mniej fajny sposób. "from __main__ import ..."Pozwala używać kodu z głównego modułu wewnątrz środowiska stworzonego przez sztuczną timeit.

Warto zauważyć, że jest to wygodne opakowanie, Timer(...).timeit(...)więc nie jest szczególnie dobre w czasie. Osobiście zdecydowanie wolę używać, Timer(...).repeat(...)jak pokazano powyżej.


Ostrzeżenia

timeitWszędzie istnieje kilka zastrzeżeń z tym związanych.

  • Koszty ogólne nie są rozliczane. Powiedz, że chcesz mieć czas x += 1, aby dowiedzieć się, jak długo trwa dodawanie:

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

    Cóż, to nie jest 0,0476 µs. Wiesz tylko, że to mniej niż to. Wszystkie błędy są pozytywne.

    Więc spróbuj znaleźć czysty narzut:

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

    To dobre 30% narzutu od samego momentu! Może to znacznie wypaczyć względne czasy. Ale tak naprawdę zależało ci tylko na dodawaniu czasu; czasy przeglądania xrównież muszą być uwzględnione w kosztach ogólnych:

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

    Różnica nie jest dużo większa, ale jest.

  • Metody mutowania są niebezpieczne.

    >>> python -m timeit -s "x = [0]*100000" "while x: x.pop()"
    10000000 loops, best of 3: 0.0436 usec per loop

    Ale to całkowicie źle! xjest pustą listą po pierwszej iteracji. Musisz ponownie zainicjować:

    >>> python -m timeit "x = [0]*100000" "while x: x.pop()"
    100 loops, best of 3: 9.79 msec per loop

    Ale wtedy masz dużo kosztów ogólnych. Uwzględnij to osobno.

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

    Zauważ, że odejmowanie narzutu jest tutaj uzasadnione tylko dlatego, że narzut stanowi ułamek czasu.

    Na przykład warto zauważyć, że zarówno Sortowanie wstawiane, jak i Sortowanie czasowe mają zupełnie nietypowe zachowania czasowe dla już posortowanych list. Oznacza to, że będziesz potrzebować pośredniego random.shufflesortowania, jeśli chcesz uniknąć rujnowania swoich czasów.


1
co znaczy usec? czy to mikrosekundy?
Hasan Iqbal

2
@HasanIqbalAnik Tak.
Veedrac,

@StefanPochmann Ponieważ nie próbuje wielokrotnie próbkować.
Veedrac

Czytelnicy tej odpowiedzi mogą być również zainteresowani używaniem Pythona timeitz programu, ale działając tak samo jak w wierszu poleceń? .
Graham

@ Veedrac Biorąc pod uwagę twoje zdanie na temat odejmowania czystego narzutu czasowego, timeitwykonuje passpolecenie, gdy nie podano żadnych argumentów, co oczywiście zajmuje trochę czasu. Jeśli podane passzostaną jakiekolwiek argumenty, nie zostaną wykonane, więc odjęcie niektórych 0.014usecs od każdego czasu byłoby niepoprawne.
Arne

99

Jeśli chcesz szybko porównać dwa bloki kodu / funkcji, możesz:

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)

43

Uważam, że najłatwiejszym sposobem użycia timeit jest z wiersza poleceń:

Biorąc pod uwagę test.py :

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

uruchom czas w następujący sposób:

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

18

dla mnie to najszybszy sposób:

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


timeit.timeit(stmt=foo, number=1234567)

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)


3

pozwala skonfigurować ten sam słownik w każdym z poniższych i przetestować czas wykonania.

Argumentem konfiguracji jest w zasadzie konfiguracja słownika

Liczba ma uruchomić kod 1000000 razy. Nie konfiguracja, ale stmt

Po uruchomieniu możesz zobaczyć, że indeks jest znacznie szybszy niż get. Możesz go uruchomić wiele razy, aby zobaczyć.

Kod w zasadzie próbuje uzyskać wartość c w słowniku.

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

Oto moje wyniki, twoje będą się różnić.

według indeksu: 0.20900007452246427

przez get: 0,54841166886888


Jakiej wersji Pythona używasz?
Eduardo

3

po prostu przekaż cały kod jako argument timeit:

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


0

Wbudowany moduł timeit działa najlepiej z wiersza polecenia IPython.

Do funkcji czasowych z poziomu modułu:

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

0

Przykład użycia interpretera REPL w języku Python z funkcją akceptującą parametry.

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

0

Utworzyłbyś dwie funkcje, a następnie uruchomiłeś coś podobnego do tego. Zauważ, że chcesz wybrać tę samą liczbę wykonania / uruchomienia, aby porównać jabłko z jabłkiem.
Zostało to przetestowane pod Pythonem 3.7.

wprowadź opis zdjęcia tutaj Oto kod ułatwiający kopiowanie

!/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")
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.