web-dev-qa-db-fra.com

Comment puis-je profiler Python code ligne par ligne?

J'utilise cProfile pour profiler mon code, et cela fonctionne très bien. J'utilise également gprof2dot.py pour visualiser les résultats (cela est un peu plus clair).

Cependant, cProfile (et la plupart des autres Python profileurs que j'ai vus jusqu'à présent) ne semblent profiler qu'au niveau de l'appel de fonction. Cela crée de la confusion lorsque certaines fonctions sont appelées à partir de différents endroits - j'ai aucune idée si l'appel n ° 1 ou l'appel n ° 2 prend la majorité du temps, ce qui est encore pire lorsque la fonction en question a six niveaux de profondeur, appelés depuis sept autres endroits.

Comment obtenir un profilage ligne par ligne?

Au lieu de cela:

function #12, total time: 2.0s

J'aimerais voir quelque chose comme ça:

function #12 (called from somefile.py:102) 0.5s
function #12 (called from main.py:12) 1.5s

cProfile montre combien de temps total "transfère" au parent, mais encore une fois cette connexion est perdue lorsque vous avez un tas de couches et d'appels interconnectés.

Idéalement, j'aimerais avoir une interface graphique qui analyserait les données, puis me montrerait mon fichier source avec un temps total donné à chaque ligne. Quelque chose comme ça:

main.py:

a = 1 # 0.0s
result = func(a) # 0.4s
c = 1000 # 0.0s
result = func(c) # 5.0s

Ensuite, je serais en mesure de cliquer sur le deuxième appel "func (c)" pour voir ce qui prend du temps dans cet appel, distinct de l'appel "func (a)".

Cela a-t-il du sens? Existe-t-il une bibliothèque de profilage qui collecte ce type d'informations? Y a-t-il un outil génial que j'ai manqué?

103
rocketmonkeys

Je crois que c'est à cela que line_profiler de Robert Kern est destiné. Depuis le lien:

File: pystone.py
Function: Proc2 at line 149
Total time: 0.606656 s

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
   149                                           @profile
   150                                           def Proc2(IntParIO):
   151     50000        82003      1.6     13.5      IntLoc = IntParIO + 10
   152     50000        63162      1.3     10.4      while 1:
   153     50000        69065      1.4     11.4          if Char1Glob == 'A':
   154     50000        66354      1.3     10.9              IntLoc = IntLoc - 1
   155     50000        67263      1.3     11.1              IntParIO = IntLoc - IntGlob
   156     50000        65494      1.3     10.8              EnumLoc = Ident1
   157     50000        68001      1.4     11.2          if EnumLoc == Ident1:
   158     50000        63739      1.3     10.5              break
   159     50000        61575      1.2     10.1      return IntParIO

J'espère que ça t'as aidé!

110
Joe Kington

Vous pouvez également utiliser pprofile ( pypi ). Si vous souhaitez profiler l'exécution entière, elle ne nécessite pas de modification du code source. Vous pouvez également profiler un sous-ensemble d'un programme plus vaste de deux manières:

  • basculer le profilage lorsque vous atteignez un point spécifique dans le code, tel que:

    import pprofile
    profiler = pprofile.Profile()
    with profiler:
        some_code
    # Process profile content: generate a cachegrind file and send it to user.
    
  • basculer le profilage de manière asynchrone à partir de la pile d'appels (nécessite un moyen de déclencher ce code dans l'application considérée, par exemple un gestionnaire de signal ou un thread de travail disponible) en utilisant le profilage statistique:

    import pprofile
    profiler = pprofile.StatisticalProfile()
    statistical_profiler_thread = pprofile.StatisticalThread(
        profiler=profiler,
    )
    with statistical_profiler_thread:
        sleep(n)
    # Likewise, process profile content
    

Le format de sortie des annotations de code ressemble beaucoup au profileur de lignes:

