Ver Mensaje Individual
  #1 (permalink)  
Antiguo 30/04/2014, 03:09
sukoy
 
Fecha de Ingreso: febrero-2011
Mensajes: 54
Antigüedad: 13 años, 2 meses
Puntos: 18
Apuntes sobre calculo de rendimiento.

En algunos casos nos puede interesar saber el tiempo que tarda en ejecutarse un programa o función y el consum de memoria que ocupa.

Tenemos por ejemplo la función:

Código Python:
Ver original
  1. def fibonacci(n):
  2.     if n < 3: return 1
  3.     else:
  4.         return fibonacci(n-2) + fibonacci(n-1)

Que calcula el número de fibonacci.

Para controlar el tiempo de ejecución bastaria con importar el módulo time y hacer un registro antes y otro después de su ejecución y calcular la diferencia.

Código Python:
Ver original
  1. import time
  2.  
  3. def func_time():
  4.     ti = time.time()
  5.     fibonacci(20)
  6.     tf = time.time()
  7.     print tf-ti
  8.    
  9. func_time()

Código:
0.00212001800537
Para hacer esto más práctico podemos hacer un decorador.
Un decorador es una función que recibe como parámetro otra función y devuelve el resultado de ésta modificado( decorado).

Código Python:
Ver original
  1. def timeit(func):
  2.     def timed(*args, **kw):
  3.         ti = time.time()
  4.         result = func(*args, **kw)
  5.         tf = time.time()
  6.         print('Time:{:.10f} sec'.format(tf-ti))
  7.         return result
  8.     return timed

Y lo aplicamos de esta forma:

Código Python:
Ver original
  1. @timeit
  2. def prueba():
  3.     return fibonacci(10)
  4. print prueba()

o bien:

Código Python:
Ver original
  1. @timeit
  2. def main(n):
  3.     def fibonacci(n):
  4.         if n < 3: return 1
  5.         else:
  6.             return fibonacci(n-2) + fibonacci(n-1)
  7.    
  8. print main(20)

En este caso al ser una función recursiva no podemos aplicar el decorador directamente a la función ya que nos daría un resultado para cada recursión. En caso de que no lo sea, sí podemos hacerlo.

Código Python:
Ver original
  1. @timeit
  2. def cuadrado(n):
  3.     return n**2
  4.  
  5. print cuadrado(20)

Código:
Time:0.0000021458 sec
400

Añadimos algunas cosas más: El proceso PID y el consumo de RAM con el módulo resource.
Código Python:
Ver original
  1. import resource
  2. import time
  3. import os
  4.  
  5. def timeit(func):
  6.     def timed(*args, **kw):
  7.         ts = time.time()
  8.         pid = os.getppid()
  9.         result = func(*args, **kw)
  10.         te = time.time()
  11.         maxmem = (resource.getrusage(resource.RUSAGE_SELF).ru_maxrss)/1000
  12.         info = (pid, func.__name__, te-ts, maxmem)
  13.         print('PID:{}\nName:{}\nTime:{:.10f} sec\nRAM:{} MB'.format(*info))
  14.         return result
  15.     return timed

Si guardamos este archivo como por ejemplo decotime.py en el path correspondiente. Podemos importarlo desde nuetros programas

Código Python:
Ver original
  1. import decotime
  2.  
  3. @decotime.timeit
  4. def prueba():
  5.     return fibonacci(35)
  6. print prueba()

Con lo que, sobre la misma función anterior para fibonacci(35) obtengo:

Código:
PID:8833
Name:prueba
Time:2.8051321507 sec
RAM:92 MB
9227465
fibonacci(40)

Código:
PID:8833
Name:prueba
Time:31.0330379009 sec
RAM:89 MB
102334155
Y una versión con memoización de fibonacci:
Código Python:
Ver original
  1. import decotime
  2.  
  3. @decotime.timeit
  4. def main(n):
  5.  
  6.     mem = {1:1,2:1}
  7.     def fibo(n):
  8.         if n < 3: return 1
  9.         if not n in mem:
  10.             mem[n] = fibo(n-1) + fibo(n-2)
  11.         return mem[n]
  12.    
  13. main(40)

Código:
PID:8833
Name:prueba
Time:0.0000319481 sec
RAM:96 MB
102334155
La diferencia es espectacular...

Saludos.

Pd, no he encontrado en la documentación de resource nada sobre las unidades que devuelve ru_maxrss asi que no sé si esto es cierto :
maxmem = (resource.getrusage(resource.RUSAGE_SELF).ru_maxrs s)/1000
- Es posible que algunas cosas solo funcionen en *nix.