Analyse comparative et profilage

Dans ce chapitre, nous allons découvrir comment l'analyse comparative et le profilage aident à résoudre les problèmes de performances.

Supposons que nous ayons écrit un code et qu'il donne également le résultat souhaité, mais que se passe-t-il si nous voulons exécuter ce code un peu plus rapidement car les besoins ont changé. Dans ce cas, nous devons découvrir quelles parties de notre code ralentissent l'ensemble du programme. Dans ce cas, l'analyse comparative et le profilage peuvent être utiles.

Qu'est-ce que l'analyse comparative?

Le benchmarking vise à évaluer quelque chose par rapport à une norme. Cependant, la question qui se pose ici est de savoir quel serait le benchmarking et pourquoi nous en avons besoin en cas de programmation logicielle. L'analyse comparative du code signifie à quelle vitesse le code s'exécute et où se trouve le goulot d'étranglement. L'une des principales raisons de l'analyse comparative est qu'elle optimise le code.

Comment fonctionne l'analyse comparative?

Si nous parlons du fonctionnement de l'analyse comparative, nous devons commencer par comparer l'ensemble du programme comme un état actuel, puis nous pouvons combiner des micro-points de référence et décomposer un programme en programmes plus petits. Afin de trouver les goulots d'étranglement au sein de notre programme et de l'optimiser. En d'autres termes, nous pouvons le comprendre comme divisant le gros et difficile problème en une série de problèmes plus petits et un peu plus faciles pour les optimiser.

Module Python pour l'analyse comparative

En Python, nous avons un module par défaut pour l'analyse comparative qui s'appelle timeit. Avec l'aide dutimeit module, nous pouvons mesurer les performances d'un petit morceau de code Python dans notre programme principal.

Exemple

Dans le script Python suivant, nous importons le timeit module, qui mesure en outre le temps nécessaire pour exécuter deux fonctions - functionA et 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)

Après avoir exécuté le script ci-dessus, nous obtiendrons le temps d'exécution des deux fonctions comme indiqué ci-dessous.

Production

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

Écrire notre propre minuterie à l'aide de la fonction décorateur

En Python, nous pouvons créer notre propre minuterie, qui agira comme le timeitmodule. Cela peut être fait avec l'aide dudecoratorfonction. Voici un exemple de minuterie personnalisée -

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

Le script python ci-dessus aide à importer des modules de temps aléatoires. Nous avons créé la fonction décoratrice timer_func (). Cela a la fonction function_timer () à l'intérieur. Maintenant, la fonction imbriquée saisira le temps avant d'appeler la fonction passée. Ensuite, il attend le retour de la fonction et saisit l'heure de fin. De cette façon, nous pouvons enfin faire en sorte que le script python affiche l'heure d'exécution. Le script générera la sortie comme indiqué ci-dessous.

Production

Myfunction took 8.000457763671875 seconds to complete its execution.

Qu'est-ce que le profilage?

Parfois, le programmeur veut mesurer certains attributs comme l'utilisation de la mémoire, la complexité du temps ou l'utilisation d'instructions particulières sur les programmes pour mesurer la capacité réelle de ce programme. Ce type de mesure du programme s'appelle le profilage. Le profilage utilise une analyse de programme dynamique pour effectuer une telle mesure.

Dans les sections suivantes, nous découvrirons les différents modules Python pour le profilage.

cProfile - le module intégré

cProfileest un module intégré Python pour le profilage. Le module est une extension C avec une surcharge raisonnable qui le rend approprié pour le profilage de programmes de longue durée. Après l'avoir exécuté, il enregistre toutes les fonctions et les temps d'exécution. C'est très puissant mais parfois un peu difficile à interpréter et à appliquer. Dans l'exemple suivant, nous utilisons cProfile sur le code ci-dessous -

Exemple

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

Le code ci-dessus est enregistré dans le thread_increment.pyfichier. Maintenant, exécutez le code avec cProfile sur la ligne de commande comme suit -

(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 sortie ci-dessus, il est clair que cProfile imprime toutes les 3577 fonctions appelées, avec le temps passé dans chacune et le nombre de fois qu'elles ont été appelées. Voici les colonnes que nous avons reçues en sortie -

  • ncalls - C'est le nombre d'appels effectués.

  • tottime - C'est le temps total passé dans la fonction donnée.

  • percall - Il fait référence au quotient du temps de temps divisé par ncalls.

  • cumtime- C'est le temps cumulé passé dans cette fonction et dans toutes les sous-fonctions. Il est même précis pour les fonctions récursives.

  • percall - C'est le quotient cumtime divisé par les appels primitifs.

  • filename:lineno(function) - Il fournit essentiellement les données respectives de chaque fonction.