Warning: Undefined array key "tbm_guide_level" in /data/websites/htmlit/web/app/themes/htmlit/src/ViewModel/Post/Templates/SingleGuide.php on line 113

Warning: Trying to access array offset on value of type null in /data/websites/htmlit/web/app/themes/htmlit/src/ViewModel/Post/Templates/SingleGuide.php on line 113

Warning: Undefined array key "tbm_guide_level" in /data/websites/htmlit/web/app/themes/htmlit/src/ViewModel/Post/Templates/SingleGuide.php on line 113

Warning: Trying to access array offset on value of type null in /data/websites/htmlit/web/app/themes/htmlit/src/ViewModel/Post/Templates/SingleGuide.php on line 113

Warning: Undefined array key "tbm_guide_level" in /data/websites/htmlit/web/app/themes/htmlit/src/ViewModel/Post/Templates/SingleGuide.php on line 113

Warning: Trying to access array offset on value of type null in /data/websites/htmlit/web/app/themes/htmlit/src/ViewModel/Post/Templates/SingleGuide.php on line 113
Ottimizzare PHP | HTML.it
Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Ottimizzare PHP

Come migliorare le prestazioni del vostro motore PHP: consigli e soluzioni
Come migliorare le prestazioni del vostro motore PHP: consigli e soluzioni
Link copiato negli appunti

In questo articolo ci addentreremo nell'oscuro mondo dell'ottimizzazione di PHP. Dico oscuro visto che le variabili che entrano in gioco al momento di decidere dove e come ottimizzare sono talmente tante che non permettono la stesura di principi base ed utili per tutti. Per questo motivo vi esorto a leggere l'articolo avendo bene in mente che, in base alla situazione in cui vi trovate, alcune scelte potrebbero risultare dannose rispetto ad altre.

L'approccio all'ottimizzazione che intendo seguire non sarà basato solamente sulla modifica del codice PHP affinché questo sia più veloce, ma anche su come modificare i suoi software di supporto (Apache a Mysql su tutti) per rendere le prestazioni ottime. Per motivi di spazio, e data la quantità enorme di possibili situazioni in cui un utente si può trovare, cercherò comunque di essere il più generico possibile, tralasciando concetti troppo specifici.

Molte persone si pongono il problema di quando è meglio ottimizzare, e molte altre rispondono dicendo che il momento migliore per l'ottimizzazione è dopo aver completato l'applicazione. A mio parere, sarebbe preferibile ottimizzare il codice PHP "a moduli", cioè cercare di ottimizzare il codice ogni volta che un file si considera finito o cumunque modificabile solamente nei suoi aspetti più semplici. In questo modo ci evitiamo lo stress di dover riscrivere quasi totalmente l'applicazione.

Scalabilità o velocità?

Quando si decide di affrontare l'ostacolo "ottimizzazione", ci si trova di fronte ad un piccolo bivio: abbiamo bisogno di un'applicazione che sfrutti molte risorse e venga eseguita in pochi centesimi di secondo, oppure un'applicazione che venga eseguita in poco tempo ma richieda molte meno risorse? La scelta è ardua.

Poniamo che A sia lo script più veloce e B quello che sfrutta meno risorse:

Molte volte un file PHP deve essere eseguito più volte dal web server. Al momento della chiamata dello script A, Apache (o qualsiasi altro webserver) si occuperà di far eseguire lo script e di inviare l'output generato al broswer dell'utente. PHP riuscirà ad eseguire lo script A (anche se di esecuzione vera e propria non si tratta) in mezzo centesimo di secondo (0.05), ma questo procedimento richiederà l'utilizzo di molte risorse, presupponiamo 20 megabyte di ram su un totale di 100.

L'esecuzione dello script B richiederà invece più tempo, presupponiamo 0.07 secondi, ma, a differenza dello script A, utilizzerà solo 10 megabyte di ram.

Fino a 5 connessioni simultanee, ogni richiesta HTTP sullo script A verrà eseguita in meno tempo rispetto alle richieste sullo script B. In caso le 5 connessioni simultanee vengano superate, lo script A richiederà accesso alla memoria virtuale, dato che la memoria fisica non basta a contenere tutte le risorse necessarie ad eseguire lo script, perdendo così almeno l' 80-90% delle prestazioni (da 0.05 ogni script verrà eseguito in 0.1), mentre lo script di tipo B potrà continuare a girare tranquillamente fino alle 10 connessioni simultanee.

Risulta molto più arduo scrivere uno script ottimizzato per la velocità rispetto ad uno script ottimizzato per sfruttare poche risorse. Difatti le risorse utilizzate sono una quantità sempre uguale e definita, che non varia quasi mai, mentre la velocità dipende da moltissimi fattori esterni.

