Come posso profilare il codice Python riga per riga?


116

Sto usando cProfile per profilare il mio codice e funziona alla grande. Uso anche gprof2dot.py per visualizzare i risultati (lo rende un po 'più chiaro).

Tuttavia, cProfile (e la maggior parte degli altri profiler Python che ho visto finora) sembrano profilare solo a livello di chiamata di funzione. Ciò causa confusione quando determinate funzioni vengono chiamate da luoghi diversi: non ho idea se la chiamata # 1 o la chiamata # 2 stia occupando la maggior parte del tempo. La situazione peggiora ulteriormente quando la funzione in questione è profonda sei livelli, richiamata da altri sette punti.

Come si ottiene una profilazione riga per riga?

Invece di questo:

function #12, total time: 2.0s

Mi piacerebbe vedere qualcosa di simile:

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

cProfile mostra quanto del tempo totale si "trasferisce" al genitore, ma ancora una volta questa connessione viene persa quando si hanno un mucchio di livelli e chiamate interconnesse.

Idealmente, mi piacerebbe avere una GUI che analizzi i dati, quindi mi mostri il mio file sorgente con un tempo totale assegnato a ciascuna riga. Qualcosa come questo:

main.py:

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

Quindi sarei in grado di fare clic sulla seconda chiamata "func (c)" per vedere cosa sta richiedendo tempo in quella chiamata, separatamente dalla chiamata "func (a)".

Ha senso? Esiste una libreria di profili che raccolga questo tipo di informazioni? C'è qualche strumento fantastico che mi sono perso?


2
La mia ipotesi è che ti interesserebbe pstats.print_callers. Un esempio è qui .
Muhammad Alkarouri

Muhammad, è decisamente utile! Almeno risolve un problema: separare le chiamate di funzione a seconda dell'origine. Penso che la risposta di Joe Kington sia più vicina al mio obiettivo, ma print_callers () mi porta decisamente a metà strada. Grazie!
rocketmonkeys

Risposte:


120

Credo che questo sia ciò a cui è destinato line_profiler di Robert Kern . Dal link:

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

Spero che aiuti!


10
Line_profiler funziona con Python 3? Non ho potuto ottenere alcuna informazione su questo.
user1251007

3
line_profiler non mostra gli hit e il tempo per me. Qualcuno può dirmi perché? E come risolverlo?
I159

6
Ecco il decoratore che ho scritto: gist.github.com/kylegibson/6583590 . Se stai eseguendo nosetest, assicurati di usare l'opzione -s in modo che stdout venga stampato immediatamente.
Kyle Gibson,

5
come appare lo script python che produce questo output? import line_profiler;e poi ?
Zhubarb

10
qualcuno può mostrare come utilizzare effettivamente questa libreria? Il file readme insegna come installare e risponde a varie domande frequenti, ma non menziona come usarlo dopo l'installazione di un pip ..
cryanbhu

47

Puoi anche usare pprofile ( pypi ). Se si desidera profilare l'intera esecuzione, non è necessaria la modifica del codice sorgente. Puoi anche profilare un sottoinsieme di un programma più grande in due modi:

  • attivare / disattivare la profilazione quando si raggiunge un punto specifico del codice, come ad esempio:

    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")
  • attivare / disattivare la profilazione in modo asincrono dallo stack di chiamate (richiede un modo per attivare questo codice nell'applicazione considerata, ad esempio un gestore di segnali o un thread di lavoro disponibile) utilizzando la profilazione statistica:

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

Il formato di output dell'annotazione del codice è molto simile al profiler di riga:

$ 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

Si noti che poiché pprofile non si basa sulla modifica del codice, può profilare le istruzioni del modulo di primo livello, consentendo di profilare il tempo di avvio del programma (quanto tempo ci vuole per importare i moduli, inizializzare le globali, ...).

Può generare output in formato cachegrind, quindi puoi usare kcachegrind per sfogliare facilmente risultati di grandi dimensioni.

Divulgazione: sono un autore del profilo.


1
+1 Grazie per il tuo contributo. Sembra ben fatto. Ho una prospettiva leggermente diversa: misurare il tempo inclusivo impiegato da dichiarazioni e funzioni è un obiettivo. Scoprire cosa si può fare per rendere il codice più veloce è un obiettivo diverso. La differenza diventa dolorosamente ovvia quando il codice diventa grande, come 10 ^ 6 righe di codice. Il codice può far perdere grandi percentuali di tempo. Il modo in cui lo trovo è prendendo un piccolo numero di campioni molto dettagliati ed esaminandoli con un occhio umano, senza riassumere. Il problema è esposto dalla frazione di tempo che spreca.
Mike Dunlavey

1
Hai ragione, non ho menzionato l'utilizzo di pprofile quando si vuole profilare un sottoinsieme più piccolo. Ho modificato il mio post per aggiungere esempi di questo.
vpelletier

3
Questo è esattamente quello che stavo cercando: non invadente e ampio.
egpbos

1
Bello strumento, ma funziona molte volte più lentamente del codice originale.
Roma

4

Puoi chiedere aiuto al pacchetto line_profiler per questo

1. Prima installa il pacchetto:

    pip install line_profiler

2. Usa il comando magico per caricare il pacchetto nel tuo ambiente python / notebook

    %load_ext line_profiler

3. Se desideri
creare il profilo dei codici per una funzione, procedi come segue:

    %lprun -f demo_func demo_func(arg1, arg2)

otterrai un bell'output formattato con tutti i dettagli se segui questi passaggi :)

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

4

Solo per migliorare la risposta di @Joe Kington sopra menzionata .

Per Python 3.x , usa line_profiler :


Installazione:

pip install line_profiler

Uso:

Supponiamo di avere il programma main.pye al suo interno funzioni fun_a()e fun_b()di voler profilare rispetto al tempo; dovrai usare il decoratore @profilesubito prima delle definizioni delle funzioni. Ad esempio,

@profile
def fun_a():
    #do something

@profile
def fun_b():
    #do something more

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

Il programma può essere profilato eseguendo il comando della shell:

$ kernprof -l -v main.py

Gli argomenti possono essere recuperati utilizzando $ 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.

I risultati verranno stampati sulla console come:

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

MODIFICA: i risultati dei profiler possono essere analizzati utilizzando il pacchetto TAMPPA . Usandolo, possiamo ottenere grafici desiderati riga per riga come tracciare


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.