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 è colui che pone spesso i problemi maggiori. Poniamo che voi abbiate un collegamento ad internet di 10 Mbit, e che una pagina richieda 40 kb di banda per essere inviata: sole 25 richieste simultanee satureranno la vostra linea. In questo caso le opzioni sono molteplici: cercare di produrre pagine HTML che pesino il meno possibile sfruttando vari accorgimenti che elencherò successivamente, oppure aumentare la vostra banda in caso in cui anche l'ottimizzazione del peso delle pagine non porti ad ottimi risultati.
  • Connessioni a server esterni possono portare molte volte a rallentamenti di cui difficilmente ci accorgiamo: potrebbe capitare che il nostro server MySQL al quale ci connettiamo sia al momento saturo di richieste, e quindi ci troveremo a dover attendere molto tempo (la parola "molto" è sempre relativa ai tempi di esecuzione media di uno script PHP) anche solo per visualizzare una pagina che contenga il risultato di una semplice query. A mio avviso l'operazione migliore consiste nell'avere a disposizione un server differente per ogni sito o applicazione, in modo da riuscire a comprendere in quali casi i rallentamenti possono essere vìdovuti ad un eccessivo sfruttamento dei server esterni.
  • Computer contenente molti programmi inutili che funzionano background e dei quali non ci accorgiamo. Questi programmi occupano ram e richiedono risorse alla CPU per essere eseguiti. Per questo motivo sarebbe ottimale riuscrie a lavorare su macchine che compiano solamente il lavoro di webserver e che si occupino, anche in questo caso, di offrire solamente i servizi necessari. Se per esempio la macchina si trova nel nostro ufficio e noi siamo gli unici che possiamo caricarci sopra dei file, è inutile aprire un server FTP sulla macchina, visto che l'upload e la modifica dei file possono essere fatti direttamente. Da evitare anche gli screensaver molto complessi: è preferibile spegnere lo schermo.

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 indica il numero massimo di processi figli che possono essere generati contemporaneamente. Ogni richiesta HTTP che implica oltrepassare questo valore viene messa in coda. Più alto è questo numero, meno attese ci saranno in caso di molte richieste simultanee, anche se la CPU e la RAM potrebbero risentirne.
  • StartServers indica il numero di processi figli che devono essere generati all'avvio di Apache. Questi processi figli sono in attesa di ricevere richieste HTTP: per questo motivo le prime richieste effettuate saranno più veloci.
  • Possiamo controllare completamente la gestione dei processi figli agendo sulle variabili MaxSpareServers e MaxRequestPerChild: la prima indica il numero massimo di processi che possono essere attivi; tutti quelli extra verranno terminati. La seconda indica il numero di richieste HTTP che un unico processo figlio piò gestire prima di terminare: 0 significa che il processo non deve mai terminare, mentre consiglio un numero da 300 a 800 in caso sospettate ci possano essere dei problemi di memoria. In caso vi occupiate di siti molto ampi, consiglio di settare le variabili MinSpareServers e MaxSpareServers rispettivamente a 32 e 64.
  • SendBufferSize indica le dimensioni del buffer di output utilizzato dalle connessioni TCP/IP. Settatelo pari alle dimenisioni del file più grande cge può essere normalmente caricato.
  • TimeOut dovrebbe essere settato ad un numero infriore a 300 in caso i vostri script abbiano una bassa latenza.

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:

  • ZendAccellerator: in coppia con il gratuito ZendOptimizer permettere di raggiungere prestazioni veramente straordinarie, molto più degli altri prodotti commerciali e non con cui è stato paragonato. L'unica pecca è il prezzo, ma vi assicuro che ne vale la pena.
  • Turcke MMChache: è un progetto open source molto ben strutturato. Paragonata agli altri prodotti opensource, direi che offre delle ottime prestazioni su qualsiasi piattaforma supportata. Questo sofware è molto utile, e può essere affiancato a tutti gli altri software Turcke.
  • PHP Accelerator: è il progetto open source più conosciuto per quanto riguarda l'ottimizzazione delle prestazioni del codice PHP.

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:

<?php
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
$result = $bench->get();

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 richiede meno risorse rispetto al costrutto print o alla funzione print. Per questo conviene sempre usare echo durante i processi di stampa;
  • Echo accetta parametri separati da una virgola, il che incrementa le prestazioni dato che la concatenazione tramite punti, che richiede la generazione di una nuova stringa, non viene effettuata.
  • PHP esegue il parsing completo delle stringhe racchiuse tra apici doppi, alla ricerca di variabili. Per questo motivo conviene racchiudere tra apici singoli le parti di testo che non contengono variabili, ed utilizzare il concatenamento delle stringhe per migliorare ulteriormente le prestazioni e la leggibilità;
  • 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. Non mi soffermo sull'argomento, visto che potete trovare un ottimo articolo di Fabio Sutto su freephp.html.it. È buona norma racchiudere come intestazione globale di ogni script PHP una chiamata ad ob_start (con eventuali argomenti in caso decidiate di utilizzare qualche tipo di compressione), ed utilizzare ob_end_flush per svuotare periodicamente il buffer in modo da visualizzare qualche informazione all'utente.

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:

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

In questo caso la funzione count viene richiamata una volta per ogni iterazione del ciclo, quindi una volta per ogni valore contenuto nell'array. Di conseguenza converrebbe salvare il valore restituito da count in una variabile, ed eseguire l'iterazione su questo numero:

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, più brevemente questo sarà interpretato.

È 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 sono di gran lunga più lente delle equivalenti scritte in C; per questo motivo cercate sempre di sfruttare le funzioni built in di PHP, ed in caso notiate dei grossi rallentamenti dovuti a funzioni o classi da voi scritte, prendete in considerazione il fatto di riscrivere completamente l'estensione come libreria esterna sfruttando l'API Zend.

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 del Forum di html.it.

Ti consigliamo anche