La scelta deve essere effettuata studiando la macchina sulla quale ci troviamo, informandoci sul numero massimo di connessioni alla nostra applicazione che potrebbero avvenire simultaneamente, distribuendo adeguatamente il tempo di lavoro.

Come avrete potuto capire, uno script che sfrutta meno risorse ha molta importanza in situazioni nelle quali l'hardware non è dei migliori oppure quando il flusso di richieste http simultanee è molto alto, mentre uno script veloce è utile nei casi in cui l'hardware non ponga grosse limitazioni in quanto a memoria e non vi siano molte connessioni concorrenti.

In base alla scelta effettuata dovremmo cercare di assegnare molto più tempo allo studio di opposti campi di ricerca, soffermandoci comunque su alcuni aspetti basilari che tratterò nel prossimo paragrafo.

I colli di bottiglia

Con "collo di bottiglia" si intende "qualcosa" che causa rallentamenti al nostro script PHP. Trovandoci a dover affrontare una macchina, la prima cosa fondamentale da comprendere è il fatto che i rallentamenti potranno essere causati da moltissimi fattori. Prenderli in considerazione tutti sarebbe solamente una perdita di tempo: capita a volte di impegare molto più tempo per studiare un modo che permetta di eseguire il nostro script 1% più velocemente, quando invece un attento studio di hardware e software ci potrebbero portare a miglioramenti nettamente superiori.

I normali colli di bottiglia sono:

  • Il Network
  • Connessioni a server esterni
  • Computer contenente molti programmi inutili

Molti accorgimenti presi potranno sembrare superflui ed inutili, ma molte volte sono proprio gli aspetti presi meno in considerazione che ci impediscono di avere sotto mano applicazioni reattive e veloci.

Migliorare le prestazioni di Apache

Apache sfrutta un modello di web serving chiamato di pre-forking: al momento dell'avvio dell'applicaizone, questa si occupa di generare un numero prefissato di processi che serviranno le varie richieste HTTP; questi sotto processi sono gestiti dal processo principale che si occupa di coordinare i lavori. Questo tipo di approccio assicura una grande sicurezza, ma richiede anche un po' di lavoro per cercare ottimizzarne le prestazioni. Tengo a precisare che parleremo di apache 1.3: la 2.0 è poco utilizzata e da ancora vari problemi affiancata a PHP, anche se le sue prestazioni sembrano migliori grazie all'utilizzo del modello multi-thread.

Per migliorare le prestazioni di Apache, dovremo lavorare sul file httpd.conf, modificando alcune direttive molto importanti nella gestione dei processi figli:

  • MaxClients
  • StartServers
  • Possiamo controllare completamente la gestione dei processi figli agendo sulle variabili MaxSpareServers MaxRequestPerChild
  • SendBufferSize
  • TimeOut

Programmi utili

Quando un client richiede di visualizzare una pagina, PHP di dovrà occupare di compilare questa pagina in Bytecode, eseguire il bytecode ed inviare l'HTML risultante al browser del client. Il processo di compilazione di uno script è quello che richiede più tempo e risorse.

Da tempo è sorto il problema di cercare di sviluppare sofware che permettessero di mantenere in memoria il bytecode generato da PHP in modo da non richiederne ogni volta la ricompilazione, ed ormai da qualche anno questi software hanno cominiciato a nascere.

Esistono molte valide alternative che cercerò di elencarvi qui sotto:

Sicuramente esistono molti altri validi prodotti che non ho preso in considerazione, ma ritengo che questi siano i migliori in circolazione. Un caldo consiglio è quello di testarli tutti, per comprendere quello più adatto alle vostre esigenze.

Questi programmi, affiancati ad operazioni di caching HTML e di compressione dell'output potranno portare a miglioramenti enormi per quanto riguarda le prestazioni dei vostri siti internet.

Per quanto rigurda il caching, consiglio l'utilizzo di librerie esterne come jpcache o l'ottima libreria fornita da PEAR, che potrete sfruttare per eseguire non solo il caching delle pagine HTML ma anche di variabili e query. I sistemi di caching dovrebbero essere sfruttati soprattutto con script che visualizzano lo stesso tipo di output per periodi di tempo abbastanza lunghi, ma è anche possibile eseguire il caching di pagine che variano molto più di frequente nel caso queste siano visitate molte volte in un breve lasso di tempo.

Purtroppo non posso dilungarmi molto sul caching in questo articolo, ma cercherò comunque di farvi avere il prima possibile molte più informazioni a riguardo.

Ottimizziamo il codice PHP

Cerco ora di addentrarmi nella parte che molti di voi riterranno più interessante per quanto riguarda l'argomento ottimizzazione.

