timeit — Mide el tiempo de ejecución de pequeños fragmentos de código

Código fuente: Lib/timeit.py


Este módulo proporciona una forma sencilla de cronometrar pequeños fragmentos de código Python. Tiene tanto una Interfaz de línea de comandos como una invocable. Evita una serie de trampas comunes para medir los tiempos de ejecución. Véase también la introducción de Tim Peters al capítulo «Algoritmos» en la segunda edición de Python Cookbook, publicado por O’Reilly.

Ejemplos básicos

En el ejemplo siguiente se muestra cómo se puede utilizar Interfaz de línea de comandos para comparar tres expresiones diferentes:

$ python3 -m timeit '"-".join(str(n) for n in range(100))'
10000 loops, best of 5: 30.2 usec per loop
$ python3 -m timeit '"-".join([str(n) for n in range(100)])'
10000 loops, best of 5: 27.5 usec per loop
$ python3 -m timeit '"-".join(map(str, range(100)))'
10000 loops, best of 5: 23.2 usec per loop

Esto se puede lograr desde Interfaz de Python con:

>>> import timeit
>>> timeit.timeit('"-".join(str(n) for n in range(100))', number=10000)
0.3018611848820001
>>> timeit.timeit('"-".join([str(n) for n in range(100)])', number=10000)
0.2727368790656328
>>> timeit.timeit('"-".join(map(str, range(100)))', number=10000)
0.23702679807320237

Un invocable también se puede pasar desde el Interfaz de Python:

>>> timeit.timeit(lambda: "-".join(map(str, range(100))), number=10000)
0.19665591977536678

Sin embargo, tenga en cuenta que timeit() determinará automáticamente el número de repeticiones solo cuando se utiliza la interfaz de línea de comandos. En la sección Ejemplos puede encontrar ejemplos más avanzados.

Interfaz de Python

El módulo define tres funciones de conveniencia y una clase pública:

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)

Cree una instancia de Timer con la instrucción dada, el código setup y la función timer y ejecute su método timeit() con las ejecuciones number. El argumento globals opcional especifica un espacio de nombres en el que se ejecutará el código.

Distinto en la versión 3.5: El parámetro opcional globals fue añadido.

timeit.repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=5, number=1000000, globals=None)

Crea una instancia de Timer con la instrucción dada, el código setup y la función timer y ejecuta su método repeat() con las ejecuciones repeat y number dadas. El argumento globals opcional especifica un espacio de nombres en el que se ejecutará el código.

Distinto en la versión 3.5: El parámetro opcional globals fue añadido.

Distinto en la versión 3.7: El valor por defecto para repeat cambió de 3 a 5.

timeit.default_timer()

El temporizador por defecto, que es siempre time.perf_counter().

Distinto en la versión 3.3: time.perf_counter() es ahora el temporizador por defecto.

class timeit.Timer(stmt='pass', setup='pass', timer=<timer function>, globals=None)

Clase para la velocidad de tiempo de ejecución de pequeños fragmentos de código.

El constructor toma una instrucción que se cronometra, una instrucción adicional utilizada para la instalación, y una función de temporizador. Ambas instrucciones tienen como valor predeterminado 'pass'; la función del temporizador depende de la plataforma (consulte la cadena doc del módulo). stmt y setup también pueden contener varias instrucciones separadas por ; o líneas nuevas, siempre y cuando no contengan literales de cadena de varias líneas. La instrucción se ejecutará de forma predeterminada dentro del espacio de nombres timeit; este comportamiento se puede controlar pasando un espacio de nombres a globals.

Para medir el tiempo de ejecución de la primera instrucción, utilice el método timeit(). Los métodos repeat() y autorange() son métodos de conveniencia para llamar al método timeit() varias veces.

El tiempo de ejecución de setup se excluye de la ejecución de tiempo total.

Los parámetros stmt y setup también pueden tomar objetos a los que se puede llamar sin argumentos. Esto embebe llamadas a ellos en una función de temporizador que luego será ejecutada por timeit(). Tenga en cuenta que la sobrecarga de tiempo es un poco mayor en este caso debido a las llamadas de función adicionales.

Distinto en la versión 3.5: El parámetro opcional globals fue añadido.

timeit(number=1000000)

