2009-05-20 13 views
55

Estoy programando en python en Windows y me gustaría medir con precisión el tiempo que tarda una función en ejecutarse. He escrito una función "time_it" que toma otra función, la ejecuta y devuelve el tiempo que tardó en ejecutarse.Temporización precisa de funciones en python

def time_it(f, *args): 
    start = time.clock() 
    f(*args) 
    return (time.clock() - start)*1000 

llamo esto 1000 veces y promedió el resultado. (la constante 1000 al final es dar la respuesta en milisegundos.)

Esta función parece funcionar, pero tengo la sensación de que estoy haciendo algo mal, y que al hacerlo de esta manera estoy usando más tiempo que la función realmente utiliza cuando se está ejecutando.

¿Existe alguna forma más estándar o aceptada de hacerlo?

Cuando cambié mi función de prueba para llamar a una impresión de modo que demorara más, mi función time_it devuelve un promedio de 2.5 ms mientras que cProfile.run ('f()' devuelve una media de 7.0 ms. Pensé que mi función sobreestimaría el tiempo en todo caso, ¿qué está pasando aquí?

Una nota adicional, es el tiempo relativo de funciones comparadas entre sí que me importa, no el tiempo absoluto, ya que obviamente variará dependiendo del hardware y otros factores.

Respuesta

33

En lugar de escribir su propio código de perfiles, le sugiero que echa un vistazo a los perfiladores incorporados en Python (o profilecProfile, dependiendo de sus necesidades): http://docs.python.org/library/profile.html

+0

Ignorarme: esa cadena no es un nombre de función, es un bloque de código evaluado. Entonces puedes usarlo para un tiempo rápido. Esta es la respuesta correcta. Y en otras noticias, "no es" es significativamente más rápido que "! =" - pero puede tener otras implicaciones. –

+1

Y en esa tangente - antes de usar "no es" para cualquier cosa inteligente - tenga esto en cuenta - http://stackoverflow.com/questions/1392433/python-why-is-hello-is-hello –

65

Use la de la biblioteca estándar de Python.

Uso básico:

from timeit import Timer 

# first argument is the code to be run, the second "setup" argument is only run once, 
# and it not included in the execution time. 
t = Timer("""x.index(123)""", setup="""x = range(1000)""") 

print t.timeit() # prints float, for example 5.8254 
# ..or.. 
print t.timeit(1000) # repeat 1000 times instead of the default 1million 
+2

quiero mi función ser llamado con diferentes argumentos, sin embargo, cuando llamo t = timeit.Timer ("f()", "from ___main___ import f") con diferentes argumentos y ejecuto t.timeit (10000) de nuevo, obtengo los mismos resultados a través del diferentes argumentos deberían dar como resultado tiempos de ejecución muy diferentes. –

20

Este código es muy inexacta

total= 0 
for i in range(1000): 
    start= time.clock() 
    function() 
    end= time.clock() 
    total += end-start 
time= total/1000 

Este código es menos inexacta

start= time.clock() 
for i in range(1000): 
    function() 
end= time.clock() 
time= (end-start)/1000 

Los sufre muy inexactos de sesgo de medición si el tiempo de ejecución de la función está cerca de la precisión del reloj. La mayoría de los tiempos medidos son meramente números aleatorios entre 0 y unos pocos tics del reloj.

Dependiendo de la carga de trabajo de su sistema, el "tiempo" que observa desde una sola función puede ser totalmente un artefacto de la programación del sistema operativo y otros gastos generales incontrolables.

La segunda versión (menos imprecisa) tiene menos sesgo de medición. Si su función es realmente rápida, puede necesitar ejecutarla 10.000 veces para amortiguar la programación del sistema operativo y otros gastos generales.

Ambos son, por supuesto, terriblemente engañosos. El tiempo de ejecución para su programa, como un todo, no es la suma de los tiempos de ejecución de la función. Solo puedes usar los números para comparaciones relativas. No son medidas absolutas que transmiten mucho significado.

+1

¿Por qué/1000? El método time.clock() devuelve segundos como un valor de coma flotante. Si esperaba que retornara milisegundos, tendría sentido, sin embargo, dividir entre 1000 conversiones en kilosegundos, una unidad que nunca antes había visto. – pixelgrease

+0

@pixelgrease milli/1000 = micro, no kilo :) – stenci

+0

@stenci Sugiere que el valor resultante es en segundos, por ejemplo, 1000 segundos. Si lo divide por 1000, obtiene 1 "kilosegundo". – zehelvion

13

Si desea sincronizar un método de python, incluso si el bloque que mide puede arrojar, un buen enfoque es utilizar la instrucción with. Definir una clase Timer como

import time 

class Timer:  
    def __enter__(self): 
     self.start = time.clock() 
     return self 

    def __exit__(self, *args): 
     self.end = time.clock() 
     self.interval = self.end - self.start 

Entonces es posible que desee vez que un método de conexión que pueden lanzar.Use

import httplib 

with Timer() as t: 
    conn = httplib.HTTPConnection('google.com') 
    conn.request('GET', '/') 

print('Request took %.03f sec.' % t.interval) 

__exit()__ se llamará el método incluso si la solicitud de conexión falla. De manera más precisa, tendría que utiliza tryfinally para ver el resultado en caso de que lanza, al igual que con

try: 
    with Timer() as t: 
     conn = httplib.HTTPConnection('google.com') 
     conn.request('GET', '/') 
finally: 
    print('Request took %.03f sec.' % t.interval) 

More details here.

23

Puede crear un decorador "timeme" al igual que

import time             

def timeme(method): 
    def wrapper(*args, **kw): 
     startTime = int(round(time.time() * 1000)) 
     result = method(*args, **kw) 
     endTime = int(round(time.time() * 1000)) 

     print(endTime - startTime,'ms') 
     return result 

    return wrapper 

@timeme 
def func1(a,b,c = 'c',sleep = 1): 
    time.sleep(sleep) 
    print(a,b,c) 

func1('a','b','c',0) 
func1('a','b','c',0.5) 
func1('a','b','c',0.6) 
func1('a','b','c',1) 
+1

+ n para esta respuesta. Ojalá hubiera tal opción. ¡La belleza es que puedo exportar los resultados de registro a un archivo externo y agregar esto donde sea que lo necesite! Gracias un trillón. – curlyreggie

+5

Esto funciona muy bien para funciones no recursivas. Para las funciones recursivas, devuelve un tiempo para cada iteración de la función. – tachijuan

6

Esto es más limpio

from contextlib import contextmanager 

import time 
@contextmanager 
def timeblock(label): 
    start = time.clock() 
    try: 
     yield 
    finally: 
     end = time.clock() 
     print ('{} : {}'.format(label, end - start)) 



with timeblock("just a test"): 
      print "yippee" 
+0

Buena respuesta, simple y hasta el momento la única que he encontrado que le permite enviar una etiqueta a la función del temporizador. – Gabriel

+0

Solución ordenada pero un poco demasiado complicada. Pasé una hora tratando de descubrir por qué 'time.sleep (10)' solo tomó 0.002 segundos para ejecutarse de acuerdo con este código. (por cierto, gran diferencia entre 'time.clock()' y 'time.time()' en python) – swdev

4

Similar a la respuesta de @ AlexMartelli

import timeit 
timeit.timeit(fun, number=10000) 

puede hacer el truco.