Un insieme di tool molto interessanti sull'argomento può essere trovato sul sito di Sebastian Bergmann, il quale si sta occupando da tempo di fornire agli sviluppatori PHP interessanti librerie per aiutare durante il processo di ottimizzazione e per l'individuazione dei colli di bottiglia. L'utilizzo di questi tool non è molto complesso; segue un semplice esempio:


require_once("Timer.php");

$timer = new Benchmark_Timer;

//Inizio un nuovo time
$timer->start();

//Setto una stringa di riferimento per il benchmark corrente
$timer->set_marker("Benchmark per il loop FOR");

//Eseguo il loop
for ($i = 0; $i < 10000; $i++) {
  print "$in";
}
$timer->set_marker("End For Loop");

$timer->set_marker("Benchmark per il loop WHILE");
$i = 0;
while ($i < 10000) {
  print "$in";
  $i++;
}

$timer->set_marker("End While Loop");

//Salvo un array che contiene il profile dei due spezzoni di codice eseguiti
$profiling = $timer->get_profiling();

for ($i = 0; $i < count($profiling); $i++) {
  print "Nome del Marker: {$profiling[$i][name]}n";
  print "Tempo: {$profiling[$i][time]}n";
  print "Differenza: {$profiling[$i][diff]}n";
  print "Totale: {$profiling[$i][total]}n";
}
?>

In caso voleste utilizzare il tool iterate:

<?php
require_once("Iterate.php");

function print_hello() {
  print "Hello World!!";
}

$bench = new Benchmark_Iterate;
//Eseguo la funzione 200 volte
$bench->run("print_hello", 200);

//Salvo il benchmark

print "La sesta iterazione ha richiesto $result[6], mentre la ventesima ha richiesto took $result[20]n";

print "Il tempo medio di esecuzione è stato di $result[mean] per un totale di $result[iterations] iterazioni...";
?>

Controllo dell'output

Uno degli aspetti da tenere in considerazione è sicuramente quello che riguarda l'ouput: difatti il processo di stampa di una stringa richiede molti cicli di CPU, e bisogna prendere in considerazione vari aspetti, elencati di seguito:

  • Il costrutto echo print
  • Echo
  • PHP esegue il parsing completo delle stringhe racchiuse tra apici doppi
  • Dato che le risorse richieste per la stampa di un dato sono molte, è nettamente meglio utilizzarle il meno possibile; per questo motivo si dovrebbe cercare di raggruppare l'output in una sola variabile, da visualizzare alla fine con un unico comando di stampa.

Altra opzione da prendere in considerazione per quanto riguarda l'elaborazione dell'output è sicuramente l'utilizzo delle funzioni di output buffering articolo

Controllo dei cicli e delle iterazioni

Molte volte nei nostri script incontriamo dei cicli che eseguono iterazioni su un particolare dato. I cicli sono quelli che spesso rallentano di molto i nostri script, visto che molte volte racchiudono nei loro corpi operazioni inutili, oppure operazioni che potrebbero essere svolte al loro esterno. Ogni ciclo viene tradotto da PHP in modo differente: in PHP il ciclo più veloce è il while, seguito dal for. Il foreach, anche se molto comodo, risulta lento e ripetitivo. Ritengo obbligatorio precisare che la differenza di prestazioni tra ciclo while e ciclo fro è minima, e molte volte no rappresenta un problema.

Un grosso problema è invece rappresentato dalle iterazioni su risultati di una funzione: molto spesso ci troviamo di fronte a codice del genere:


echo $array[$i],'
';
}

In questo caso la funzione count

for($i = 0, $tot = count($array); $i < $tot; $i++){
echo $array[$i],'
';
}

Il codice soprastante non è ancora completamente ottimizzato: abbiamo visto che le chiamate ad echo richiedono molte risorse; potremmo modificare il codice nel seguente modo:

for($i = 0, $tot = count($array), $stringa = ''; $i < $tot; $i++){
$stringa .= $array[$i].'
';
}
echo $stringa;

In questo modo ci risparmiamo varie chiamate ad echo raggruppando l'output in un'unica variabile e stampandolo alla fine del ciclo.

Durante le operazioni di parsing, lo zend engine deve controllare ogni singolo carattere contenuto nel file di testo e comprenderne il suo significato in base al contesto in cui si trova. Per questo motivo, più breve sarà il codice scritto

È buona norma seguire le seguenti regole:

  • Omettere le parentesi graffe dopo cicli e costrutti in caso questi debbano eseguire una sola riga di operazioni. In questo modo risparmieremo un po' di lavoro allo zend engine, evitandogli di parsare il blocco di codice per 2 volte;
  • Evitare, dove è possibile, di utilizzare costrutti if/else: molte volte gli else possono essere omessi, con vantaggi per la generazione di bytecode migliore. Oltretutto risulta utile, per diminuire gli assegnamenti, una sintassi tipo $val = $condizione ? $val1 : $val2 rispetto ad usare il corrispettivo if/else
  • La generazione di nuove variabili nella symble table richiede molto tempo: meno saranno le variabili utilizzati, meno risorse richiederà il codice e più velocemente sarà eseguito.

