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

116

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

Sin embargo, cProfile (y la mayoría de los otros perfiladores de Python que he visto hasta ahora) parecen solo perfilar en el nivel de llamada de función. Esto causa confusión cuando se llaman a ciertas funciones desde diferentes lugares; no tengo idea si la llamada n. ° 1 o la llamada n. ° 2 están ocupando la mayor parte del tiempo. Esto empeora aún más cuando la función en cuestión tiene seis niveles de profundidad, llamada 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 analizara los datos y luego me mostrara mi archivo fuente con el 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

Entonces podría hacer clic en la segunda llamada "func (c)" para ver qué está ocupando tiempo en esa llamada, separada de la llamada "func (a)".

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

rocketmonkeys
fuente
2
Supongo que te interesaría pstats.print_callers. Un ejemplo está aquí .
Muhammad Alkarouri
Muhammad, ¡eso es definitivamente útil! Al menos soluciona un problema: separar las llamadas a funciones según el origen. Creo que la respuesta de Joe Kington está más cerca de mi objetivo, pero print_callers () definitivamente me lleva a la mitad del camino. ¡Gracias!
rocketmonkeys

Respuestas:

120

Creo que para eso está destinado el perfil de línea 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!

Joe Kington
fuente
10
¿Line_profiler funciona con Python 3? No pude obtener ninguna información sobre eso.
user1251007
3
line_profiler no muestra visitas ni tiempo para mí. puede alguien decirme por que? ¿Y cómo solucionarlo?
I159
6
Aquí está el decorador que escribí: gist.github.com/kylegibson/6583590 . Si está realizando pruebas de nariz, asegúrese de utilizar la opción -s para que stdout se imprima inmediatamente.
Kyle Gibson
5
¿Cómo se ve el script de Python que produce esta salida? import line_profiler;y entonces ?
Zhubarb
10
¿Alguien puede mostrar cómo usar esta biblioteca? El archivo Léame enseña cómo instalar y responde a varias preguntas frecuentes, pero no menciona cómo usarlo después de una instalación de pip ..
cryanbhu
47

También puede usar pprofile ( pypi ). Si desea crear un perfil de la ejecución completa, no es necesario modificar el código fuente. También puede perfilar un subconjunto de un programa más grande de dos formas:

  • alternar la creación de perfiles al llegar a 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.
    
    # You can also write the result to the console:
    profiler.print_stats()
    
    # Or to a file:
    profiler.dump_stats("/tmp/profiler_stats.txt")
  • alternar la generación de perfiles de forma asíncrona desde la pila de llamadas (requiere una forma de activar este código en una aplicación considerada, por ejemplo, un controlador de señales o un hilo 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 depende de la modificación del código, puede perfilar declaraciones de módulo de nivel superior, lo que permite perfilar el tiempo de inicio del programa (cuánto tiempo lleva importar módulos, inicializar globales, ...).

Puede generar una salida con formato cachegrind, por lo que puede usar kcachegrind para buscar resultados grandes fácilmente.

Divulgación: soy el autor del perfil.

vpelletier
fuente
1
+1 Gracias por tu contribución. Se ve bien hecho. Tengo una perspectiva un poco diferente: uno de los objetivos es medir el tiempo inclusivo que toman las declaraciones y las funciones. Averiguar qué se puede hacer para acelerar el código es un objetivo diferente. La diferencia se vuelve dolorosamente obvia a medida que el código se agranda, como 10 ^ 6 líneas de código. El código puede estar perdiendo un gran porcentaje de tiempo. La forma en que lo encuentro es tomando una pequeña cantidad de muestras muy detalladas y examinándolas con un ojo humano, no resumiendo. El problema queda expuesto por la fracción de tiempo que pierde.
Mike Dunlavey
1
Tiene razón, no mencioné el uso de pprofile cuando se quiere perfilar un subconjunto más pequeño. Edité mi publicación para agregar ejemplos de esto.
vpelletier
3
Esto es exactamente lo que estaba buscando: no intrusivo y extenso.
egpbos
1
Buena herramienta, pero funciona varias veces más lento que el código original.
Rominf
4

Puede tomar la ayuda del paquete line_profiler para esto

1. Primero instale el paquete:

    pip install line_profiler

2. Use el comando mágico para cargar el paquete en su entorno python / notebook

    %load_ext line_profiler

3. Si desea perfilar los códigos para una función,
haga lo siguiente:

    %lprun -f demo_func demo_func(arg1, arg2)

obtendrá una buena salida formateada con todos los detalles si sigue estos pasos :)

Line #      Hits      Time    Per Hit   % Time  Line Contents
 1                                           def demo_func(a,b):
 2         1        248.0    248.0     64.8      print(a+b)
 3         1         40.0     40.0     10.4      print(a)
 4         1         94.0     94.0     24.5      print(a*b)
 5         1          1.0      1.0      0.3      return a/b
Sateesh
fuente
4

Solo para mejorar la respuesta mencionada anteriormente de @Joe Kington .

Para Python 3.x , use line_profiler :


Instalación:

pip install line_profiler

Uso:

Suponga que tiene el programa main.pyy dentro de él, funciones fun_a()y fun_b()que desea perfilar con respecto al tiempo; deberá utilizar el decorador @profilejusto antes de las definiciones de funciones. Por ejemplo,

@profile
def fun_a():
    #do something

@profile
def fun_b():
    #do something more

if __name__ == '__main__':
    fun_a()
    fun_b()

El programa puede perfilarse ejecutando el comando de shell:

$ kernprof -l -v main.py

Los argumentos se pueden recuperar usando $ kernprof -h

Usage: kernprof [-s setupfile] [-o output_file_path] scriptfile [arg] ...

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -l, --line-by-line    Use the line-by-line profiler from the line_profiler
                        module instead of Profile. Implies --builtin.
  -b, --builtin         Put 'profile' in the builtins. Use 'profile.enable()'
                        and 'profile.disable()' in your code to turn it on and
                        off, or '@profile' to decorate a single function, or
                        'with profile:' to profile a single section of code.
  -o OUTFILE, --outfile=OUTFILE
                        Save stats to <outfile>
  -s SETUP, --setup=SETUP
                        Code to execute before the code to profile
  -v, --view            View the results of the profile in addition to saving
                        it.

Los resultados se imprimirán en la consola como:

Total time: 17.6699 s
File: main.py
Function: fun_a at line 5

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    5                                           @profile
    6                                           def fun_a():
...

EDITAR: Los resultados de los perfiladores se pueden analizar utilizando el paquete TAMPPA . Usándolo, podemos obtener gráficos deseados línea por línea como trama

Pe Dro
fuente
1

PyVmMonitor tiene una vista en vivo que puede ayudarlo allí (puede conectarse a un programa en ejecución y obtener estadísticas de él).

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

Fabio Zadrozny
fuente