Benchmarking e profilazione

In questo capitolo impareremo come il benchmarking e la profilazione aiutano ad affrontare i problemi di prestazioni.

Supponiamo di aver scritto un codice e sta dando anche il risultato desiderato, ma cosa succederebbe se volessimo eseguire questo codice un po 'più velocemente perché le esigenze sono cambiate. In questo caso, dobbiamo scoprire quali parti del nostro codice stanno rallentando l'intero programma. In questo caso, il benchmarking e la profilazione possono essere utili.

Cos'è il benchmarking?

Il benchmarking mira a valutare qualcosa rispetto a uno standard. Tuttavia, la domanda che sorge qui è che cosa sarebbe il benchmarking e perché ne abbiamo bisogno in caso di programmazione software. Il benchmarking del codice indica la velocità di esecuzione del codice e dove si trova il collo di bottiglia. Uno dei motivi principali per il benchmarking è che ottimizza il codice.

Come funziona il benchmarking?

Se parliamo del funzionamento del benchmarking, dobbiamo iniziare confrontando l'intero programma come uno stato corrente, quindi possiamo combinare micro benchmark e quindi scomporre un programma in programmi più piccoli. Al fine di trovare i colli di bottiglia all'interno del nostro programma e ottimizzarlo. In altre parole, possiamo intenderlo come rompere il problema grande e difficile in una serie di problemi più piccoli e un po 'più facili per ottimizzarli.

Modulo Python per il benchmarking

In Python, abbiamo un modulo predefinito per il benchmarking che viene chiamato timeit. Con l'aiuto ditimeit modulo, possiamo misurare le prestazioni di una piccola parte di codice Python all'interno del nostro programma principale.

Esempio

Nel seguente script Python, stiamo importando il file timeit modulo, che misura ulteriormente il tempo impiegato per eseguire due funzioni: functionA e functionB -

import timeit
import time
def functionA():
   print("Function A starts the execution:")
   print("Function A completes the execution:")
def functionB():
   print("Function B starts the execution")
   print("Function B completes the execution")
start_time = timeit.default_timer()
functionA()
print(timeit.default_timer() - start_time)
start_time = timeit.default_timer()
functionB()
print(timeit.default_timer() - start_time)

Dopo aver eseguito lo script precedente, otterremo il tempo di esecuzione di entrambe le funzioni come mostrato di seguito.

Produzione

Function A starts the execution:
Function A completes the execution:
0.0014599495514175942
Function B starts the execution
Function B completes the execution
0.0017024724827479076

Scrivere il nostro timer utilizzando la funzione decoratore

In Python, possiamo creare il nostro timer, che agirà proprio come il timeitmodulo. Può essere fatto con l'aiuto didecoratorfunzione. Di seguito è riportato un esempio del timer personalizzato:

import random
import time

def timer_func(func):

   def function_timer(*args, **kwargs):
   start = time.time()
   value = func(*args, **kwargs)
   end = time.time()
   runtime = end - start
   msg = "{func} took {time} seconds to complete its execution."
      print(msg.format(func = func.__name__,time = runtime))
   return value
   return function_timer

@timer_func
def Myfunction():
   for x in range(5):
   sleep_time = random.choice(range(1,3))
   time.sleep(sleep_time)

if __name__ == '__main__':
   Myfunction()

Lo script python di cui sopra aiuta a importare moduli in tempo casuale. Abbiamo creato la funzione decorator timer_func (). Questo ha la funzione function_timer () al suo interno. Ora, la funzione annidata prenderà il tempo prima di chiamare la funzione passata. Quindi attende che la funzione ritorni e acquisisce l'ora di fine. In questo modo, possiamo finalmente fare in modo che lo script python stampi il tempo di esecuzione. Lo script genererà l'output come mostrato di seguito.

Produzione

Myfunction took 8.000457763671875 seconds to complete its execution.

Cos'è la profilazione?

A volte il programmatore vuole misurare alcuni attributi come l'uso della memoria, la complessità temporale o l'utilizzo di particolari istruzioni sui programmi per misurare la reale capacità di quel programma. Questo tipo di misurazione del programma è chiamato profilazione. La creazione di profili utilizza l'analisi dinamica del programma per eseguire tale misurazione.

Nelle sezioni successive, impareremo a conoscere i diversi moduli Python per la profilazione.

cProfile - il modulo integrato

cProfileè un modulo integrato in Python per la profilazione. Il modulo è un'estensione C con un overhead ragionevole che lo rende adatto per la creazione di profili di programmi a esecuzione prolungata. Dopo averlo eseguito, registra tutte le funzioni e i tempi di esecuzione. È molto potente ma a volte un po 'difficile da interpretare e da agire. Nell'esempio seguente, stiamo usando cProfile nel codice seguente -

Esempio

def increment_global():

   global x
   x += 1

def taskofThread(lock):

   for _ in range(50000):
   lock.acquire()
   increment_global()
   lock.release()

def main():
   global x
   x = 0

   lock = threading.Lock()

   t1 = threading.Thread(target=taskofThread, args=(lock,))
   t2 = threading.Thread(target= taskofThread, args=(lock,))

   t1.start()
   t2.start()

   t1.join()
   t2.join()

if __name__ == "__main__":
   for i in range(5):
      main()
   print("x = {1} after Iteration {0}".format(i,x))

Il codice sopra viene salvato in thread_increment.pyfile. Ora, esegui il codice con cProfile sulla riga di comando come segue:

(base) D:\ProgramData>python -m cProfile thread_increment.py
x = 100000 after Iteration 0
x = 100000 after Iteration 1
x = 100000 after Iteration 2
x = 100000 after Iteration 3
x = 100000 after Iteration 4
      3577 function calls (3522 primitive calls) in 1.688 seconds

   Ordered by: standard name

   ncalls tottime percall cumtime percall filename:lineno(function)

   5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:103(release)
   5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:143(__init__)
   5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:147(__enter__)
   … … … …

Dall'output di cui sopra, è chiaro che cProfile stampa tutte le 3577 funzioni chiamate, con il tempo trascorso in ciascuna e il numero di volte in cui sono state chiamate. Di seguito sono riportate le colonne che abbiamo ottenuto in output -

  • ncalls - È il numero di chiamate effettuate.

  • tottime - È il tempo totale trascorso nella funzione data.

  • percall - Si riferisce al quoziente di tempo diviso per ncalls.

  • cumtime- È il tempo cumulativo trascorso in questa e in tutte le sottofunzioni. È anche accurato per le funzioni ricorsive.

  • percall - È il quoziente di cumtime diviso per chiamate primitive.

  • filename:lineno(function) - Fondamentalmente fornisce i rispettivi dati di ciascuna funzione.