Tiempo number ejecuciones de la instrucción principal. Esto ejecuta la instrucción setup una vez y, a continuación, retorna el tiempo que se tarda en ejecutar la instrucción principal varias veces, medida en segundos como un float. El argumento es el número de veces que se ejecuta el bucle, por defecto a un millón. La instrucción principal, la instrucción setup y la función timer que se va a utilizar se pasan al constructor.

Nota

De forma predeterminada, timeit() desactiva temporalmente garbage collection durante la medición. La ventaja de este enfoque es que hace que los tiempos independientes sean más comparables. La desventaja es que GC puede ser un componente importante del rendimiento de la función que se está midiendo. Si es así, GC se puede volver a habilitar como la primera instrucción en la cadena setup. Por ejemplo:

timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit()
autorange(callback=None)

Determina automáticamente cuántas veces llamar a timeit().

Esta es una función de conveniencia que llama a timeit() repetidamente para que el tiempo total >= 0,2 segundos, retornando el eventual (número de bucles, tiempo tomado para ese número de bucles). Este método llama a timeit() con números crecientes de la secuencia 1, 2, 5, 10, 20, 50, … hasta que el tiempo necesario sea de al menos 0.2 segundos.

Si callback se da y no es None, se llamará después de cada prueba con dos argumentos: callback(number, time_taken).

Nuevo en la versión 3.6.

repeat(repeat=5, number=1000000)

Llama a timeit() algunas veces.

Esta es una función de conveniencia que llama a timeit() repetidamente, retornando una lista de resultados. El primer argumento especifica cuántas veces se debe llamar a timeit(). El segundo argumento especifica el argumento number para timeit().

Nota

Es tentador calcular la media y la desviación estándar del vector de resultados e informar de estos. Sin embargo, esto no es muy útil. En un caso típico, el valor más bajo proporciona un límite inferior para la rapidez con la que el equipo puede ejecutar el fragmento de código especificado; valores más altos en el vector de resultado normalmente no son causados por la variabilidad en la velocidad de Python, sino por otros procesos que interfieren con su precisión de sincronización. Así que el min() del resultado es probablemente el único número que debería estar interesado en. Después de eso, usted debe mirar todo el vector y aplicar el sentido común en lugar de las estadísticas.

Distinto en la versión 3.7: El valor por defecto para repeat cambió de 3 a 5.

print_exc(file=None)

Ayudante para imprimir un seguimiento desde el código cronometrado.

Uso típico:

t = Timer(...)       # outside the try/except
try:
    t.timeit(...)    # or t.repeat(...)
except Exception:
    t.print_exc()

La ventaja sobre el seguimiento estándar es que se mostrarán las líneas de origen de la plantilla compilada. El argumento opcional file dirige dónde se envía el seguimiento; por defecto a sys.stderr.

Interfaz de línea de comandos

Cuando se llama como un programa desde la línea de comandos, se utiliza el siguiente formulario:

python -m timeit [-n N] [-r N] [-u U] [-s S] [-h] [statement ...]

Cuando las siguientes opciones son entendidas:

-n N, --number=N

cuantas veces para ejecutar “statement

-r N, --repeat=N

cuantas veces se va a repetir el temporizador (predeterminado 5)

-s S, --setup=S

instrucción a ser ejecutada una vez inicialmente (por defecto pass)

-p, --process

mide el tiempo de proceso, no el tiempo total de ejecución, utilizando time.process_time() en lugar de time.perf_counter(), que es el valor predeterminado

Nuevo en la versión 3.3.

-u, --unit=U

especifica una unidad de tiempo para la salida del temporizador; puede ser nsec, usec, msec o sec

Nuevo en la versión 3.5.

-v, --verbose

imprime los resultados de tiempo en bruto; repetir para más dígitos de precisión

-h, --help

imprime un mensaje de uso corto y sale

Se puede dar una instrucción de varias líneas especificando cada línea como un argumento de instrucción independiente; Las líneas con sangría son posibles entrecomillando un argumento y utilizando espacios iniciales. Múltiples opciones -s se tratan de forma similar.

Si no se da -n, se calcula un número adecuado de bucles intentando aumentar los números de la secuencia 1, 2, 5, 10, 20, 50, … hasta que el tiempo total sea de al menos 0.2 segundos.

