Cómo usar el módulo timeit

351

Entiendo el concepto de lo que timeithace pero no estoy seguro de cómo implementarlo en mi código.

¿Cómo puedo comparar dos funciones, digamos insertion_sorty tim_sort, con timeit?

Neemaximo
fuente

Respuestas:

266

La forma en que funciona timeit es ejecutar el código de configuración una vez y luego realizar llamadas repetidas a una serie de declaraciones. Por lo tanto, si desea probar la clasificación, es necesario tener cuidado para que una pasada en una ordenación en el lugar no afecte a la siguiente pasada con datos ya ordenados (eso, por supuesto, haría que el Timsort realmente brille porque funciona mejor) cuando los datos ya están parcialmente ordenados).

Aquí hay un ejemplo de cómo configurar una prueba para ordenar:

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

Tenga en cuenta que la serie de declaraciones hace una copia nueva de los datos sin clasificar en cada pase.

Además, tenga en cuenta la técnica de sincronización de ejecutar el conjunto de mediciones siete veces y mantener solo el mejor momento; esto realmente puede ayudar a reducir las distorsiones de medición debidas a otros procesos que se ejecutan en su sistema.

Esos son mis consejos para usar Timeit correctamente. Espero que esto ayude :-)

Raymond Hettinger
fuente
8
Sí, incluye la copia de la lista (que es muy rápida en comparación con el tipo en sí). Sin embargo, si no copia, el primer pase clasifica la lista y el pase restante no tiene que hacer ningún trabajo. Si desea saber el tiempo para el tipo, ejecute lo anterior con y sin el timsort(a)y tome la diferencia :-)
Raymond Hettinger
Recomiendo repetir 7 veces para cada configuración, y luego promediar; en lugar de al revés. De esta manera, si cada pico debido a otros procesos tiene una buena posibilidad de ser ignorado por completo, en lugar de ser promediado.
max
75
@max Use el min () en lugar del promedio de los tiempos. Esa es una recomendación mía, de Tim Peters y de Guido van Rossum. El tiempo más rápido representa lo mejor que un algoritmo puede realizar cuando se cargan los cachés y el sistema no está ocupado con otras tareas. Todos los tiempos son ruidosos: el tiempo más rápido es el menos ruidoso. Es fácil demostrar que los tiempos más rápidos son los más reproducibles y, por lo tanto, los más útiles al momento de dos implementaciones diferentes.
Raymond Hettinger
44
Calcula un promedio (bueno, total, pero es equivalente) para 1000 entradas; luego repite 7 veces y toma el mínimo . Necesita promediar más de 1000 entradas porque desea la complejidad promedio (no el mejor de los casos) del algoritmo. Necesitas el mínimo precisamente por la razón que diste. Pensé que podía mejorar su enfoque eligiendo una entrada, ejecutando el algoritmo 7 veces, tomando el mínimo; luego repitiéndolo para 1000 entradas diferentes y tomando el promedio. ¡Lo que no me di cuenta es que tu .repeat(7,1000)ya lo haces (usando la misma semilla)! Entonces su solución es IMO perfecta.
max
55
Solo puedo agregar que la forma en que asigna su presupuesto de 7000 ejecuciones (por ejemplo, .repeat(7, 1000)vs .repeat(2, 3500)vs .repeat(35, 200) debería depender de cómo el error debido a la carga del sistema se compara con el error debido a la variabilidad de entrada. En el caso extremo, si su sistema está siempre bajo una carga pesada, y ve una cola larga y delgada a la izquierda de la distribución del tiempo de ejecución (cuando lo atrapa en un estado inactivo raro), incluso podría .repeat(7000,1)ser más útil que .repeat(7,1000)si no puede presupuestar más de 7000 ejecuciones.
max
277

Si desea usar timeiten una sesión interactiva de Python, hay dos opciones convenientes:

  1. Use el shell de IPython . Cuenta con la conveniente %timeitfunción especial:

    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. En un intérprete estándar de Python, puede acceder a las funciones y otros nombres que definió anteriormente durante la sesión interactiva importándolos desde __main__la declaración de configuración:

    >>> 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]
