Profilazione del codice Python utilizzando timeit e cProfile - KDnuggets

Profilazione del codice Python utilizzando timeit e cProfile – KDnuggets

Nodo di origine: 2874649

Profilazione del codice Python utilizzando timeit e cProfile
Immagine dell'autore
 

Come sviluppatore di software, probabilmente avrai sentito la citazione “L’ottimizzazione prematura è la radice di tutti i mali”-più di una volta-nella tua carriera. Anche se l’ottimizzazione potrebbe non essere molto utile (o assolutamente necessaria) per i piccoli progetti, la profilazione è spesso utile. 

Dopo aver terminato la codifica di un modulo, è buona norma profilare il codice per misurare il tempo necessario per l'esecuzione di ciascuna sezione. Ciò può aiutare a identificare gli odori del codice e guidare le ottimizzazioni per migliorare la qualità del codice. Quindi profila sempre il tuo codice prima di ottimizzare!

Per muovere i primi passi, questa guida ti aiuterà a iniziare con la profilazione in Python, utilizzando il built-in timeit ed cProfilo moduli. Imparerai a utilizzare sia l'interfaccia della riga di comando che i richiamabili equivalenti all'interno degli script Python.

Il modulo timeit fa parte della libreria standard Python e offre alcune funzioni utili che possono essere utilizzate per cronometrare brevi frammenti di codice.

Facciamo un semplice esempio di inversione di una lista Python. Misureremo i tempi di esecuzione per ottenere una copia invertita della lista utilizzando:

  • , il reversed() funzione e
  • elenco affettamento. 
>>> nums=[6,9,2,3,7]
>>> list(reversed(nums))
[7, 3, 2, 9, 6]
>>> nums[::-1]
[7, 3, 2, 9, 6]

 

Esecuzione di Timeit dalla riga di comando

Puoi correre timeit alla riga di comando utilizzando la sintassi:

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

 

Ti viene richiesto di fornire la dichiarazione stmt il cui tempo di esecuzione deve essere misurato. 

Puoi specificare il setup codice quando necessario, utilizzando l'opzione breve -s o l'opzione lunga –setup. Il codice di installazione verrà eseguito una sola volta.

Il number di volte per eseguire l'istruzione: l'opzione breve -n o l'opzione lunga –number è facoltativa. E il numero di volte per ripetere questo ciclo: anche l'opzione breve -r o l'opzione lunga –repeat è facoltativa.

Vediamo quanto sopra in azione per il nostro esempio:

Qui la creazione dell'elenco è il setup codice e invertendo l'elenco è l'istruzione da cronometrare:

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

 

Quando non specifichi valori per repeat, viene utilizzato il valore predefinito 5. E quando non lo specifichi number, il codice viene eseguito tante volte quanto necessario in modo da raggiungere un tempo totale di almeno 0.2 secondi.

Questo esempio imposta esplicitamente il numero di volte per eseguire l'istruzione:

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

 

Il valore predefinito di repeat è 5, ma possiamo impostarlo su qualsiasi valore adatto:

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

 

Cronometramo anche l'approccio di suddivisione dell'elenco:

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

 

L'approccio di suddivisione in elenchi sembra essere più veloce (tutti gli esempi sono in Python 3.10 su Ubuntu 22.04).

Esecuzione di timeit in uno script Python

Ecco l'equivalente dell'esecuzione di timeit all'interno dello script 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}")

 

Il timeit() callable restituisce il tempo di esecuzione di stmt per number di volte. Si noti che possiamo menzionare esplicitamente il numero di volte da eseguire o effettuare number prendi il valore predefinito di 1000000.

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

 

Questo esegue l'istruzione, senza ripetere la funzione timer, per l'oggetto specificato number di volte e restituisce il tempo di esecuzione. È anche abbastanza comune da usare time.repeat() e prenditi il ​​tempo minimo come mostrato:

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

 

Questo ripeterà il processo di esecuzione del codice number di volte repeat numero di volte e restituisce il tempo di esecuzione minimo. Qui abbiamo 5 ripetizioni da 100000 volte ciascuna.

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

