Come usare il modulo timeit


351

Comprendo il concetto di cosa timeitfa ma non sono sicuro di come implementarlo nel mio codice.

Come posso confrontare due funzioni, diciamo insertion_sorte tim_sort, con timeit?

Risposte:


266

Il modo in cui timeit funziona consiste nell'eseguire il codice di installazione una volta e quindi effettuare chiamate ripetute a una serie di istruzioni. Quindi, se si desidera testare l'ordinamento, è necessario prestare attenzione affinché un passaggio in un ordinamento sul posto non influisca sul passaggio successivo con dati già ordinati (che, ovviamente, renderebbero il Timsort davvero brillante perché funziona meglio quando i dati sono già parzialmente ordinati).

Ecco un esempio di come impostare un test per l'ordinamento:

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

Si noti che la serie di istruzioni crea una nuova copia dei dati non ordinati su ogni passaggio.

Inoltre, prendi nota della tecnica di temporizzazione dell'esecuzione della suite di misurazione sette volte e del mantenimento solo del momento migliore: ciò può davvero aiutare a ridurre le distorsioni di misurazione dovute ad altri processi in esecuzione sul tuo sistema.

Questi sono i miei consigli per usare correttamente timeit. Spero che sia di aiuto :-)


8
Sì, include la copia dell'elenco (che è molto veloce rispetto all'ordinamento stesso). Se non copi, il primo passaggio ordina l'elenco e i rimanenti non devono eseguire alcun lavoro. Se vuoi conoscere il tempo giusto per l'ordinamento, esegui quanto sopra con e senza il timsort(a)e prendi la differenza :-)
Raymond Hettinger

Consiglierei di ripetere 7 volte per ogni configurazione, quindi media; piuttosto che il contrario. In questo modo, se ogni picco dovuto ad altri processi ha buone probabilità di essere ignorato del tutto, piuttosto che mediato.
massimo

75
@max Utilizza min () anziché la media dei tempi. Questa è una mia raccomandazione, di Tim Peters e di Guido van Rossum. Il tempo più veloce rappresenta il meglio che un algoritmo può eseguire quando le cache sono caricate e il sistema non è occupato con altre attività. Tutti i tempi sono rumorosi: il tempo più veloce è il meno rumoroso. È facile dimostrare che i tempi più veloci sono i più riproducibili e quindi i più utili quando si cronometrano due diverse implementazioni.
Raymond Hettinger,

4
Calcoli una media (bene, totale, ma è equivalente) per 1000 input; quindi ripetere 7 volte e prendere il minimo . È necessaria la media di oltre 1000 input perché si desidera la complessità dell'algoritmo media (non nel migliore dei casi). Hai bisogno del minimo proprio per il motivo che hai dato. Ho pensato di poter migliorare il tuo approccio scegliendo un input, eseguendo l'algoritmo 7 volte, prendendo il minimo; quindi ripetendolo per 1000 input diversi e prendendo la media. Quello che non ho realizzato è che lo fai .repeat(7,1000)già (usando lo stesso seme)! Quindi la tua soluzione è IMO perfetta.
max