Sven Marnach
fuente
97
+1 por mostrar la from __main__ import ftécnica. No creo que esto sea tan conocido como debería ser. Es útil en casos como este donde se está cronometrando una llamada a una función o método. En otros casos (cronometrar una serie de pasos), es menos útil porque introduce sobrecarga de llamadas de función.
Raymond Hettinger
15
Puedes hacerlo%timeit f(x)
qed el
Nota: la configuración "import f" hace que el acceso a fa sea de lectura local rápida, que no refleja exactamente una llamada de función global (de función rápida corta) en un código normal típico. En Py3.5 + se pueden suministrar globales globals: "Modificado en la versión 3.5: se agregó el parámetro opcional global". Antes de los globales del módulo timeit donde era inevitable (lo que no tiene mucho sentido). Posiblemente, los globales del código de llamada ( sys._getframe(N).f_globals) deberían haber sido los predeterminados desde el principio.
kxr
140

Te contaré un secreto: la mejor manera de usarlo timeites en la línea de comando.

En la línea de comando, timeitrealiza un análisis estadístico adecuado: le indica cuánto tiempo tomó la ejecución más corta. Esto es bueno porque todo error en el tiempo es positivo. Entonces, el menor tiempo tiene el menor error. ¡No hay forma de obtener un error negativo porque una computadora nunca puede calcular más rápido de lo que puede calcular!

Entonces, la interfaz de línea de comandos:

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

Eso es bastante simple, ¿eh?

Puedes configurar cosas:

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

¡lo cual también es útil!

Si desea varias líneas, puede usar la continuación automática del shell o usar argumentos separados:

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

Eso da una configuración de

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

y tiempos

sum(x)
min(y)

Si desea tener secuencias de comandos más largas, puede sentirse tentado a moverse timeitdentro de una secuencia de comandos de Python. Sugiero evitar eso porque el análisis y el tiempo son simplemente mejores en la línea de comando. En cambio, tiendo a hacer scripts de 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

Esto puede tardar un poco más debido a las múltiples inicializaciones, pero normalmente eso no es gran cosa.


Pero que pasa si quieres usartimeit dentro de tu módulo?

Bueno, la forma simple es hacer:

def function(...):
    ...

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

y eso te da acumulativo ( no mínimo!) para ejecutar ese número de veces.

Para obtener un buen análisis, use .repeaty tome el mínimo:

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

Normalmente debe combinar esto con en functools.partiallugar de lambda: ...reducir los gastos generales. Por lo tanto, podría tener algo como:

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

También puedes hacer:

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

lo que le daría algo más cercano a la interfaz desde la línea de comandos, pero de una manera mucho menos genial. Le "from __main__ import ..."permite usar el código de su módulo principal dentro del entorno artificial creado por timeit.

Vale la pena señalar que este es un envoltorio conveniente Timer(...).timeit(...)y, por lo tanto, no es particularmente bueno en el momento. Yo personalmente prefiero usarTimer(...).repeat(...) como he mostrado anteriormente.


Advertencias

