Come usare cProfile per profilare il codice Python

Python potrebbe non essere il linguaggio più veloce in circolazione, ma spesso è abbastanza veloce. E Python è l'ideale quando il tempo del programmatore è più importante del tempo della CPU.

Detto questo, se una determinata app Python è in ritardo, non sei obbligato a succhiarlo. Gli strumenti inclusi con l'installazione di serie dell'interprete Python possono fornire feedback dettagliati su quali parti del programma sono lente e offrono alcuni suggerimenti su come velocizzarle.

Come usare cProfile

Il cProfilemodulo raccoglie statistiche sul tempo di esecuzione di un programma Python. Può segnalare qualsiasi cosa, dall'intera app a una singola istruzione o espressione.

Ecco un esempio di giocattolo di come utilizzare cProfile:

def add (x, y): x + = str (y) return x def add_2 (x, y): if y% 20000 == 0: z = [] per q nell'intervallo (0,400000): z.append ( q) def main (): a = [] for n in range (0,200000): add (a, n) add_2 (a, n) if __name__ == '__main__': import cProfile cProfile.run ('main ( ) ') 

Questo esempio esegue la main()funzione dell'applicazione e analizza le prestazioni di main()e tutto ciò che viene main()chiamato. È anche possibile analizzare solo una  parte di un programma, ma l'uso più comune per i principianti è quello di profilare l'intero programma.

Esegui l'esempio sopra e sarai accolto con qualcosa di simile al seguente output:

Quello che viene mostrato qui è un elenco di tutte le chiamate di funzione effettuate dal programma, insieme alle statistiche su ciascuna:

  • In alto (prima riga in blu), vediamo il numero totale di chiamate effettuate nel programma profilato e il tempo di esecuzione totale. Potresti anche vedere una figura per "chiamate primitive", che significa chiamate non ricorsive , o chiamate effettuate direttamente a una funzione che a loro volta non si chiamano più in basso nello stack di chiamate.
  • ncalls : numero di chiamate effettuate. Se vedi due numeri separati da una barra, il secondo numero è il numero di chiamate primitive per quella funzione.
  • tottime : tempo totale trascorso nella funzione, escluse le chiamate ad altre funzioni.
  • percall : tempo medio per chiamata per tottime , derivato prendendo tottime e dividendolo per ncalls .
  • cumtime : tempo totale trascorso nella funzione, incluse le chiamate ad altre funzioni.
  • percall (# 2): tempo medio per chiamata per cumtime ( cumtime diviso ncalls ).
  • filename: lineno : il nome del file, il numero di riga e il nome della funzione per la chiamata in questione.

Come modificare i report di cProfile

Per impostazione predefinita, cProfileordina l'output per "nome standard", il che significa che ordina in base al testo nella colonna all'estrema destra (nome file, numero di riga, ecc.).

Il formato predefinito è utile se si desidera un rapporto generale dall'alto verso il basso di ogni singola chiamata di funzione come riferimento. Ma se stai cercando di arrivare al fondo di un collo di bottiglia, probabilmente vorrai che le parti del programma che richiedono più tempo siano elencate per prime.

Possiamo produrre questi risultati invocando in  cProfile modo leggermente diverso. Nota come la parte inferiore del programma sopra può essere rielaborata per ordinare le statistiche in base a una colonna diversa (in questo caso ncalls):

if __name__ == '__main__': import cProfile, pstats profiler = cProfile.Profile () profiler.enable () main () profiler.disable () stats = pstats.Stats (profiler) .sort_stats ('ncalls') stats.print_stats () 

I risultati saranno simili a questo:

Ecco come funziona tutto questo:

  • Invece di eseguire un comando attraverso cProfile.run(), che non è molto flessibile, creiamo una profilatura oggetto , profiler.
  • Quando vogliamo profilare un'azione, chiamiamo prima .enable()l'istanza dell'oggetto profiler, quindi eseguiamo l'azione, quindi chiamiamo .disable(). (Questo è un modo per profilare solo una parte di un programma.)
  • Il pstatsmodulo viene utilizzato per manipolare i risultati raccolti dall'oggetto profiler e stampare tali risultati.

La combinazione di un oggetto profiler e pstatsci consente di manipolare i dati del profilo acquisiti, ad esempio per ordinare le statistiche generate in modo diverso. In questo esempio, using .sort_stats('ncalls')ordina le statistiche in base alla ncallscolonna. Sono disponibili altre opzioni di ordinamento.

Come utilizzare i risultati di cProfile per l'ottimizzazione

Le opzioni di ordinamento disponibili per l' cProfile output ci consentono di individuare potenziali colli di bottiglia delle prestazioni in un programma.

ncalls

La prima e più significativa informazione con cui puoi scovare cProfileè quali funzioni vengono chiamate più frequentemente, tramite la ncallscolonna.

In Python, il semplice atto di effettuare una chiamata a una funzione comporta un sovraccarico relativamente elevato. Se una funzione viene chiamata ripetutamente in un ciclo ristretto, anche se non è una funzione di lunga durata, è garantito che influirà sulle prestazioni.

Nell'esempio sopra, la funzione add(e la funzione add_2) viene chiamata ripetutamente in un ciclo. Spostare il ciclo nella addfunzione stessa, o addincorporare completamente la funzione, risolverebbe questo problema.

tottime

Un'altra statistica utile mostra quali funzioni il programma trascorre la maggior parte del suo tempo in esecuzione, tramite la tottimecolonna.

Nell'esempio precedente, la add_2funzione utilizza un ciclo per simulare un calcolo costoso, che spinge il suo tottimepunteggio verso l'alto. Qualsiasi funzione con un tottimepunteggio elevato merita un'analisi ravvicinata, soprattutto se viene chiamata molte volte o in un ciclo stretto.

Si noti che è sempre necessario considerare il contesto  in cui viene utilizzata la funzione. Se una funzione ha un alto tottimema viene chiamata solo una volta, ad esempio solo all'avvio del programma, è meno probabile che si tratti di un collo di bottiglia. Tuttavia, se stai cercando di ridurre il tempo di avvio, ti consigliamo di sapere se una funzione chiamata all'avvio sta facendo aspettare tutto il resto.

Come esportare i dati di cProfile

Se desideri utilizzare cProfilele statistiche generate di in modi più avanzati, puoi esportarle in un file di dati:

stats = pstats.Stats (profiler) stats.dump_stats ('/ path / to / stats_file.dat') 

Questo file può essere letto di nuovo utilizzando il pstatsmodulo, quindi ordinato o visualizzato con pstats. I dati possono essere riutilizzati anche da altri programmi. Due esempi:

  • pyprof2calltreerende visualizzazioni dettagliate del grafico delle chiamate del programma e delle statistiche di utilizzo dai dati del profilo. Questo articolo fornisce un esempio dettagliato del suo utilizzo nel mondo reale.
  • snakevizgenera anche visualizzazioni dai cProfiledati, ma utilizza una rappresentazione diversa per i dati: un grafico "a raggiera" anziché il grafico "a fiamma" di pyprof2calltree.

Oltre cProfile per la profilazione di Python

cProfilenon è certo l'unico modo per profilare un'applicazione Python. cProfileè sicuramente uno dei modi più convenienti, dato che è fornito in bundle con Python. Ma altri meritano attenzione.

Un progetto py-spy,, costruisce un profilo per un'applicazione Python campionando la sua attività di chiamata. py-spypuò essere utilizzato per esaminare un'app Python in esecuzione senza doverla arrestare e riavviare e senza dover alterare la sua base di codice, in modo che possa essere utilizzata per profilare le applicazioni distribuite. py-spygenera anche alcune statistiche sull'overhead sostenuto dal runtime Python (ad esempio, overhead di garbage collection), che cProfilenon lo fa.