Evaluación comparativa y elaboración de perfiles

En este capítulo, aprenderemos cómo la evaluación comparativa y la elaboración de perfiles ayudan a abordar los problemas de rendimiento.

Supongamos que hemos escrito un código y también está dando el resultado deseado, pero ¿qué pasa si queremos ejecutar este código un poco más rápido porque las necesidades han cambiado? En este caso, necesitamos averiguar qué partes de nuestro código ralentizan todo el programa. En este caso, la evaluación comparativa y la elaboración de perfiles pueden resultar útiles.

¿Qué es el Benchmarking?

La evaluación comparativa tiene como objetivo evaluar algo en comparación con un estándar. Sin embargo, la pregunta que surge aquí es cuál sería el benchmarking y por qué lo necesitamos en el caso de la programación de software. La evaluación comparativa del código significa qué tan rápido se ejecuta el código y dónde está el cuello de botella. Una de las principales razones de la evaluación comparativa es que optimiza el código.

¿Cómo funciona la evaluación comparativa?

Si hablamos del funcionamiento de la evaluación comparativa, debemos comenzar comparando todo el programa como un estado actual, luego podemos combinar micro evaluaciones comparativas y luego descomponer un programa en programas más pequeños. Para encontrar los cuellos de botella dentro de nuestro programa y optimizarlo. En otras palabras, podemos entenderlo como dividir el gran y difícil problema en una serie de problemas más pequeños y un poco más fáciles para optimizarlos.

Módulo de Python para evaluación comparativa

En Python, tenemos un módulo predeterminado para evaluación comparativa que se llama timeit. Con la ayuda deltimeit módulo, podemos medir el rendimiento de una pequeña parte del código Python dentro de nuestro programa principal.

Ejemplo

En la siguiente secuencia de comandos de Python, estamos importando el timeit módulo, que mide además el tiempo necesario para ejecutar dos funciones: functionA y functionB -

import timeit
import time
def functionA():
   print("Function A starts the execution:")
   print("Function A completes the execution:")
def functionB():
   print("Function B starts the execution")
   print("Function B completes the execution")
start_time = timeit.default_timer()
functionA()
print(timeit.default_timer() - start_time)
start_time = timeit.default_timer()
functionB()
print(timeit.default_timer() - start_time)

Después de ejecutar el script anterior, obtendremos el tiempo de ejecución de ambas funciones como se muestra a continuación.

Salida

Function A starts the execution:
Function A completes the execution:
0.0014599495514175942
Function B starts the execution
Function B completes the execution
0.0017024724827479076

Escribiendo nuestro propio temporizador usando la función decoradora

En Python, podemos crear nuestro propio temporizador, que actuará como el timeitmódulo. Se puede hacer con la ayuda deldecoratorfunción. A continuación se muestra un ejemplo del temporizador personalizado:

import random
import time

def timer_func(func):

   def function_timer(*args, **kwargs):
   start = time.time()
   value = func(*args, **kwargs)
   end = time.time()
   runtime = end - start
   msg = "{func} took {time} seconds to complete its execution."
      print(msg.format(func = func.__name__,time = runtime))
   return value
   return function_timer

@timer_func
def Myfunction():
   for x in range(5):
   sleep_time = random.choice(range(1,3))
   time.sleep(sleep_time)

if __name__ == '__main__':
   Myfunction()

El script de Python anterior ayuda a importar módulos de tiempo aleatorios. Hemos creado la función decoradora timer_func (). Este tiene la función function_timer () dentro. Ahora, la función anidada tomará el tiempo antes de llamar a la función pasada. Luego, espera a que la función regrese y toma la hora de finalización. De esta manera, finalmente podemos hacer que el script de Python imprima el tiempo de ejecución. El script generará la salida como se muestra a continuación.

Salida

Myfunction took 8.000457763671875 seconds to complete its execution.

¿Qué es la elaboración de perfiles?

A veces, el programador desea medir algunos atributos como el uso de la memoria, la complejidad del tiempo o el uso de instrucciones particulares sobre los programas para medir la capacidad real de ese programa. Este tipo de medición sobre el programa se denomina elaboración de perfiles. La creación de perfiles utiliza un análisis de programa dinámico para realizar dicha medición.

En las secciones siguientes, aprenderemos sobre los diferentes módulos de Python para la creación de perfiles.

cProfile - el módulo incorporado

cProfilees un módulo integrado de Python para la creación de perfiles. El módulo es una extensión C con una sobrecarga razonable que lo hace adecuado para perfilar programas de larga duración. Después de ejecutarlo, registra todas las funciones y tiempos de ejecución. Es muy poderoso, pero a veces un poco difícil de interpretar y actuar. En el siguiente ejemplo, estamos usando cProfile en el siguiente código:

Ejemplo

def increment_global():

   global x
   x += 1

def taskofThread(lock):

   for _ in range(50000):
   lock.acquire()
   increment_global()
   lock.release()

def main():
   global x
   x = 0

   lock = threading.Lock()

   t1 = threading.Thread(target=taskofThread, args=(lock,))
   t2 = threading.Thread(target= taskofThread, args=(lock,))

   t1.start()
   t2.start()

   t1.join()
   t2.join()

if __name__ == "__main__":
   for i in range(5):
      main()
   print("x = {1} after Iteration {0}".format(i,x))

El código anterior se guarda en el thread_increment.pyarchivo. Ahora, ejecute el código con cProfile en la línea de comando de la siguiente manera:

(base) D:\ProgramData>python -m cProfile thread_increment.py
x = 100000 after Iteration 0
x = 100000 after Iteration 1
x = 100000 after Iteration 2
x = 100000 after Iteration 3
x = 100000 after Iteration 4
      3577 function calls (3522 primitive calls) in 1.688 seconds

   Ordered by: standard name

   ncalls tottime percall cumtime percall filename:lineno(function)

   5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:103(release)
   5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:143(__init__)
   5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:147(__enter__)
   … … … …

De la salida anterior, está claro que cProfile imprime todas las 3577 funciones llamadas, con el tiempo invertido en cada una y el número de veces que se han llamado. Las siguientes son las columnas que obtuvimos en la salida:

  • ncalls - Es el número de llamadas realizadas.

  • tottime - Es el tiempo total empleado en la función dada.

  • percall - Se refiere al cociente de tottime dividido por ncalls.

  • cumtime- Es el tiempo acumulado empleado en esta y todas las subfunciones. Incluso es preciso para funciones recursivas.

  • percall - Es el cociente de tiempo acumulado dividido por llamadas primitivas.

  • filename:lineno(function) - Básicamente proporciona los datos respectivos de cada función.