timeit - Cronometra l'Esecuzione di Piccoli Frammenti di Codice Python.
Scopo: Cronometra l'esecuzione di piccoli frammenti di codice Python
Il modulo timeit fornisce una semplice interfaccia per determinare il tempo di esecuzione di piccoli frammenti di codice Python. Usa una funzione di cronometraggio specifica alla piattaforma per fornire un calcolo del tempo più accurato possibile e ridurre l'impatto dei costi di partenza e chiusura dello script rispetto al calcolo del tempo eseguendo il codice ripetutamente.
Contenuti del Modulo
timeit definisce una singola classe pubblica, Timer
. Il costruttore per Timer
riceve una istruzione da cronometrare e una istruzione di "impostazione" (usata ad esempio per inizializzare variabili). Le istruzioni Python dovrebbero essere stringhe e possono includere ritorni a capo.
Il metodo timeit()
esegue una volta l'istruzione di impostazione, poi esegue l'istruzione primaria ripetutamente e ritorna il valore del tempo trascorso. Un argomento di timeit()
regola il numero di ripetizioni per l'istruzione; nella modalità predefinita è 1.000.000.
Esempio Base
Per illustrare come vengono usati i vari argomenti per Timer
, ecco un semplice esempio che stampa un valore specificato quando viene eseguita ciascuna istruzione.
# timeit_example.py
import timeit
# using setitem
t = timeit.Timer("print('istruzione principale')", "print('impostazione')")
print('TIMEIT:')
print(t.timeit(2))
print('RIPETE:')
print(t.repeat(3, 2))
Quando eseguito, il risultato mostra l'esito delle chiamate ripetute a print()
.
$ python3 timeit_example.py TIMEIT: impostazione istruzione principale istruzione principale 6.4179998844338115e-06 RIPETE: impostazione istruzione principale istruzione principale impostazione istruzione principale istruzione principale impostazione istruzione principale istruzione principale [6.894999842188554e-06, 6.880000000819564e-06, 6.137000127637293e-06]
timeit()
esegue l'istruzione di impostazione una volta, poi chiama l'istruzione principale un numero di volte pari a count
. Ritorna un singolo valore a virgola mobile che rappresenta il valore cumulativo del tempo trascorso nell'esecuzione dell'istruzione principale.
Quando viene usato repeat()
, esso chiama timeit()
diverse volte (3 in questo caso) e tutte le risposte sono ritornate in una lista.
Conservare i Valori in un Dizionario
Questo esempio più complesso confronta il tempo trascorso per popolare un dizionario con un gran numero di valori utilizzando vari metodi. Per prima cosa sono necessarie alcune costanti per configurare Timer
. La variabile setup_statement
inizializza una lista di tuple che contengono stringhe e interi che saranno usati dalle istruzioni principali per costruire dizionari usando le stringhe come chiave e conservando gli interi come valori associati.
Il programma di esempio è timeit_dictionary.py
e viene di seguito illustrato nelle sue componenti principali:
# Alcune costanti
range_size = 1000
count = 1000
setup_statement = ';'.join([
"l = [(str(x), x) for x in range(1000)]",
"d = {}",
])
Una funzione di utilità, show_results()
, viene definita per stampare i risultati in un formato consono. Il metodo timeit()
ritorna il tempo trascorso per eseguire l'istruzione ripetutamente. Il risultato di show_results()
lo converte nel tempo trascorso per iterazione, successivamente riduce il valore alla media di tempo trascorso per inserire un valore nel dizionario.
def show_results(result):
"Stampa i microsecondi per passaggio e per elemento"
global count, range_size
per_pass = 1000000 * (result / count)
print('{:6.2f} usec/pass'.format(per_pass), end=' ')
per_item = per_pass / range_size
print('{:6.2f} usec/item'.format(per_item))
print("{} elementi".format(range_size))
print("{} iterazioni".format(count))
print()
Per stabilire una linea base, la prima configurazione provata usa __setitem__()
. Tutte le altre varianti evitano di sovrascrivere valori se sono già nel dizionario, quindi questa versione dell'esempio dovrebbe essere la più veloce.
Il primo argomento di Timer
è una stringa multi riga, con spazi conservati in modo che possa essere interpretata correttamente quando eseguita. Il secondo argomento è una costante impostata per inizializzare la lista di valori e il dizionario.
# Usa __setitem__ senza prima verificare se i valori esistono
print('__setitem__:', end=' ')
t = timeit.Timer(
textwrap.dedent(
"""
for s, i in l:
d[s] = i
"""),
setup_statement,
)
show_results(t.timeit(number=count))
La variante successiva usa setdefault()
per assicurarsi che i valori già nel dizionario non vengano sovrascritti.
# Usa setdefault
print('setdefault :', end=' ')
t = timeit.Timer(
textwrap.dedent(
"""
for s, i in l:
d.setdefault(s, i)
"""),
setup_statement,
)
show_results(t.timeit(number=count))
Questo metodo aggiunge il valore solo se viene sollevata una eccezione KeyError
durante la ricerca per un valore esistente.
# Usa eccezioni
print('KeyError :', end=' ')
t = timeit.Timer(
textwrap.dedent(
"""
for s, i in l:
try:
existing = d[s]
except KeyError:
d[s] = i
"""),
setup_statement,
)
show_results(t.timeit(number=count))
L'ultimo metodo usa in
per determinare se un dizionario ha una particolare chiave.
# Usa "in"
print('"not in" :', end=' ')
t = timeit.Timer(
textwrap.dedent(
"""
for s, i in l:
if s not in d:
d[s] = i
"""),
setup_statement,
)
Quando lo script viene eseguito produce il seguente risultato:
$ python3 timeit_dictionary.py 1000 elementi 1000 iterazioni __setitem__: 64.32 usec/pass 0.06 usec/item setdefault : 198.63 usec/pass 0.20 usec/item KeyError : 53.17 usec/pass 0.05 usec/item "not in" : 43.53 usec/pass 0.04 usec/item
Il risultato potrebbe variare in base alla macchina nella quale viene eseguito lo script e in base a quali altri programmi sono in esecuzione nel sistema. Si esperimenti con le variabili range_size
e count
, visto che combinazioni diverse produrranno diversi risultati.
Da Riga di Comando
Oltre all'interfaccia da programma, timeit
fornisce una interfaccia da riga di comando per provare i moduli senza orchestrazione.
Per eseguire il modulo si utilizzi l'opzione -m
dell'interprete Pyhton per trovare il modulo e considerarlo come il programma principale. Per ottenere aiuto per il modulo ad esempio:
$ python3 -m timeit --help Tool for measuring execution time of small code snippets. This module avoids a number of common traps for measuring execution times. See also Tim Peters' introduction to the Algorithms chapter in the Python Cookbook, published by O'Reilly. ...
L'argomento statement
funziona in modo leggermente diverso da riga di comando rispetto all'argomento per Timer
. Invece di usare una lunga stringa, si passino le singole righe di istruzione come argomenti separati da riga di comando. Per indentare le righe (come per l'interno di un ciclo), si inseriscano spazi nella stringa racchiudendo la stessa tra apici.
$ python3 -m timeit -s "d={}" "for i in range(1000):" " d[str(i)] = i" 1000 loops, best of 3: 258 usec per loop
E' anche possibile definire una funzione con codice più complesso, poi chiamare la funzione da riga di comando.
# timeit_setitem.py
def test_setitem(range_size=1000):
l = [(str(x), x) for x in range(range_size)]
d = {}
for s, i in l:
d[s] = i
Per eseguire la prova, passare il codice che importa i moduli ed esegue la funzione di prova.
$ python3 -m timeit -s "import timeit_setitem; timeit_setitem.test_setitem()" 100000000 loops, best of 3: 0.00778 usec per loop
Vedere anche:
- timeit
- La documentazione della libreria standard per questo modulo.
- profile
- Il modulo
profile
è anche utile per analisi di prestazioni - Monotonic Clocks
- Discussione sul clock monotonico dal modulo time