¿Cómo puedo perfilar el código de Python línea por línea?

He estado usando cProfile para perfilar mi código, y ha funcionado muy bien. También utilizo gprof2dot.py para visualizar los resultados (lo hace un poco más claro).

Sin embargo, cProfile (y la mayoría de los demás perfiladores de Python que he visto hasta ahora) parecen solo perfilar en el nivel de llamada de función. Esto causa confusión cuando ciertas funciones se llaman desde diferentes lugares. No tengo idea si la llamada # 1 o # 2 está ocupando la mayoría del tiempo. Esto empeora aún más cuando la función en cuestión tiene seis niveles de profundidad, y se llama desde otros siete lugares.

¿Cómo obtengo un perfil línea por línea?

En lugar de esto:

function #12, total time: 2.0s 

Me gustaría ver algo como esto:

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

cProfile muestra cuánto del tiempo total se “transfiere” al padre, pero nuevamente esta conexión se pierde cuando tienes un montón de capas y llamadas interconectadas.

Idealmente, me encantaría tener una GUI que analice a través de los datos y luego me muestre mi archivo fuente con un tiempo total asignado a cada línea. Algo como esto:

 main.py: a = 1 # 0.0s result = func(a) # 0.4s c = 1000 # 0.0s result = func(c) # 5.0s 

Luego, podría hacer clic en la segunda llamada “func (c)” para ver el tiempo que tarda esa llamada, aparte de la llamada “func (a)”.

¿Tiene sentido? ¿Hay alguna biblioteca de perfiles que recostack este tipo de información? ¿Hay alguna herramienta increíble que me haya perdido?

Creo que para eso está destinado el line_profiler de Robert Kern . Desde el enlace:

 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 

¡Espero que ayude!

También puedes usar pprofile ( pypi ). Si desea perfilar toda la ejecución, no es necesario modificar el código fuente. También puede perfilar un subconjunto de un progtwig más grande de dos maneras:

  • alternar el perfil al alcanzar un punto específico en el código, como:

     import pprofile profiler = pprofile.Profile() with profiler: some_code # Process profile content: generate a cachegrind file and send it to user. 
  • alternar perfiles de forma asíncrona desde la stack de llamadas (requiere una forma de activar este código en una aplicación considerada, por ejemplo, un controlador de señales o un subproceso de trabajo disponible) mediante el uso de perfiles estadísticos:

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

El formato de salida de la anotación de código es muy parecido al perfilador de líneas:

 $ 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 

Tenga en cuenta que debido a que pprofile no se basa en la modificación del código, puede perfilar las declaraciones de los módulos de nivel superior, lo que permite perfilar el tiempo de inicio del progtwig (cuánto tiempo lleva importar módulos, inicializar globales, …).

Puede generar resultados en formato cachegrind, por lo que puede usar kcachegrind para explorar grandes resultados fácilmente.

Divulgación: Soy el autor del perfil.

PyVmMonitor tiene una vista en vivo que puede ayudarte allí (puedes conectarte a un progtwig en ejecución y obtener estadísticas de él).

Ver: http://www.pyvmmonitor.com/