timeit — Cronometrar la ejecución de pequeños fragmentos de código Python¶
Propósito: | Cronometrar la ejecución de pequeños fragmentos de código Python. |
---|
El módulo timeit
proporciona una interfaz simple para determinar el tiempo
de ejecución de pequeñas partes de código Python. Utiliza una función de
tiempo específica de la plataforma para proporcionar el cálculo de tiempo más
preciso posible y reduce el impacto de los costos de inicio o apagado en el
cálculo de tiempo al ejecutar el código repetidamente.
Contenido del módulo¶
timeit
define una única clase pública, Timer
. El constructor de
Timer
toma una declaración para ser cronometrada y una declaración de
«configuración» (utilizada para inicializar variables, por ejemplo). Las
declaraciones de Python deben ser cadenas y pueden incluir nuevas líneas
incrustadas.
El método timeit()
ejecuta la instrucción de configuración una vez, luego
ejecuta la instrucción primaria repetidamente y devuelve la cantidad de tiempo
que pasa. El argumento de timeit()
controla cuántas veces ejecutar la
declaración; el valor predeterminado es 1,000,000.
Ejemplo básico¶
Para ilustrar cómo se usan los diversos argumentos para Timer
, aquí hay un
ejemplo simple que imprime un valor de identificación cuando se ejecuta cada
declaración.
import timeit
# using setitem
t = timeit.Timer("print('main statement')", "print('setup')")
print('TIMEIT:')
print(t.timeit(2))
print('REPEAT:')
print(t.repeat(3, 2))
Cuando se ejecuta, la salida muestra los resultados de las llamadas repetidas a
print()
.
$ python3 timeit_example.py
TIMEIT:
setup
main statement
main statement
3.7070130929350853e-06
REPEAT:
setup
main statement
main statement
setup
main statement
main statement
setup
main statement
main statement
[1.4499528333544731e-06, 1.1939555406570435e-06,
1.1870870366692543e-06]
timeit()
ejecuta la declaración de configuración una vez, luego llama a la
declaración principal count
veces. Devuelve un único valor de coma
flotante que representa la cantidad acumulada de tiempo que se pasa ejecutando
la instrucción principal.
Cuando se usa repeat()
, llama a timeit()
varias veces (3 en este caso)
y todas las respuestas se devuelven en una lista.
Almacenar valores en un diccionario¶
Este ejemplo más complejo compara la cantidad de tiempo que lleva llenar un
diccionario con una gran cantidad de valores utilizando una variedad de
métodos. Primero, se necesitan algunas constantes para configurar el
Timer
. La variable setup_statement
inicializa una lista de tuplas que
contienen cadenas y enteros que serán utilizados por las declaraciones
principales para construir diccionarios usando las cadenas como llaves y
almacenando los enteros como los valores asociados.
# A few constants
range_size = 1000
count = 1000
setup_statement = ';'.join([
"l = [(str(x), x) for x in range(1000)]",
"d = {}",
])
Se define una función de utilidad, show_results()
, para imprimir los
resultados en un formato útil. El método timeit()
devuelve la cantidad de
tiempo que lleva ejecutar la instrucción repetidamente. La salida de
show_results()
convierte eso en la cantidad de tiempo que toma por
iteración, y luego reduce aún más el valor a la cantidad promedio de tiempo que
lleva almacenar un elemento en el diccionario.
def show_results(result):
"Print microseconds per pass and per item."
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("{} items".format(range_size))
print("{} iterations".format(count))
print()
Para establecer una línea de base, la primera configuración probada usa
__setitem__()
. Todas las otras variaciones evitan sobrescribir valores que
ya están en el diccionario, por lo que esta versión simple debería ser la más
rápida.
El primer argumento para Timer
es una cadena de varias líneas, con espacio
en blanco preservado para garantizar que se analice correctamente cuando se
ejecuta. El segundo argumento es una constante establecida para inicializar la
lista de valores y el diccionario.
# Using __setitem__ without checking for existing values first
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 siguiente variación usa setdefault()
para garantizar que los valores que
ya están en el diccionario no se sobrescriban.
# Using 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))
Este método agrega el valor solo si se genera una excepción KeyError
cuando
se busca el valor existente.
# Using exceptions
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))
Y el último método usa «in
» para determinar si un diccionario tiene una
llave particular.
# Using "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,
)
show_results(t.timeit(number=count))
Cuando se ejecuta, la secuencia de comandos produce el siguiente resultado.
$ python3 timeit_dictionary.py
1000 items
1000 iterations
__setitem__: 91.79 usec/pass 0.09 usec/item
setdefault : 182.85 usec/pass 0.18 usec/item
KeyError : 80.87 usec/pass 0.08 usec/item
"not in" : 66.77 usec/pass 0.07 usec/item
Esos tiempos son para un MacMini y variarán dependiendo de qué hardware se use
y qué otros programas se estén ejecutando en el sistema. Experimenta con las
variables range_size
y count
, ya que diferentes combinaciones
producirán diferentes resultados.
Desde la línea de comando¶
Además de la interfaz programática, timeit
proporciona una interfaz de
línea de comandos para probar módulos sin instrumentación.
Para ejecutar el módulo, usa la opción -m
del intérprete de Python para
encontrar el módulo y tratarlo como el programa principal:
$ python3 -m timeit
Por ejemplo, para obtener ayuda:
$ python3 -m timeit -h
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.
...
El argumento statement
funciona de manera un poco diferente en la línea de
comando que el argumento de Timer
. En lugar de usar una cadena larga, pasa
cada línea de las instrucciones como un argumento de línea de comando separada.
Para sangrar líneas (como dentro de un bucle), incrusta espacios en la cadena
encerrándolos entre comillas.
$ python3 -m timeit -s \
"d={}" \
"for i in range(1000):" \
" d[str(i)] = i"
1000 loops, best of 3: 306 usec per loop
También es posible definir una función con código más complejo, luego llamar a la función desde la línea de comando.
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
Para ejecutar la prueba, pasa el código que importa los módulos y ejecuta la función de prueba.
$ python3 -m timeit \
"import timeit_setitem; timeit_setitem.test_setitem()"
1000 loops, best of 3: 401 usec per loop
Ver también
- Documentación de la biblioteca estándar para timeit
profile
– El móduloprofile
también es útil para el análisis de rendimiento.- Relojes monotónicos – Discusión del reloj monotónico del módulo
time
.