Hay algunas advertencias timeitque se mantienen en todas partes.

  • Los gastos generales no se contabilizan. Digamos que quiere tiempo x += 1, para averiguar cuánto tiempo lleva la suma:

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

    Bueno, es que no 0,0476 mu s. Solo sabes que es menos que eso. Todo error es positivo.

    Así que trata de encontrar gastos generales puros :

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

    ¡Es un buen 30% de gastos generales solo por el tiempo! Esto puede sesgar masivamente los tiempos relativos. Pero sólo se preocupaba por las añadiendo tiempos; los tiempos de búsqueda xtambién deben incluirse en los gastos generales:

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

    La diferencia no es mucho mayor, pero está ahí.

  • Los métodos de mutación son peligrosos.

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

    ¡Pero eso está completamente mal! xes la lista vacía después de la primera iteración. Deberá reinicializar:

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

    Pero entonces tienes muchos gastos generales. Cuenta eso por separado.

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

    Tenga en cuenta que restar la sobrecarga es razonable aquí solo porque la sobrecarga es una fracción pequeña del tiempo.

    Para su ejemplo, vale la pena señalar que tanto el orden de inserción como el orden de tiempo tienen comportamientos de tiempo completamente inusuales para las listas ya ordenadas. Esto significa que necesitará un random.shuffletipo intermedio si desea evitar arruinar sus tiempos.

Veedrac
fuente
1
¿Qué significa usec? es microsegundos?
Hasan Iqbal
2
@HasanIqbalAnik Sí.
Veedrac
@StefanPochmann Porque no intenta muestrear varias veces.
Veedrac
Los lectores de esta respuesta también pueden estar interesados ​​en usar Python timeitde un programa pero que funciona de la misma manera que la línea de comandos. .
Graham el
@Veedrac Teniendo en cuenta su declaración sobre la resta de la sobrecarga de tiempo puro, timeitejecuta una passdeclaración cuando no se dan argumentos, lo que, por supuesto, lleva algún tiempo. Si no se da ningún argumento, passse no se ejecutan, por lo que resta de algunos 0.014usecs de cada momento sería incorrecto.
Arne
99

Si desea comparar dos bloques de código / funciones rápidamente, puede hacer lo siguiente:

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)
zzart
fuente
43

Creo que la forma más fácil de usar timeit es desde la línea de comandos:

Dado test.py :

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

tiempo de ejecución así:

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

Para mí, esta es la forma más rápida:

import timeit
def foo():
    print("here is my code to time...")


timeit.timeit(stmt=foo, number=1234567)
Rodrigo Laguna
fuente
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)
David Webb
fuente
7

Esto funciona muy bien:

  python -m timeit -c "$(cat file_name.py)"
Ohad Rubin
fuente
¿Cuál sería el equivalente de Windows?
Shailen
2
¿Cómo se pasan los parámetros, si el script requiere alguno?
Juuso Ohtonen
3

permite configurar el mismo diccionario en cada uno de los siguientes y probar el tiempo de ejecución.

El argumento de configuración es básicamente configurar el diccionario

El número es para ejecutar el código 1000000 veces. No es la configuración sino el stmt

Cuando ejecuta esto, puede ver que el índice es mucho más rápido que get. Puede ejecutarlo varias veces para ver.

El código básicamente trata de obtener el valor de c en el diccionario.

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

Aquí están mis resultados, los suyos serán diferentes.

por índice: 0.20900007452246427

por get: 0.54841166886888

Stryker
fuente
¿Qué versión de Python estás usando?
Eduardo
3

simplemente pase su código completo como argumento de tiempo:

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))
Sébastien Wieckowski
fuente
1
import timeit


def oct(x):
   return x*x


timeit.Timer("for x in range(100): oct(x)", "gc.enable()").timeit()
Yagmur SAHIN
fuente
¿Qué es gc.enable()?
Robin Andrews hace
0

El módulo timeit incorporado funciona mejor desde la línea de comandos de IPython.

Para cronometrar funciones desde un módulo:

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
ChaimG
fuente
0

Ejemplo de cómo usar el intérprete REPL de Python con una función que acepta parámetros.

>>> 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                                                                                        
Vlad Bezden
fuente
0

Crearía dos funciones y luego ejecutaría algo similar a esto. Tenga en cuenta que desea elegir el mismo número de ejecución / ejecución para comparar manzana con manzana.
Esto fue probado bajo Python 3.7.

ingrese la descripción de la imagen aquí Aquí está el código para facilitar 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")
grepit
fuente