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.

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

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

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