Profilering av Python-kode ved hjelp av timeit og cProfile - KDnuggets

Profilering av Python-kode ved hjelp av timeit og cProfile – KDnuggets

Kilde node: 2874649

Profilering av Python-kode ved hjelp av timeit og cProfile
Bilde av forfatter
 

Som programvareutvikler har du sannsynligvis hørt sitatet "For tidlig optimalisering er roten til alt ondt"–mer enn én gang – i karrieren din. Selv om optimalisering kanskje ikke er veldig nyttig (eller absolutt nødvendig) for små prosjekter, er profilering ofte nyttig. 

Etter at du er ferdig med å kode en modul, er det en god praksis å profilere koden din for å måle hvor lang tid hver av seksjonene tar å utføre. Dette kan hjelpe med å identifisere kodelukter og veilede optimaliseringer for å forbedre kodekvaliteten. Så profiler alltid koden din før du optimaliserer!

For å ta de første trinnene, vil denne veiledningen hjelpe deg å komme i gang med profilering i Python – ved å bruke den innebygde tid og cProfil moduler. Du vil lære å bruke både kommandolinjegrensesnittet og tilsvarende callables i Python-skript.

Timeit-modulen er en del av Python-standardbiblioteket og tilbyr noen få praktiske funksjoner som kan brukes til å time korte kodebiter.

La oss ta et enkelt eksempel på å snu en Python-liste. Vi måler utførelsestidene for å få en reversert kopi av listen ved å bruke:

  • de reversed() funksjon, og
  • listeskjæring. 
>>> nums=[6,9,2,3,7]
>>> list(reversed(nums))
[7, 3, 2, 9, 6]
>>> nums[::-1]
[7, 3, 2, 9, 6]

 

Kjører timeit på kommandolinjen

Du kan kjøre timeit på kommandolinjen ved å bruke syntaksen:

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

 

Du er pålagt å gi erklæringen stmt hvis utførelsestid skal måles. 

Du kan spesifisere setup kode ved behov – ved å bruke det korte alternativet -s eller det lange alternativet – oppsett. Oppsettkoden kjøres kun én gang.

De number ganger for å kjøre setningen: kort alternativ -n eller lang alternativ -antall er valgfritt. Og antall ganger for å gjenta denne syklusen: kort alternativ -r eller lang alternativ - gjenta er også valgfritt.

La oss se ovenstående i aksjon for vårt eksempel:

Her er det å lage listen setup kode og reversering av listen er setningen som skal tidsbestemmes:

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

 

Når du ikke angir verdier for repeat, brukes standardverdien 5. Og når du ikke spesifiserer number, koden kjøres så mange ganger som nødvendig for å nå en total tid på minst 0.2 sekunder.

Dette eksemplet angir eksplisitt antall ganger setningen skal utføres:

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

 

Standardverdien på repeat er 5, men vi kan sette den til en hvilken som helst passende verdi:

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

 

La oss også sette tid på listeskjæringsmetoden:

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

 

Liste-tilnærmingen ser ut til å være raskere (alle eksempler er i Python 3.10 på Ubuntu 22.04).

Kjører timeit i et Python-skript

Her er det tilsvarende å kjøre timeit inne i Python-skriptet:

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}")

 

De timeit() callable returnerer utførelsestiden på stmt forum number av ganger. Legg merke til at vi eksplisitt kan nevne antall ganger å kjøre, eller gjøre number ta standardverdien på 1000000.

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

 

Dette kjører setningen – uten å gjenta timerfunksjonen – for det spesifiserte number ganger og returnerer utførelsestiden. Det er også ganske vanlig å bruke time.repeat() og ta minimumstiden som vist:

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}")

 

Dette vil gjenta prosessen med å kjøre koden number av ganger repeat antall ganger og returnerer minimum utførelsestid. Her har vi 5 repetisjoner på 100000 XNUMX ganger hver.

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

Vi har sett hvordan timeit kan brukes til å måle utførelsestidene til korte kodebiter. I praksis er det imidlertid mer nyttig å profilere et helt Python-skript. 

Dette vil gi oss utføringstidene for alle funksjonene og metodekallene – inkludert innebygde funksjoner og metoder. Så vi kan få et bedre inntrykk av de dyrere funksjonskallene og identifisere muligheter for optimalisering. For eksempel: det kan være et API-kall som er for tregt. Eller en funksjon kan ha en løkke som kan erstattes av et mer pytonisk forståelsesuttrykk. 

La oss lære hvordan du profilerer Python-skript ved å bruke cProfile-modulen (også en del av Python-standardbiblioteket). 

Tenk på følgende Python-skript:

# 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)

 

Her har vi tre funksjoner:

  • func() som går gjennom en rekke tall og skriver dem ut.
  • another func() som inneholder en oppfordring til sleep() funksjon.
  • useful_func() som returnerer indeksen til et målnummer i listen (hvis målet er til stede i listen).

Funksjonene ovenfor kalles opp hver gang du kjører main.py-skriptet.

Kjører cProfile på kommandolinjen

Kjør cProfile på kommandolinjen ved å bruke:

python3 -m file-name.py

 

Her har vi kalt filen main.py:

python3 -m main.py

 

Å kjøre dette bør gi deg følgende utgang:

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

 

Og følgende profil:

 

Profilering av Python-kode ved hjelp av timeit og cProfile
 

Her ncalls refererer til antall anrop til funksjonen og percall refererer til tiden per funksjonsanrop. Hvis verdien av ncalls er større enn én, da percall er gjennomsnittstiden for alle samtaler.

Utførelsestiden for skriptet er dominert av another_func som bruker den innebygde sleep funksjonsanrop (sover i 20 sekunder). Det ser vi print funksjonsanrop er også ganske dyre. 

Bruke cProfile i Python-skriptet

Mens du kjører cProfile på kommandolinjen fungerer fint, kan du også legge til profileringsfunksjonaliteten til Python-skriptet. Du kan bruke cProfile sammen med pstats modul for profilering og tilgang til statistikk.

Som en beste praksis for å håndtere ressursoppsett og nedbygging bedre, bruk with-setningen og opprette et profilobjekt som brukes som kontekstbehandling:

# 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()

 

La oss se nærmere på utdataprofilen som er generert:

 

Profilering av Python-kode ved hjelp av timeit og cProfile
 

Når du profilerer et stort skript, vil det være nyttig å sortere resultatene etter utførelsestid. For å gjøre det kan du ringe sort_stats på profilobjektet og sorter basert på utførelsestiden: 

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

 

Når du nå kjører skriptet, skal du kunne se resultatene sortert etter tid:

 

Profilering av Python-kode ved hjelp av timeit og cProfile

Jeg håper denne guiden hjelper deg å komme i gang med profilering i Python. Husk alltid at optimaliseringer aldri skal gå på bekostning av lesbarhet. Hvis du er interessert i å lære om andre profiler, inkludert tredjeparts Python-pakker, sjekk ut denne artikkel om Python-profiler.
 
 
Bala Priya C er en utvikler og teknisk skribent fra India. Hun liker å jobbe i skjæringspunktet mellom matematikk, programmering, datavitenskap og innholdsskaping. Hennes interesseområder og ekspertise inkluderer DevOps, datavitenskap og naturlig språkbehandling. Hun liker å lese, skrive, kode og kaffe! For øyeblikket jobber hun med å lære og dele kunnskapen sin med utviklerfellesskapet ved å skrive veiledninger, veiledninger, meningsartikler og mer.
 

Tidstempel:

Mer fra KDnuggets