Abbiamo visto come timeit può essere utilizzato per misurare i tempi di esecuzione di brevi snippet di codice. Tuttavia, in pratica, è più utile profilare un intero script Python. 

Questo ci fornirà i tempi di esecuzione di tutte le funzioni e le chiamate ai metodi, inclusi funzioni e metodi integrati. In questo modo possiamo avere un'idea migliore delle chiamate di funzioni più costose e identificare le opportunità di ottimizzazione. Ad esempio: potrebbe esserci una chiamata API troppo lenta. Oppure una funzione può avere un ciclo che può essere sostituito da un'espressione di comprensione più pythonica. 

Impariamo come profilare gli script Python utilizzando il modulo cProfile (anch'esso parte della libreria standard Python). 

Considera il seguente script 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)

 

Qui abbiamo tre funzioni:

  • func() che scorre attraverso una serie di numeri e li stampa.
  • another func() che contiene una chiamata al sleep() funzione.
  • useful_func() che restituisce l'indice di un numero di target in lista (se il target è presente nella lista).

Le funzioni sopra elencate verranno chiamate ogni volta che esegui lo script main.py.

Esecuzione di cProfile dalla riga di comando

Esegui cProfile dalla riga di comando utilizzando:

python3 -m file-name.py

 

Qui abbiamo chiamato il file main.py:

python3 -m main.py

 

L'esecuzione di questo dovrebbe darti il ​​seguente output:

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

 

E il seguente profilo:

 

Profilazione del codice Python utilizzando timeit e cProfile
 

Qui, ncalls si riferisce al numero di chiamate alla funzione e percall si riferisce al tempo per chiamata di funzione. Se il valore di ncalls è maggiore di uno, quindi percall è il tempo medio tra tutte le chiamate.

Il tempo di esecuzione dello script è dominato da another_func che utilizza il built-in sleep chiamata di funzione (dorme per 20 secondi). Lo vediamo print anche le chiamate di funzione sono piuttosto costose. 

Utilizzo di cProfile nello script Python

Sebbene l'esecuzione di cProfile dalla riga di comando funzioni correttamente, puoi anche aggiungere la funzionalità di profilazione allo script Python. Puoi usare cProfile accoppiato con il modulo pstat per la profilazione e l’accesso alle statistiche.

Come best practice per gestire meglio la configurazione e lo smontaggio delle risorse, utilizzare l'istruzione with e creare un oggetto profilo da utilizzare come gestore del contesto:

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

 

Diamo uno sguardo più da vicino al profilo di output generato:

 

Profilazione del codice Python utilizzando timeit e cProfile
 

Quando stai profilando uno script di grandi dimensioni, sarà utile ordinare i risultati in base al tempo di esecuzione. Per farlo puoi chiamare sort_stats sull'oggetto del profilo e ordinarlo in base al tempo di esecuzione: 

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

 

Quando ora esegui lo script, dovresti essere in grado di vedere i risultati ordinati per ora:

 

Profilazione del codice Python utilizzando timeit e cProfile

Spero che questa guida ti aiuti a iniziare con la profilazione in Python. Ricorda sempre che le ottimizzazioni non dovrebbero mai andare a scapito della leggibilità. Se sei interessato a conoscere altri profiler, inclusi i pacchetti Python di terze parti, dai un'occhiata a questo articolo sui profiler Python.
 
 
Bala Priya C è uno sviluppatore e scrittore tecnico dall'India. Le piace lavorare all'intersezione tra matematica, programmazione, scienza dei dati e creazione di contenuti. Le sue aree di interesse e competenza includono DevOps, data science ed elaborazione del linguaggio naturale. Le piace leggere, scrivere, programmare e il caffè! Attualmente, sta lavorando all'apprendimento e alla condivisione delle sue conoscenze con la comunità degli sviluppatori creando tutorial, guide pratiche, articoli di opinione e altro ancora.
 

Timestamp:

Di più da KDnuggets