Cum pot să fac profilul codului Python linie cu linie? (Programare, Python, Profilarea, Linie Cu Linie)

rocketmonkeys a intrebat.

Am folosit cProfile pentru a-mi face profilul codului meu și a funcționat foarte bine. De asemenea, am folosit gprof2dot.py pentru a vizualiza rezultatele (face ca totul să fie puțin mai clar).

Cu toate acestea, cProfile (și majoritatea celorlalte profilatoare Python pe care le-am văzut până acum) par să facă profilul doar la nivelul apelurilor de funcție. Acest lucru provoacă confuzie atunci când anumite funcții sunt apelate din locuri diferite – nu am nicio idee dacă apelul #1 sau apelul #2 ocupă majoritatea timpului. Acest lucru se înrăutățește și mai mult atunci când funcția în cauză se află la șase niveluri de adâncime și este apelată din alte șapte locuri.

Cum pot obține o profilare linie cu linie?

În loc de asta:

function #12, total time: 2.0s

Aș vrea să văd ceva de genul acesta:

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

cProfile arată cât din timpul total „transferă” către părintele, dar, din nou, această legătură se pierde atunci când aveți o mulțime de niveluri și apeluri interconectate.

În mod ideal, mi-ar plăcea să am o interfață grafică care să analizeze datele, apoi să-mi arate fișierul sursă cu timpul total acordat fiecărei linii. Ceva de genul acesta:

main.py:

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

Apoi aș putea să dau clic pe al doilea apel „func(c)” pentru a vedea ce consumă timp în acel apel, separat de apelul „func(a)”. Are sens?

Comentarii

  • Bănuiesc că ați fi interesat de pstats.print_callers. Un exemplu este aici. –  > Por Muhammad Alkarouri.
  • Muhammad, asta este cu siguranță util! Cel puțin rezolvă o problemă: separarea apelurilor de funcție în funcție de origine. Cred că răspunsul lui Joe Kington este mai aproape de obiectivul meu, dar print_callers() mă duce cu siguranță la jumătatea drumului. Vă mulțumim! –  > Por rocketmonkeys.
5 răspunsuri
Joe Kington

Cred că asta este ceea ce Robert Kern’s line_profiler este destinat. Din 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

Comentarii

  • Line_profiler funcționează cu Python 3? Nu am reușit să obțin nicio informație în acest sens. –  > Por user1251007.
  • line_profiler nu arată hiturile și timpul pentru mine. Poate cineva să-mi spună de ce? Și cum să rezolv? –  > Por I159.
  • Iată decoratorul pe care l-am scris: gist.github.com/kylegibson/6583590. Dacă rulați nosetests, asigurați-vă că utilizați opțiunea -s pentru ca stdout să fie tipărit imediat. –  > Por Kyle Gibson.
  • cum arată scriptul python care produce această ieșire? import line_profiler; și apoi ? –  > Por Zhubarb.
  • poate cineva să ne arate cum să folosim efectiv această bibliotecă? Readme ne învață cum să o instalăm și răspunde la diverse întrebări frecvente, dar nu menționează cum să o folosim după o instalare pip… –  > Por cryanbhu.
vpelletier

De asemenea, ați putea utiliza pprofile(pypi).Dacă doriți să faceți profilul întregii execuții, nu necesită modificarea codului sursă. Puteți, de asemenea, să realizați profilul unui subset al unui program mai mare în două moduri:

  • comutați profilarea atunci când ajungeți într-un anumit punct al codului, cum ar fi:

    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")
    
  • comutarea profilării în mod asincron de la stiva de apeluri (necesită o modalitate de a declanșa acest cod în aplicația considerată, de exemplu, un gestionar de semnal sau un fir de lucru disponibil) prin utilizarea profilării statistice:

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

Formatul de ieșire al adnotării codului este foarte asemănător cu profilatorul de linii:

$ 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

Rețineți că, deoarece pprofile nu se bazează pe modificarea codului, acesta poate realiza profilul declarațiilor de nivel superior ale modulelor, permițând realizarea profilului timpului de pornire a programului (cât timp este necesar pentru a importa module, a inițializa modulele globale, …).

Poate genera ieșiri formatate în format cachegrind, astfel încât să puteți utiliza kcachegrind pentru a răsfoi cu ușurință rezultatele mari.

