Profiliranje kode Python z uporabo timeit in cProfile - KDnuggets

Profiliranje kode Python z uporabo timeit in cProfile – KDnuggets

Izvorno vozlišče: 2874649

Profiliranje kode Python z uporabo timeit in cProfile
Slika avtorja
 

Kot razvijalec programske opreme ste verjetno že slišali ta citat "Prezgodnja optimizacija je korenina vsega zla"—več kot enkrat—v vaši karieri. Medtem ko optimizacija morda ni zelo koristna (ali absolutno potrebna) za majhne projekte, je profiliranje pogosto koristno. 

Ko končate s kodiranjem modula, je dobro, da svojo kodo profilirate, da izmerite, kako dolgo traja izvajanje vsakega od odsekov. To lahko pomaga prepoznati vonjave kode in usmerja optimizacije za izboljšanje kakovosti kode. Zato vedno profilirajte svojo kodo pred optimizacijo!

Če želite narediti prve korake, vam bo ta vodnik pomagal začeti s profiliranjem v Pythonu – z uporabo vgrajenega timeit in cProfil moduli. Naučili se boste uporabljati tako vmesnik ukazne vrstice kot enakovredne klicače znotraj skriptov Python.

Modul timeit je del standardne knjižnice Python in ponuja nekaj priročnih funkcij, ki jih je mogoče uporabiti za merjenje časa kratkih izrezkov kode.

Vzemimo preprost primer obračanja seznama Python. Izmerili bomo čase izvajanja pridobivanja obrnjene kopije seznama z uporabo:

  • o reversed() funkcijo in
  • rezanje seznama. 
>>> nums=[6,9,2,3,7]
>>> list(reversed(nums))
[7, 3, 2, 9, 6]
>>> nums[::-1]
[7, 3, 2, 9, 6]

 

Running timeit v ukazni vrstici

Lahko tečeš timeit v ukazni vrstici s sintakso:

$ python -m timeit -s 'setup-code' -n 'number' -r 'repeat' 'stmt'

 

Izjavo morate predložiti stmt katerih čas izvedbe je treba izmeriti. 

Določite lahko setup kodo, ko je to potrebno – z uporabo kratke možnosti -s ali dolge možnosti –setup. Nastavitvena koda se bo zagnala samo enkrat.

O number kolikokrat zagnati stavek: kratka možnost -n ali dolga možnost –število ni obvezna. In število ponovitev tega cikla: kratka možnost -r ali dolga možnost –repeat je prav tako neobvezna.

Oglejmo si zgoraj navedeno v akciji za naš primer:

Tukaj je ustvarjanje seznama setup koda in obračanje seznama je izjava, ki jo je treba časovno določiti:

$ python -m timeit -s 'nums=[6,9,2,3,7]' 'list(reversed(nums))'
500000 loops, best of 5: 695 nsec per loop

 

Ko ne določite vrednosti za repeat, je uporabljena privzeta vrednost 5. In ko ne navedete number, se koda zažene tolikokrat, kot je potrebno, da se doseže skupni čas vsaj 0.2 sekund.

Ta primer izrecno nastavi, kolikokrat naj se izvede stavek:

$ python -m timeit -s 'nums=[6,9,2,3,7]' -n 100Bu000 'list(reversed(nums))'
100000 loops, best of 5: 540 nsec per loop

 

Privzeta vrednost za repeat je 5, vendar ga lahko nastavimo na poljubno primerno vrednost:

$ python3 -m timeit -s 'nums=[6,9,2,3,7]' -r 3 'list(reversed(nums))'
500000 loops, best of 3: 663 nsec per loop

 

Oglejmo si tudi čas pristopa rezanja seznama:

$ python3 -m timeit -s 'nums=[6,9,2,3,7]' 'nums[::-1]'
1000000 loops, best of 5: 142 nsec per loop

 

Zdi se, da je pristop rezanja seznama hitrejši (vsi primeri so v Pythonu 3.10 na Ubuntu 22.04).

Čas izvajanja v skriptu Python

Tukaj je enakovreden času izvajanja znotraj skripta Python:

import timeit setup = 'nums=[9,2,3,7,6]'
number = 100000
stmt1 = 'list(reversed(nums))'
stmt2 = 'nums[::-1]' t1 = timeit.timeit(setup=setup,stmt=stmt1,number=number)
t2 = timeit.timeit(setup=setup,stmt=stmt2,number=number) print(f"Using reversed() fn.: {t1}")
print(f"Using list slicing: {t2}")

 

O timeit() callable vrne čas izvajanja stmt za number večkrat. Upoštevajte, da lahko izrecno omenimo, kolikokrat je treba zagnati ali narediti number vzemite privzeto vrednost 1000000.

Output >>
Using reversed() fn.: 0.08982690000000002
Using list slicing: 0.015550800000000004

 

To zažene stavek – brez ponavljanja funkcije časovnika – za podano number krat in vrne čas izvedbe. Prav tako je precej pogosta uporaba time.repeat() in si vzemite minimalni čas, kot je prikazano:

import timeit setup = 'nums=[9,2,3,7,6]'
number = 100000
stmt1 = 'list(reversed(nums))'
stmt2 = 'nums[::-1]' t1 = min(timeit.repeat(setup=setup,stmt=stmt1,number=number))
t2 = min(timeit.repeat(setup=setup,stmt=stmt2,number=number)) print(f"Using reversed() fn.: {t1}")
print(f"Using list slicing: {t2}")

 