Las mediciones de default_timer() pueden verse afectadas por otros programas que se ejecutan en la misma máquina, por lo que lo mejor que se puede hacer cuando es necesario una medición de tiempo precisa es repetir la medición un par de veces y utilizar el mejor tiempo. La opción -r es buena para esto; el valor predeterminado de 5 repeticiones es probablemente suficiente en la mayoría de los casos. Puede usar time.process_time() para medir el tiempo de CPU.

Nota

Hay una cierta sobrecarga de línea base asociada con la ejecución de una instrucción pass. El código aquí no intenta ocultarlo, pero debe ser consciente de ello. La sobrecarga de línea base se puede medir invocando el programa sin argumentos y puede diferir entre versiones de Python.

Ejemplos

Es posible proporcionar una instrucción setup que se ejecuta solo una vez al inicio:

$ python -m timeit -s 'text = "sample string"; char = "g"'  'char in text'
5000000 loops, best of 5: 0.0877 usec per loop
$ python -m timeit -s 'text = "sample string"; char = "g"'  'text.find(char)'
1000000 loops, best of 5: 0.342 usec per loop

In the output, there are three fields. The loop count, which tells you how many times the statement body was run per timing loop repetition. The repetition count (“best of 5”) which tells you how many times the timing loop was repeated, and finally the time the statement body took on average within the best repetition of the timing loop. That is, the time the fastest repetition took divided by the loop count.

>>> import timeit
>>> timeit.timeit('char in text', setup='text = "sample string"; char = "g"')
0.41440500499993504
>>> timeit.timeit('text.find(char)', setup='text = "sample string"; char = "g"')
1.7246671520006203

Se puede hacer lo mismo usando la clase Timer y sus métodos:

>>> import timeit
>>> t = timeit.Timer('char in text', setup='text = "sample string"; char = "g"')
>>> t.timeit()
0.3955516149999312
>>> t.repeat()
[0.40183617287970225, 0.37027556854118704, 0.38344867356679524, 0.3712595970846668, 0.37866875250654886]

En los ejemplos siguientes se muestra cómo cronometrar expresiones que contienen varias líneas. Aquí comparamos el coste de usar hasattr() frente a try/except para probar los atributos de objeto faltantes y presentes:

$ python -m timeit 'try:' '  str.__bool__' 'except AttributeError:' '  pass'
20000 loops, best of 5: 15.7 usec per loop
$ python -m timeit 'if hasattr(str, "__bool__"): pass'
50000 loops, best of 5: 4.26 usec per loop

$ python -m timeit 'try:' '  int.__bool__' 'except AttributeError:' '  pass'
200000 loops, best of 5: 1.43 usec per loop
$ python -m timeit 'if hasattr(int, "__bool__"): pass'
100000 loops, best of 5: 2.23 usec per loop
>>> import timeit
>>> # attribute is missing
>>> s = """\
... try:
...     str.__bool__
... except AttributeError:
...     pass
... """
>>> timeit.timeit(stmt=s, number=100000)
0.9138244460009446
>>> s = "if hasattr(str, '__bool__'): pass"
>>> timeit.timeit(stmt=s, number=100000)
0.5829014980008651
>>>
>>> # attribute is present
>>> s = """\
... try:
...     int.__bool__
... except AttributeError:
...     pass
... """
>>> timeit.timeit(stmt=s, number=100000)
0.04215312199994514
>>> s = "if hasattr(int, '__bool__'): pass"
>>> timeit.timeit(stmt=s, number=100000)
0.08588060699912603

Para dar al módulo timeit acceso a las funciones que defina, puede pasar un parámetro setup que contenga una instrucción import:

def test():
    """Stupid test function"""
    L = [i for i in range(100)]

if __name__ == '__main__':
    import timeit
    print(timeit.timeit("test()", setup="from __main__ import test"))

Otra opción es pasar globals() al parámetro globals, lo que hará que el código se ejecute dentro del espacio de nombres global actual. Esto puede ser más conveniente que especificar individualmente las importaciones:

def f(x):
    return x**2
def g(x):
    return x**4
def h(x):
    return x**8

import timeit
print(timeit.timeit('[func(42) for func in (f,g,h)]', globals=globals()))