Un benchmark non è altro che un test effettuato su un'applicazione per valutarne le prestazioni, in particolare si tratta di una procedura tesa a stimare le performance di un software con particolare attenzione alla sua velocità nell'esecuzione degli scopi per cui è stato concepito; grazie al benchmark è anche possibile effettuare dei confronti tra diverse soluzioni in modo da individuare la più performante tra di esse.
Questa tipologia di test può essere utilizzata anche per valutare il funzionamento degli script PHP ed eventualmente per apportare migliorie al loro listato sulla base dei risultati ottenuti; per questo motivo, in questa breve trattazione saranno descritte delle semplici procedure per il benchmark delle applicazioni in PHP che potrebbero aiutare lo sviluppatore a scrivere codici più efficaci, puliti e veloci in sede di produzione.
Un esempio pratico di benchmark: il test di un ciclo
Come è noto i cicli sono conosciuti anche con il come di costrutti di iterazione, questo perché la loro funzione è quella di ripetere un determinato comportamento sulla base di una clausola detta condizione di terminazione del ciclo; nel caso di cicli in cui alla condizione di terminazione corrisponda un valore basso, il benchmark può essere una procedura inutile, valida solo per testare intervalli di tempo impercettibili in fase di esecuzione. Il discorso cambia nel caso in cui il ciclo richieda un alto numero di iterazioni.
Si osservino i due cicli for
contenuti nei seguenti listati:
Listato 1
<?php # registrazione del momento iniziale del test $inizio_benchmark = (float) array_sum(explode(' ', microtime ())); # definizione di un array $ a = array_fill(0, 20000, "HTML.it"); # esecuzione del ciclo for ($i = 1; $i <= count($a); ++$i) { echo $i."<br/>n"; } # registrazione del momento finale del test $fine_benchmark = (float) array_sum(explode(' ',microtime())); # esito del benchmark echo "Esito in secondi: ". sprintf("%.5f", ($fine_benchmark-$inizio_benchmark)); ?>
Listato 2
<?php $inizio_benchmark = (float) array_sum(explode(' ',microtime())); $a = array_fill(0, 20000, "HTML.it"); $x=count($a); for ($i = 1; $i <= $x; ++$i) { echo $i."<br/>n"; } $fine_benchmark = (float) array_sum(explode(' ',microtime())); echo "Esito in secondi: ". sprintf("%.5f", ($fine_benchmark-$inizio_benchmark)); ?>
I due codici proposti, molto simili, sono stati concepiti per lo stesso scopo e funzionano nello stesso modo tranne che per la diversa procedura con cui è stata proposta la condizione di terminazione al ciclo for
.
Elencando gli elementi che accomunano le due piccole applicazioni è possibile dire che in entrambe:
- verrà calcolato il momento iniziale del test a cui dovrà essere sottoposto lo script, questo sarà possibile grazie alla funzione
microtime()
che avrà il compito di restituire uno UNIX Timestamp completo di millisecondi; - la funzione
microtime()
, produrrà una stringa composta da una serie di valori separati da uno spazio che potranno essere ridotti ad elementi di un array grazie alla funzioneexplode()
, semplicemente utilizzando lo spazio vuoto come delimitatore; - l'array creato tramite la funzione
explode()
conterrà dei valori che potranno essere sommati ottenendo il valore decimale (float
) corrispondente al momento iniziale del benchmark; - verrà creato un array, associato alla variabile
$a
, per il quale grazie alla funzionearray_fill()
sarà possibile definire l'indice iniziale (0
), il numero di valori contenuti (20.000
, per un in dice numerico che andrà da0 a 19.999
) e i valori stessi (una stringaHtml.it
ripetuta per 20.000 volte); - tramite un ciclo
for
verrà generato un numero di iterazioni pari a quello dei valori contenuti nell'array (condizione di terminazione); - alla fine del ciclo di iterazione verrà memorizzato il momento finale del benchmark che sarà rilavato utilizzando una procedura identica a quella precedentemente esposta per la registrazione del momento iniziale;
- al valore finale del benchmark verrà sottratto quello iniziale, il risultato di questa operazione sarà un intervallo di tempo espresso in secondi con 5 cifre decimali (per ottenere la massima precisione nel calcolo).
Si passi ora all'analisi delle differenze tra i due listati:
- nel primo script il numero degli elementi che compongono l'array
$a
verrà contato all'interno del ciclo for (for ($i = 0; $i <= count($a); ++$i)
), la funzionecount()
ha infatti il compito di restituire una cifra pari a tutti gli elementi presenti in un vettore; - anche nel secondo script verrà utilizzata la funzione
count()
, ma l'output di questa sarà associato come valore ad una nuova variabile ($x=count($a)
) che verrà poi utilizzata come condizione di terminazione del ciclo for (for ($i = 0; $i <= $x; ++$i)
).
Ma vediamo i tempi di esecuzione.
È ora giunto il momento relativo al test dei due script; per quanto riguarda il benchmark operato per la stesura di questo articolo sono stati ottenuti i seguenti tempi di esecuzione:
- 0.64800 per il test del primo script;
- 0.28100 per il test del secondo script.
Naturalmente questi valori non possono essere presi come assoluti, molto dipende dalle risorse a disposizione della macchina in cui viene operato il test e dai processi in esecuzione al momento del benchmark. Una possibilità per ottenere dei valori che siano quanto più possibile indicativi è quella di ripetere più volte le procedure di benchmark e ricavare in questo modo dei valori medi.
In ogni caso è possibile motivare facilmente la differenza tra i due intervalli registrati: nel primo script infatti la funzione count()
viene ripetuta per ogni iterazione del ciclo (20.000 volte), nel secondo caso invece viene eseguito un precalcolo degli elementi contenuti all'interno dell'array, in questo modo verrà passata al ciclo una condizione di terminazione precedentemente definita; riottenere questo parametro ad ogni interazione rappresenterebbe semplicemente un peso in più in fase di esecuzione.
Dal benchmark eseguito si ottiene anche una seconda informazione, infatti il secondo script è più lungo del primo, questo però non lo rende meno efficiente, esso presenta una riga in più e la definizione di una nuova variabile; se ne evince che non necessariamente listati più brevi si traducono in tempi di esecuzione più rapidi.
È importante infine sapere che il tempo di esecuzione registrato dal benchmark non va confuso con il tempo impiegato dal browser per mostrare il risultato dell'esecuzione (cioè non rappresenta il tempo di caricamento di una pagina), si tratta invece del tempo impiegato dal Web server per eseguire la porzione di codice destinata a produrre il comportamento desiderato. Per il calcolo degli intervalli lato client sarebbe più indicato ricorrere ad una soluzione concepita in JavaScript.
Benchmark tramite una funzione definita dall'utente
È possibile riprodurre la breve procedura per il benchmark proposta nel capitolo precedente sotto forma di una semplice funzione definita dall'utente, che potrà essere richiamata ogni volta che si desidera effettuare un test su un'applicazione o un confronto tra due porzioni di codice concepite per produrre lo stesso comportamento e il medesimo risultato.
Si osservi il seguente listato:
<?php # definzione della funzione function benchmark() { # assegnazione delle variabili list($usec, $sec) = explode(" ",microtime()); # valore di ritorno return ((float)$usec + (float)$sec); } ?>
Il codice esposto presenta una funzione a cui non vengono passati parametri e che utilizza il costrutto list()
per assegnare una lista di variabili ad una determinata operazione; in questo caso le variabili saranno associate al risultato della funzione explode()
applicata sui timestamp prodotti da microtime()
e delimitati da uno spazio vuoto.
La funzione appena creata potrà essere richiamata all'interno di una qualsiasi applicazione che si desidera testare; il momento iniziale e quello finale del benchmark potranno essere registrati attraverso la valorizzazione di due variabili tramite la chiamata alla funzione definita dall'utente, queste variabile dovranno essere valorizzate immediatamente prima e immediatamente dopo l'esecuzione della porzione di codice da testare.
La funzione round()
permetterà di ottenere l'esito del benchmark (la differenza tra i due timestamp memorizzati nelle variabili) espresso in secondi e seguito da 5 cifre decimali; di seguito potrà essere visualizzato un esempio pratico per l'utilizzo della funzione descritta:
<?php # chiamata alla pagina che contiene al funzione @require("benchmark.php"); # registrazione del momento iniziale del test $inizio_benchmark = benchmark(); $a = array_fill(0, 20000, "HTML.it"); $x=count($a); for ($i = 1; $i <= $x; ++$i) { echo $i."<br/>n"; } # registrazione del momento iniziale del test $fine_benchmark = benchmark(); # esito con restituzione dell'intervallo in secondi con 5 decimali echo "Esito in secondi: ". round($fine_benchmark-$inizio_benchmark,5); ?>
Nuovi strumenti per il benchmark in PHP 5
Gli esempi fatti in precedenza possono essere utilizzati sia nelle versioni 4 che 5 di PHP, per coloro che però lavorano direttamente su piattaforme basate su PHP 5 (scelta consigliabile), questa versione del linguaggio offre una funzione microtime()
migliorata con l'aggiunta del parametro get_as_float
che potrà essere utilizzato passando alla funzione l'argomento TRUE; in questo modo l'output registrato dalla funzione verrà automaticamente restituito in secondi e completo di decimali.
Sulla base di questo nuovo parametro il secondo esempio di benchmark proposto nel primo capitolo potrà essere tradotto nel modo seguente:
<?php $inizio_benchmark = microtime(true); $a = array_fill(0, 20000, 'HTML.it'); $x=count($a); for ($i = 1; $i <= $x; ++$i) { echo $i."<br/>n"; } $fine_benchmark = microtime(true); $esito = $fine_benchmark - $inizio_benchmark; echo "Esito in secondi: ". $esito; ?>
Inoltre, può essere utile sapere che, a partire dalla versione 5.1 di PHP, il timestamp relativo al momento iniziale di una richiesta viene memorizzato all'interno di una variabile d'ambiente denominata $_SERVER['REQUEST_TIME']
, uno strumento in più a disposizione per coloro che sono alla ricerca di costrutti nativi per le procedure di benchmark delle applicazioni in PHP.
Conclusioni
Le procedure di benchmark sono dei semplici test per verificare le prestazioni di una determinata applicazione o per confrontare l'efficienza di due porzioni di codice destinate allo stesso scopo; in generale è possibile dire che una maggiore velocità di esecuzione corrisponde ad un migliore esito del benchmark.
Effettuare i test delle applicazioni scritte in PHP non è molto complesso, nel corso di questa breve trattazione sono state mostrate alcune semplici procedure basate sulle funzioni messe a disposizione da PHP per la misurazione degli intervalli di esecuzione.