5
Posso solo aggiungere che il modo in cui allocare il budget di 7000 esecuzioni (ad esempio, .repeat(7, 1000)vs .repeat(2, 3500)vs .repeat(35, 200) dovrebbe dipendere da come l'errore dovuto al carico del sistema si confronta con l'errore dovuto alla variabilità dell'input. Nel caso estremo se il tuo sistema è sempre sotto carico pesante e vedi una coda lunga e sottile a sinistra della distribuzione del tempo di esecuzione (quando la prendi in uno stato di inattività raro), potresti persino trovare .repeat(7000,1)più utile che .repeat(7,1000)se non è possibile programmare più di 7000 corse.
massimo

277

Se vuoi usarlo timeitin una sessione interattiva di Python, ci sono due comode opzioni:

  1. Usa la shell IPython . Presenta la comoda %timeitfunzione speciale:

    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 un interprete Python standard, è possibile accedere a funzioni e altri nomi definiti in precedenza durante la sessione interattiva importandoli dall'istruzione __main__setup:

    >>> 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 per mostrare la from __main__ import ftecnica. Non penso che questo sia così conosciuto come dovrebbe essere. È utile in casi come questo in cui viene programmata una chiamata di funzione o metodo. In altri casi (tempistica di una serie di passaggi), è meno utile perché introduce un overhead di chiamata di funzione.
Raymond Hettinger,

15
Si può solo fare%timeit f(x)
qed

Nota: l'impostazione "import f" consente di accedere a una lettura locale veloce - che non riflette esattamente una chiamata di funzione globale (di funzione veloce breve) nel tipico codice normale. In Py3.5 + è possibile fornire globali reali: "Modificato nella versione 3.5: è stato aggiunto il parametro globals opzionale."; Prima che i globi del modulo timeit fossero inevitabili (il che non ha molto senso). Forse i globali del codice chiamante ( sys._getframe(N).f_globals) avrebbero dovuto essere il default dall'inizio.
kxr,

140

Ti farò conoscere un segreto: il modo migliore di usare timeitè dalla riga di comando.

Sulla riga di comando, timeitesegue un'analisi statistica corretta: ti dice quanto tempo ha richiesto la corsa più breve. Questo è positivo perché tutti gli errori nei tempi sono positivi. Quindi il minor tempo ha il minimo errore. Non c'è modo di ottenere errori negativi perché un computer non può mai calcolare più velocemente di quanto possa calcolare!

Quindi, l'interfaccia della riga di comando:

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

È abbastanza semplice, eh?

Puoi impostare cose:

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

che è utile anche!

Se si desidera più righe, è possibile utilizzare la continuazione automatica della shell o utilizzare argomenti separati:

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

Questo dà una configurazione di

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

e tempi

sum(x)
min(y)

Se vuoi avere script più lunghi, potresti essere tentato di spostarti timeitall'interno di uno script Python. Suggerisco di evitarlo perché l'analisi e i tempi sono semplicemente migliori sulla riga di comando. Invece, tendo a creare script di shell:

 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

Questo può richiedere un po 'più di tempo a causa delle molteplici inizializzazioni, ma normalmente non è un grosso problema.


Ma cosa succede se si desidera utilizzare timeitall'interno del modulo?

Bene, il modo semplice è fare:

def function(...):
    ...

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

e questo ti dà il tempo cumulativo ( non minimo!) per eseguire quel numero di volte.

Per ottenere una buona analisi, utilizzare .repeate prendere il minimo:

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

Normalmente dovresti combinarlo con functools.partialinvece di lambda: ...abbassare il sovraccarico. Quindi potresti avere qualcosa del tipo:

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

Puoi anche fare:

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

che ti darebbe qualcosa di più vicino all'interfaccia dalla riga di comando, ma in un modo molto meno interessante. Il "from __main__ import ..."consente di utilizzare codice dal modulo principale all'interno dell'ambiente artificiale creato da timeit.

Vale la pena notare che questo è un involucro pratico per Timer(...).timeit(...)e quindi non è particolarmente bravo nei tempi. Personalmente preferisco di gran lunga usare Timer(...).repeat(...)come ho mostrato sopra.


Avvertenze

Ci sono alcuni avvertimenti con timeitquella presa ovunque.

  • Le spese generali non sono contabilizzate. Di 'che vuoi tempo x += 1, per scoprire quanto tempo richiede l'aggiunta:

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

    Bene, non è 0,0476 µs. Sai solo che è meno di quello. Tutto l'errore è positivo.

    Quindi prova a trovare un overhead puro :

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

    Questo è un buon overhead del 30% solo dal momento! Ciò può distorcere enormemente i tempi relativi. Ma ti importavi solo dei tempi di aggiunta ; anche i tempi di ricerca per xdevono essere inclusi nell'overhead:

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

    La differenza non è molto più grande, ma è lì.

  • I metodi mutanti sono pericolosi.

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

    Ma è completamente sbagliato! xè l'elenco vuoto dopo la prima iterazione. Dovrai reinizializzare:

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

    Ma poi hai un sacco di spese generali. Conto per quello separatamente.

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

    Si noti che sottraendo l'overhead è ragionevole qui solo perché l'overhead è una frazione del tempo minima.

    Per il tuo esempio, vale la pena notare che sia Insertion Sort che Tim Sort hanno comportamenti di temporizzazione completamente insoliti per gli elenchi già ordinati. Questo significa che avrai bisogno di una via di random.shufflemezzo se vuoi evitare di rovinare i tuoi tempi.


1
cosa significa usec? sono microsecondi?
Hasan Iqbal,

2
@HasanIqbalAnik Sì.
Veedrac,

@StefanPochmann Perché non tenta di campionare più volte.
Veedrac,

I lettori di questa risposta potrebbero anche essere interessati a utilizzare Python's timeitda un programma ma funzionando allo stesso modo della riga di comando? .
Graham,

@Veedrac Considerando la tua affermazione sulla sottrazione del sovraccarico di tempismo puro, timeitesegue una passdichiarazione quando non vengono forniti argomenti, il che, ovviamente, richiede del tempo. Se vengono forniti argomenti, nonpass verranno eseguiti, quindi sottraendo alcuni usec da ogni tempistica sarebbe errato. 0.014
Arne,

99

Se si desidera confrontare rapidamente due blocchi di codice / funzioni, è possibile:

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

Trovo il modo più semplice per usare timeit è dalla riga di comando:

Dato test.py :

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

eseguire timeit in questo modo:

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

18

per me, questo è il modo più veloce:

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)

7

Questo funziona alla grande:

  python -m timeit -c "$(cat file_name.py)"

Quale sarebbe l'equivalente di Windows?
Shailen,

2
Come si passano i parametri, se lo script ne richiede uno?
Juuso Ohtonen,

3

consente di impostare lo stesso dizionario in ciascuno dei seguenti e testare il tempo di esecuzione.

L'argomento setup è fondamentalmente l'impostazione del dizionario

Il numero deve eseguire il codice 1000000 volte. Non l'installazione ma lo stmt

Quando esegui questo, puoi vedere che l'indice è molto più veloce di ottenere. Puoi eseguirlo più volte per vedere.

Fondamentalmente il codice cerca di ottenere il valore di c nel dizionario.

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

Ecco i miei risultati, i tuoi differiranno.

per indice: 0.20900007452246427

per ottenere: 0,54841166886888


Quale versione di Python stai usando?
Eduardo,

3

passa semplicemente l'intero codice come argomento di 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))

1
import timeit


def oct(x):
   return x*x


timeit.Timer("for x in range(100): oct(x)", "gc.enable()").timeit()

Che cosa è gc.enable()?
Robin Andrews

0

Il modulo timeit incorporato funziona meglio dalla riga di comando di IPython.

Per temporizzare le funzioni all'interno di un modulo:

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

Esempio di come utilizzare l'interprete REPL di Python con la funzione che accetta i parametri.

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

Dovresti creare due funzioni e quindi eseguire qualcosa di simile a questo. Si noti che si desidera scegliere lo stesso numero di esecuzione / esecuzione per confrontare Apple con Apple.
Questo è stato testato sotto Python 3.7.

inserisci qui la descrizione dell'immagine Ecco il codice per facilitarne la copia

!/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")
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.