$ pprofile --threads 0 demo/threads.py
Command line: ['demo/threads.py']
Total duration: 1.00573s
File: demo/threads.py
File duration: 1.00168s (99.60%)
Line #|      Hits|         Time| Time per hit|      %|Source code
------+----------+-------------+-------------+-------+-----------
     1|         2|  3.21865e-05|  1.60933e-05|  0.00%|import threading
     2|         1|  5.96046e-06|  5.96046e-06|  0.00%|import time
     3|         0|            0|            0|  0.00%|
     4|         2|   1.5974e-05|  7.98702e-06|  0.00%|def func():
     5|         1|      1.00111|      1.00111| 99.54%|  time.sleep(1)
     6|         0|            0|            0|  0.00%|
     7|         2|  2.00272e-05|  1.00136e-05|  0.00%|def func2():
     8|         1|  1.69277e-05|  1.69277e-05|  0.00%|  pass
     9|         0|            0|            0|  0.00%|
    10|         1|  1.81198e-05|  1.81198e-05|  0.00%|t1 = threading.Thread(target=func)
(call)|         1|  0.000610828|  0.000610828|  0.06%|# /usr/lib/python2.7/threading.py:436 __init__
    11|         1|  1.52588e-05|  1.52588e-05|  0.00%|t2 = threading.Thread(target=func)
(call)|         1|  0.000438929|  0.000438929|  0.04%|# /usr/lib/python2.7/threading.py:436 __init__
    12|         1|  4.79221e-05|  4.79221e-05|  0.00%|t1.start()
(call)|         1|  0.000843048|  0.000843048|  0.08%|# /usr/lib/python2.7/threading.py:485 start
    13|         1|  6.48499e-05|  6.48499e-05|  0.01%|t2.start()
(call)|         1|   0.00115609|   0.00115609|  0.11%|# /usr/lib/python2.7/threading.py:485 start
    14|         1|  0.000205994|  0.000205994|  0.02%|(func(), func2())
(call)|         1|      1.00112|      1.00112| 99.54%|# demo/threads.py:4 func
(call)|         1|  3.09944e-05|  3.09944e-05|  0.00%|# demo/threads.py:7 func2
    15|         1|  7.62939e-05|  7.62939e-05|  0.01%|t1.join()
(call)|         1|  0.000423908|  0.000423908|  0.04%|# /usr/lib/python2.7/threading.py:653 join
    16|         1|  5.26905e-05|  5.26905e-05|  0.01%|t2.join()
(call)|         1|  0.000320196|  0.000320196|  0.03%|# /usr/lib/python2.7/threading.py:653 join

Notez que parce que pprofile ne repose pas sur la modification de code, il peut profiler les instructions de module de niveau supérieur, ce qui permet de profiler le temps de démarrage du programme (combien de temps il faut pour importer des modules, initialiser des globaux, ...).

Il peut générer une sortie au format cachegrind, vous pouvez donc utiliser kcachegrind pour parcourir facilement les grands résultats.

Divulgation: je suis l'auteur du profil.

36
vpelletier

PyVmMonitor a une vue en direct qui peut vous y aider (vous pouvez vous connecter à un programme en cours d'exécution et en obtenir des statistiques).

Voir: http://www.pyvmmonitor.com/

1
Fabio Zadrozny

Vous pouvez utiliser l'aide de line_profiler package pour cela

1. Installez d'abord le paquet:

    pip install line_profiler

2. Utilisez la commande magique pour charger le paquet dans votre environnement python/notebook

    %load_ext line_profiler

. Si vous souhaitez profiler les codes d'une fonction,
procédez comme suit:
% lprun -f nom_fonction appel_fonction

    %lprun -f function_defined_by_you function_defined_by_you(arg1, arg2)

VOUS OBTIENDREZ UNE SORTIE FORMATÉE AVEC TOUS LES DÉTAILS SI VOUS SUIVEZ LES ÉTAPES CI-DESSUS

1
Sateesh