Regola d'oro, da non perdere mai di vista, è il fatto che le funzioni scritte in PHP

Classi ed oggetti

Un discorso a parte dovrebbe essere affrontato per quanto riguarda la programmazione ad oggetti. Gli oggetti sono gestiti, a livello di implementazione, da PHP4 come semplici array. Molti metodi per array funzionano difatti anche per gli oggetti. Dato questo strano modo di interpretare le cose, le applicazioni strutturate ad oggetti sono normalmente più lente se equiparate ad altre scritte usando un approccio puramente procedurale.

Quando ci addentriamo nell'ambito di ottimizzazione delle classi, dobbiamo prendere in considerazione i seguenti parametri:

  • Tutte le variabili dovrebbero essere inizializzate prima del loro utilizzo;
  • Le operazioni sono 3 volte più lente se eseguite su proprietà di oggetti, 2 volte più lente in caso siano eseguite su variabili locali. Per questo motivo è d'obbligo salvare un riferimento alla proprietà in una variabile locale, in caso questa sia acceduta più di 2 volte.
  • Le operazioni su proprietà non inizializzate possono essere fino a 15 volte più lente, e fino a 10 volte più lente se eseguite su variabili locali;
  • Richiamare metodi presenti nelle classi genitore richiede più tempo di esecuzione rispetto a richiamare metodi definiti nella classe figlia;
  • Dichiarare una variabile locale in un metodo senza utilizzarla rallenta di molto il codice, dato che PHP si occupa di controllare se è stata definita come globale;
  • Gli oggetti andrebbero sempre passati per riferimento, e se possibile inizializzati con &new;
  • Lavorare con i riferimenti

    PHP non passa automaticamente i valori ad una funzione per riferimento. Questo significa che ogni parametro passato viene copiato in memoria per poi essere utilizzato all'interno dal blocco di codice eseguito.

    Sfruttare i riferimenti migliora di gran lunga le prestazioni, ma solamente in alcuni casi; difatti dovrebbe essere buona norma passare per riferimento tutti gli argomenti contenenti array ed oggetti i quali, occupando molta memoria, richiederebbero tempo e risorse in caso dovessero essere copiati.

    Diversamente le stringhe ed i numeri non dovrebbero essere passati per riferimento, pena la perdita di prestazioni: difatti PHP si occupa di tenere in memoria il numero di riferimenti fatti ad una data variabile contenente questi due tipi di valori, senza tracciare questi riferimenti. In caso di passaggio tramite & di questi argomenti, il codice sarebbe rallentato, visto che i valori dovrebbero essere tracciati dall'interprete.

    Anche la restituzione di valori per riferimento è un ottimo metodo per ottimizzare il codice. Ricordo che nella versione 5 di PHP tutti gli argomenti saranno passati per riferimento, con netto guadagno sulle prestazioni.

    Conclusioni

    Concludo l'articolo con qualche suggerimento aggiuntivo generale:

    • Cercate di limitare l'utilizzo delle espressioni regolari a casi in cui lo ritenete strettamente necessario;
    • Le funzioni ereg_* sono più lente delle funzioni preg_* (anche se entrambe dovrebbero essere usate con parsimonia);
    • Utilizzate unset su tutte le variabili che avete smesso di utilizzare (soprattutto se queste contengono dati di grosse dimensioni, come array o oggetti);
    • Usate connessioni persistenti a database solo nel caso in cui siate sicuri che queste non possano mai superare il valore di MAX_CONNECTIONS del vostro db Mysql; in caso contrario le connessioni non persistenti rallentano di poco lo script, ma vi permettono di evitare il fallimento di richieste che porterebbe ad un netto rallentamento del sistema;
    • Prendete in considerazione la possibilità di utilizzare mysql_unbuffered_query per eseguire richieste al database che restituiscono molti valori: in questo caso non potrete utilizzare mysql_num_rows, ma verranno utilizzate molte meno risorse visto che la funzione non caricherà in memoria il risultato della selezione. Potreste ottenere dei rallentamenti in caso di piccole operazioni di selezione;

    Spero di essere stato un minimo esauriente; abbiamo trattato moltissimi argomenti, alcuni dei quali solamente scalfiti dato il poco spazio a disposizione. Per chiunque abbia bisogno di chiarimenti, consiglio di fare un salto sulla sezione PHP

Ti consigliamo anche