Dezvăluire: Sunt autorul pprofile.

Comentarii

  • +1 Mulțumesc pentru contribuția dvs. Arată bine făcut. Am o perspectivă puțin diferită – măsurarea timpului inclusiv luat de declarații și funcții este un obiectiv. A afla ce se poate face pentru a face codul mai rapid este un alt obiectiv. Diferența devine dureros de evidentă pe măsură ce codul devine mai mare – cum ar fi 10^6 linii de cod. Codul poate irosi procente mari de timp. Modul în care îl găsesc eu este luând un număr mic de mostre foarte detaliate și examinându-le cu un ochi uman – nu rezumând. Problema este expusă prin fracțiunea de timp pe care o irosește. –  > Por Mike Dunlavey.
  • Aveți dreptate, nu am menționat utilizarea pprofile atunci când se dorește profilarea unui subset mai mic. Mi-am editat postarea pentru a adăuga exemple în acest sens. –  > Por vpelletier.
  • Este exact ceea ce căutam: neintruziv și extins. –  > Por egpbos.
  • Un instrument drăguț, dar rulează de câteva ori mai lent decât codul original. –  > Por rominf.
  • În modul determinist, are o supraîncărcare semnificativă – reversul portabilității. În cazul codului mai lent, vă recomand să utilizați modul statistic, care are un cost suplimentar ridicol de mic – în detrimentul impreciziei urmelor și al lizibilității. Dar poate fi și un prim pas: identificați punctul fierbinte în modul statistic, produceți un caz mai mic care declanșează punctul fierbinte identificat și utilizați profilarea deterministă pentru a obține toate detaliile. –  > Por vpelletier.
Sateesh

Puteți apela la ajutorul line_profiler pentru acest lucru

1. În primul rând, instalați pachetul:

    pip install line_profiler

2. Utilizați comanda magic pentru a încărca pachetul în mediul python/notebook

    %load_ext line_profiler

3. Dacă doriți să faceți profilul codurilor pentru o funcție, atunci
procedați după cum urmează:

    %lprun -f demo_func demo_func(arg1, arg2)

veți obține o ieșire frumos formatată cu toate detaliile dacă urmați acești pași 🙂

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

Pe Dro

Doar pentru a îmbunătăți răspunsul lui @Joe Kington ‘s menționat mai sus.

Pentru Python 3.x, utilizați line_profiler:


Instalare:

pip install line_profiler

Utilizare:

Să presupunem că aveți programul main.py și în cadrul acestuia, funcțiile fun_a() și fun_b() pe care doriți să le profilați în funcție de timp; va trebui să utilizați decoratorul @profile chiar înainte de definițiile funcțiilor. De exemplu,

@profile
def fun_a():
    #do something

@profile
def fun_b():
    #do something more

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

Programul poate fi profilat prin executarea comenzii shell:

$ kernprof -l -v main.py

Argumentele pot fi preluate folosind $ 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.

Rezultatele vor fi tipărite pe consolă sub forma::

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


EDIT: Rezultatele de la profilatoare pot fi analizate folosind comanda TAMPPA pachet. Utilizându-l, putem obține graficele dorite linie cu linie ca

Comentarii

  • Instrucțiunile sunt corecte, dar graficul este înșelător, deoarece line_profiler nu face profilul utilizării memoriei (memory_profiler o face, dar de multe ori dă greș). Aș recomanda să folosiți în schimb profilatorul (meu) Scalene, dacă sunteți pe Mac OS X sau Linux: pip install -U scalene, github.com/emeryberger/scalene — acesta face simultan profilarea la nivel de linie a timpului de procesare și a memoriei (și nu numai!). –  > Por EmeryBerger.
  • Bună ziua @emeryberger, graficul prezentat este realizat de un nou pachet: TAMPPA. deși este supus unor probleme. Știu că există mai multe modalități. Vă mulțumim că ați împărtășit unul. V-aș recomanda să trimiteți un răspuns detaliat aici 🙂 Ați trimis o problemă pentru ‘memory_profiler’ ? –  > Por Pe Dro.
Fabio Zadrozny

PyVmMonitor are un live-view care vă poate ajuta acolo (vă puteți conecta la un program în execuție și puteți obține statistici de la acesta).

Vedeți: http://www.pyvmmonitor.com/