Comment utiliser cProfile pour profiler le code Python

Python n'est peut-être pas le langage le plus rapide, mais il est souvent assez rapide. Et Python est idéal lorsque le temps du programmeur compte plus que le temps CPU.

Cela dit, si une application Python donnée est lente, vous n'êtes pas obligé de la sucer. Les outils inclus avec l'installation standard de l'interpréteur Python peuvent vous fournir des informations détaillées sur les parties de votre programme qui sont lentes et vous donner des conseils sur la façon de les accélérer.

Comment utiliser cProfile

Le cProfilemodule rassemble des statistiques sur le temps d'exécution d'un programme Python. Il peut rendre compte de tout, de l'application entière à une seule instruction ou expression.

Voici un exemple de jouet d'utilisation cProfile:

def add (x, y): x + = str (y) return x def add_2 (x, y): if y% 20000 == 0: z = [] pour q dans la plage (0,400000): z.append ( q) def main (): a = [] pour n dans la plage (0,200000): add (a, n) add_2 (a, n) if __name__ == '__main__': import cProfile cProfile.run ('main ( ) ') 

Cet exemple exécute la fonction de l'application main()et analyse les performances main()et tout les main()appels. Il est également possible d'analyser seulement une  partie d'un programme, mais l'utilisation la plus courante pour les démarreurs est de profiler l'ensemble du programme.

Exécutez l'exemple ci-dessus et vous serez accueilli avec quelque chose comme la sortie suivante:

Ce qui est montré ici est une liste de tous les appels de fonction effectués par le programme, ainsi que des statistiques sur chacun:

  • En haut (première ligne en bleu), on voit le nombre total d'appels effectués dans le programme profilé et le temps total d'exécution. Vous pouvez également voir un chiffre pour les «appels primitifs», c'est -à-dire les appels non récursifs , ou les appels effectués directement à une fonction qui, à son tour, ne s'appellent pas plus bas dans la pile d'appels.
  • ncalls : nombre d'appels effectués. Si vous voyez deux nombres séparés par une barre oblique, le deuxième nombre est le nombre d'appels primitifs pour cette fonction.
  • tottime : temps total passé dans la fonction, sans compter les appels à d'autres fonctions.
  • percall : temps moyen par appel pour le temps , calculé en prenant le temps et en le divisant par ncalls .
  • cumtime : temps total passé dans la fonction, y compris les appels à d'autres fonctions.
  • percall (# 2): Temps moyen par appel pour cumtime ( cumtime divisé par ncalls ).
  • filename: lineno : nom de fichier, numéro de ligne et nom de fonction de l'appel en question.

Comment modifier les rapports cProfile

Par défaut, cProfiletrie sa sortie par «nom standard», ce qui signifie qu'il trie par le texte dans la colonne la plus à droite (nom de fichier, numéro de ligne, etc.).

Le format par défaut est utile si vous souhaitez un rapport général de haut en bas de chaque appel de fonction pour référence. Mais si vous essayez d'aller au fond d'un goulot d'étranglement, vous voudrez probablement que les parties du programme les plus chronophages soient répertoriées en premier.

Nous pouvons produire ces résultats en invoquant  cProfile un peu différemment. Notez comment la partie inférieure du programme ci-dessus peut être retravaillée pour trier les statistiques par une colonne différente (dans ce cas ncalls):

if __name__ == '__main__': import cProfile, pstats profiler = cProfile.Profile () profiler.enable () main () profiler.disable () stats = pstats.Stats (profiler) .sort_stats ('ncalls') stats.print_stats () 

Les résultats ressembleront à ceci:

Voici comment tout cela fonctionne:

  • Au lieu d'exécuter une commande par l' intermédiaire de cProfile.run(), ce qui est très flexible, nous créons un profilage objet , profiler.
  • Lorsque nous voulons profiler une action, nous appelons d'abord .enable()l'instance d'objet du profileur, puis exécutons l'action, puis appelons .disable(). (C'est une façon de profiler uniquement une partie d'un programme.)
  • Le pstatsmodule est utilisé pour manipuler les résultats collectés par l'objet profileur et imprimer ces résultats.

La combinaison d'un objet profileur et pstatsnous permet de manipuler les données de profil capturées - par exemple, pour trier différemment les statistiques générées. Dans cet exemple, using .sort_stats('ncalls')trie les statistiques par ncallscolonne. D'autres options de tri sont disponibles.

Comment utiliser les résultats de cProfile pour l'optimisation

Les options de tri disponibles pour la cProfile sortie nous permettent de déceler les goulots d'étranglement potentiels des performances dans un programme.

ncalls

La première et la plus importante information avec cProfilelaquelle vous pouvez trouver est les fonctions les plus fréquemment appelées, via la ncallscolonne.

En Python, le simple fait de faire un appel de fonction entraîne une surcharge relativement importante. Si une fonction est appelée à plusieurs reprises dans une boucle serrée, même si ce n'est pas une fonction de longue durée, cela aura un impact sur les performances.

Dans l'exemple ci-dessus, la fonction add(et la fonction add_2) est appelée à plusieurs reprises dans une boucle. Déplacer la boucle dans la addfonction elle-même, ou insérer addentièrement la fonction, résoudrait ce problème.

tottime

Une autre statistique utile détaille les fonctions que le programme passe la plupart de son temps à exécuter, par le biais de la tottimecolonne.

Dans l'exemple ci-dessus, la add_2fonction utilise une boucle pour simuler un calcul coûteux, qui pousse son tottimescore vers le haut. Toute fonction avec un tottimescore élevé mérite un examen attentif, surtout si elle est appelée plusieurs fois ou en boucle serrée.

Notez que vous devez toujours tenir compte du contexte  dans lequel la fonction est utilisée. Si une fonction a une valeur élevée tottimemais n'est appelée qu'une seule fois - par exemple, uniquement au démarrage du programme - il est moins probable que ce soit un goulot d'étranglement. Cependant, si vous essayez de réduire le temps de démarrage, vous voudrez savoir si une fonction appelée au démarrage fait attendre tout le reste.

Comment exporter des données cProfile

Si vous souhaitez utiliser cProfileles statistiques générées par des méthodes plus avancées, vous pouvez les exporter vers un fichier de données:

stats = pstats.Stats (profileur) stats.dump_stats ('/ chemin / vers / stats_file.dat') 

Ce fichier peut être relu en utilisant le pstatsmodule, puis trié ou affiché avec pstats. Les données peuvent également être réutilisées par d'autres programmes. Deux exemples:

  • pyprof2calltreerend des visualisations détaillées du graphique d'appel du programme et des statistiques d'utilisation à partir des données de profil. Cet article fournit un exemple détaillé de son utilisation dans le monde réel.
  • snakevizgénère également des visualisations à partir de cProfiledonnées, mais utilise une représentation différente pour les données - un graphique «sunburst» plutôt que «flamme» de pyprof2calltree.

Au-delà de cProfile pour le profilage Python

cProfilen'est pas le seul moyen de profiler une application Python. cProfileest certainement l'un des moyens les plus pratiques, étant donné qu'il est fourni avec Python. Mais d'autres méritent l'attention.

Un projet py-spy,, crée un profil pour une application Python en échantillonnant son activité d'appel. py-spypeut être utilisé pour examiner une application Python en cours d'exécution sans avoir à l'arrêter et à la redémarrer, et sans avoir à modifier sa base de code, afin qu'elle puisse être utilisée pour profiler les applications déployées. py-spygénère également des statistiques sur la surcharge encourue par le runtime Python (par exemple, la surcharge du garbage collection), ce qui cProfilen'est pas le cas.