To bo ponovilo postopek izvajanja kode number krat repeat število krat in vrne najmanjši čas izvajanja. Tu imamo 5 ponovitev po 100000-krat.

Output >>
Using reversed() fn.: 0.055375300000000016
Using list slicing: 0.015101400000000043

Videli smo, kako se lahko timeit uporablja za merjenje časov izvajanja kratkih odrezkov kode. Vendar je v praksi bolj koristno profilirati celoten skript Python. 

To nam bo dalo čas izvajanja vseh funkcij in klicev metod – vključno z vgrajenimi funkcijami in metodami. Tako lahko dobimo boljšo predstavo o dražjih funkcijskih klicih in prepoznamo priložnosti za optimizacijo. Na primer: morda je klic API-ja prepočasen. Lahko pa ima funkcija zanko, ki jo je mogoče nadomestiti z bolj Pythonic izrazom razumevanja. 

Naučimo se profilirati skripte Python z uporabo modula cProfile (prav tako del standardne knjižnice Python). 

Razmislite o naslednjem skriptu Python:

# main.py
import time def func(num): for i in range(num): print(i) def another_func(num): time.sleep(num) print(f"Slept for {num} seconds") def useful_func(nums, target): if target in nums: return nums.index(target) if __name__ == "__main__": func(1000) another_func(20) useful_func([2, 8, 12, 4], 12)

 

Tukaj imamo tri funkcije:

  • func() ki preleti niz števil in jih natisne.
  • another func() ki vsebuje klic na sleep() Funkcija.
  • useful_func() ki vrne indeks ciljne številke na seznamu (če je cilj na seznamu).

Zgoraj navedene funkcije bodo poklicane vsakič, ko zaženete skript main.py.

Zagon cProfile v ukazni vrstici

Zaženite cProfile v ukazni vrstici z:

python3 -m file-name.py

 

Tu smo datoteko poimenovali main.py:

python3 -m main.py

 

Zagon tega bi vam moral dati naslednje rezultate:

 Output >> 0 ... 999 Slept for 20 seconds

 

In naslednji profil:

 

Profiliranje kode Python z uporabo timeit in cProfile
 

Tu ncalls se nanaša na število klicev funkcije in percall se nanaša na čas na klic funkcije. Če je vrednost ncalls je torej večji od ena percall je povprečni čas vseh klicev.

Prevladuje čas izvajanja skripta another_func ki uporablja vgrajeno sleep klic funkcije (spi 20 sekund). To vidimo print tudi funkcijski klici so precej dragi. 

Uporaba cProfile v skriptu Python

Čeprav zagon cProfile v ukazni vrstici deluje dobro, lahko skriptu Python dodate tudi funkcijo profiliranja. Uporabite lahko cProfile skupaj z modul pstats za profiliranje in dostop do statistike.

Kot najboljšo prakso za boljše upravljanje nastavitve in razgradnje virov uporabite stavek with in ustvarite objekt profila, ki se uporablja kot upravitelj konteksta:

# main.py
import pstats
import time
import cProfile def func(num): for i in range(num): print(i) def another_func(num): time.sleep(num) print(f"Slept for {num} seconds") def useful_func(nums, target): if target in nums: return nums.index(target) if __name__ == "__main__": with cProfile.Profile() as profile: func(1000) another_func(20) useful_func([2, 8, 12, 4], 12) profile_result = pstats.Stats(profile) profile_result.print_stats()

 

Oglejmo si podrobneje ustvarjeni izhodni profil:

 

Profiliranje kode Python z uporabo timeit in cProfile
 

Ko profilirate velik skript, vam bo v pomoč razvrstite rezultate po času izvajanja. Če želite to narediti, lahko pokličete sort_stats na objektu profila in razvrsti glede na čas izvajanja: 

...
if __name__ == "__main__": with cProfile.Profile() as profile: func(1000) another_func(20) useful_func([2, 8, 12, 4], 12) profile_result = pstats.Stats(profile) profile_result.sort_stats(pstats.SortKey.TIME) profile_result.print_stats()

 

Ko zdaj zaženete skript, bi morali imeti možnost videti rezultate, razvrščene po času:

 

Profiliranje kode Python z uporabo timeit in cProfile

Upam, da vam bo ta vodnik pomagal začeti s profiliranjem v Pythonu. Vedno si zapomnite, da optimizacije nikoli ne smejo priti na ceno berljivosti. Če vas zanima več o drugih profilerjih, vključno s paketi Python tretjih oseb, si oglejte to članek o Python profilerjih.
 
 
Bala Priya C je razvijalec in tehnični pisec iz Indije. Rada dela na presečišču matematike, programiranja, znanosti o podatkih in ustvarjanja vsebin. Njena področja zanimanja in strokovnega znanja vključujejo DevOps, znanost o podatkih in obdelavo naravnega jezika. Uživa v branju, pisanju, kodiranju in kavi! Trenutno se uči in svoje znanje deli s skupnostjo razvijalcev, tako da piše vadnice, vodnike z navodili, mnenja in drugo.
 

Časovni žig:

Več od KDnuggets