PHP: come scrivere uno script ad alte prestazioni
PHP potrebbe essere un linguaggio simile a uno script e criticato per essere lento, ma rimane uno dei linguaggi per server Web più utilizzati. Nel tempo, le principali società web che utilizzano PHP hanno cercato di ottimizzare il motore. L'introduzione del concetto di Netturbino nella versione 5.3 è stato un notevole progresso. Ma ci sono molti modi per farlo'ottimizzare le prestazioni di uno script.
Utilizzate principalmente nell'ambito della semplice generazione di pagine, queste ottimizzazioni, sebbene possano portare un certo guadagno, non sono sufficientemente visibili per l'utente. L'ottimizzazione dei database, dei web server con l'utilizzo di Nginx, l'ottimizzazione dei motori con l'arrivo di HHVM o anche HippyVM ti permetteranno semplicemente di velocizzare il rendering delle tue pagine e ottimizzare i tempi di risposta alle tue query in modo più semplice . Nonostante ciò, a volte sviluppiamo script PHP con l'obiettivo di creare trattamento pesanteo i server Web non possono eseguire alcuna operazione.
In questo post, approfondirò tre aree di ottimizzazione che ho recentemente applicato a uno script che doveva elaborare file CSV o XLS contenenti una grande quantità di informazioni. La quantità di memoria utilizzata ha raggiunto 1 GB senza preoccupazioni e potrebbe durare più di 1/2 ora.
Prima di presentarti le tre nozioni PHP che possono permetterti di farloottimizzare i tempi di esecuzione oltre alla quantità di memoria occupata, sappi che prima di lamentarti di un linguaggio X, l'algoritmo del tuo script è responsabile di buona parte della tua elaborazione. C++ può essere infinitamente più veloce di PHP, ma se i tuoi algoritmi C++ sono difettosi, non risolverà immediatamente i tuoi problemi.
Dealloca le sue variabili, limita il consumo di memoria
Prima del rilascio di PHP 5.3, il problema di PHP era il eccessivo consumo di memoria. Inoltre, ho spesso sentito dire che il consumo di memoria di uno script PHP non potrebbe mai essere ridotto... Se è stato vero per un po', fortunatamente non lo è più. Questa gestione utilizza la nozione di Garbage Collector che vedremo poco più avanti.
Una buona abitudine persa...
In alcune lingue, questo era obbligatorio. In PHP, questa nozione è stata completamente dimenticata! Questa piccola funzione nativa unset(), è touna formidabile utilità. È equivalente alla funzione free() in C++ e consente di farlo deallocare e quindi di libera subito la memoria utilizzato dalla variabile. La variabile è completamente distrutta. Questo inizialmente libera PHP dalle variabili inutilizzate. Questo ha un altro interesse, aiutandoci a strutturare meglio i nostri algoritmi. Ovviamente quando un metodo termina le variabili vengono automaticamente deallocate ma vedrai di seguito che questo ti permette di ottimizzare il tempo che il "garbage collector" impiega a lavorare o semplicemente a fare il suo lavoro nel caso in cui venga disattivato. C'è quindi anche un guadagno in termini di velocità. E credimi, il GC consuma molte risorse per liberare memoria.
Come ho detto nel paragrafo precedente, alla fine di una funzione o di un metodo, PHP rimuove le variabili inutilizzate. Ma nel caso di uno script che elabora dati di massa, è possibile che una funzione gestisca le altre. Come in alcune lingue, se si tratta di una funzione principale, probabilmente tenderai a memorizzare le variabili dalla funzione prima di passarle ad altre funzioni. Possiamo finire rapidamente con una grande quantità di dati. Non appena non viene più utilizzato, non ha senso "portarlo" in giro, lo deallochiamo.
Recentemente, mentre scrivevo uno script in cui stavo estraendo un intero file più grande di 15 MB, una volta che l'avevo usato, l'ho cancellato e ho permesso di acquisire memoria !
Comprendere il Garbage Collector
È stato durante la ricerca sulla gestione della memoria che mi sono imbattuto nell'articolo di un collega. In questo articolo ormai un po' datato, Pascal Martin spiega la novità che non è più una, il Garbage Collector.
Cos'è Garbage Collector?
Per chi non lo conoscesse o ne avesse già sentito parlare ma non ha mai avuto modo di utilizzarlo, il Garbage Collector che in francese si traduce come "raccogli briciole", funzionalità di basso livello che all'intervallo X interrompe il processo in esecuzione ed esegue una scansione della memoria allocata dallo script per rilevare quali variabili non sono più accessibili per eliminarle.
Non capisco, prima mi è stato detto che alla fine di un metodo PHP distrugge le variabili.
Questo non è del tutto vero. Allo stesso modo di C o C++ (padre di PHP), alla fine di un metodo, PHP esce e perde il riferimento che aveva sull'oggetto. In C esiste solo la nozione di puntatore, in C++ coesistono le nozioni di puntatore e riferimento.
Una volta perso il riferimento all'oggetto, e una volta avviato il Garbage Collector, quest'ultimo determinerà che la variabile non è più accessibile e la distruggerà.
Questo concetto è molto presente in JAVA così come in altri linguaggi più recenti e di conseguenza di livello superiore.
Cosa ha portato il Garbage Collector?
Il GC ha fornito una notevole flessibilità di sviluppo. È ovvio che lo sviluppo in C è più tecnico che in Java, PHP o altri. Nonostante questo, alcune sfaccettature sono ormai dimenticate come la gestione della memoria ed è il nostro codice che a volte si immedesima. Se questo ha portato flessibilità nel nostro sviluppo, ha anche rallentato l'esecuzione dei nostri programmi.
Con questi strumenti, ciò che non dobbiamo dimenticare è che possiamo sempre controllare la memoria in qualche modo. Questione di volontà, o di necessità...
Infine, penso che questo tipo di strumenti sia molto pratico ma non può sostituire le istruzioni fornite dagli sviluppatori. Da parte mia, trovo che la nozione C++ di shared_ptr
è molto più interessante e potrebbe offrire guadagni di prestazioni fuori misura in futuro se Zend decide di utilizzare questo metodo in alcuni casi.
E in PHP?
Da PHP 5.3 sono state aggiunte quattro funzioni.
- gc_enable
- gc_enabled
- gc_disable
- gc_collect_cycles
Ti lascio il tempo libero di leggere la documentazione, ma per essere veloci ti permettono di attivare il GC, sapere se è attivo, disattivarlo e avviare manualmente la raccolta.
Pascal Martin ha pubblicato nel post che ti ho linkato sopra, uno script in cui esegue una batteria di prova. Possiamo vedere chiaramente nel rapporto di prova che la versione senza GC raggiunge un enorme consumo di memoria, raggiungendo anche il limite e andando in crash.
Segno di riferimento
Ecco il codice eseguito. Questa è stata presa dal blog di Pascal Martin e più in particolare dal suo articolo.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
classe Nodo {
la percezione $parentNode;
la percezione $childNodes = schieramento();
function Nodo() {
$ This->nodeValue = str_repeat('0123456789', 128);
}
}
function creareRelazione() {
$genitore = nuovi nodo();
$bambino = nuovi nodo();
$parent->childNodes[] = $child;
$child->parentNode = $parent;
}
per ($io = 0; $i 500000; $io++) {
creareRelazione();
// Questa parte viene eseguita durante il secondo test
if ($opzioni["gc_manuale"]) {
gc_collect_cycles();
}
}
|
Eseguo tre test separatamente:
- Eseguo questo test con le opzioni di base. Il Garbage Collector è abilitato e PHP lo esegue regolarmente. La sceneggiatura ha una durata 7.55 secondes e la memoria utilizzata ha raggiunto È 20.98.
- Eseguo lo stesso test disabilitando il Garbage Collector e chiamandolo ad ogni turno di loop. Lo script dura 3.79 secondi e la memoria utilizzata ha raggiunto il picco I 244.77.
- Eseguo un terzo test disabilitando il Garbage Collector e non raccogliendo mai manualmente. La memoria deve quindi riempirsi fortemente. Lo script dura 4.46 secondi e la memoria ha raggiunto Vai 1.98.
Con questo test, possiamo vedere chiaramente l'importanza della gestione della memoria. Nel nostro esempio, portato all'estremo, possiamo vedere chiaramente l'importanza. Da un lato, il Garbage Collector lavora molto sul consumo di memoria, ma tenderà a rallentare l'esecuzione dello script. Lo vediamo molto bene confrontando il 1° test (con GC) e il 3° test senza gestione.
Dove viene svolto il nostro lavoro sulle prestazioni è il test 2. Nessuna gestione automatica della memoria, gestione manuale ottimizzata all'interno di questo script. Riusciamo ad accelerare la velocità di elaborazione di quasi due volte (7.55/3.79 = 1.99). In questo modo, abbiamo anche limitato la nostra memoria a 245 KB contro i 21 MB per la gestione automatica. Questo è un coefficiente di quasi 88 ((20.98 * 1024) / 244.77 = 87.77).
Conclusione
Sebbene l'esempio precedente spinga al limite la gestione della memoria, questo test ha lo scopo di mostrarci quanto può essere. importante studiare la gestione della memoria nei nostri script. I pagamenti possono essere impressionanti in alcuni casi. È possibile risparmiare tempo di elaborazione, memoria e tutto ciò che ne consegue.
Comprendere e utilizzare i riferimenti
Come ti ho detto poco prima, PHP implementa il passaggio di variabili per riferimento e, come ti ho detto sopra, questo tipo di passaggio è più o meno equivalente al passaggio per puntatore.
- Comprensione del passaggio per riferimento
- Comprensione del passaggio del puntatore
Questa è una parte importante, non solo per PHP perché questa nozione esisteva molto prima, ma per l'IT. Fondamentalmente, quando dichiari una funzione, il passaggio viene eseguito copiando.// Passa dalla funzione di copia pubblica foo($arg) {…}
Ciò implica alcuni pro e contro a seconda del codice. L'oggetto viene copiato, il che significa che la memoria allocata aumenterà del peso dell'oggetto passato. Questo può essere ridicolo se passi un valore booleano, ma potrebbe essere molto più importante se passi un array, ad esempio.
1
2
3
4
5
6
7
8
9
10
11
|
la percezione function foo() {
$un = 1;
barra($a);
eco $a; //$a è 1
}
la percezione function bar($arg) {
$arg = 2;
}
|
In questo caso vediamo che il valore non è stato modificato, questo può essere interessante se usiamo la variabile $a come base e non vogliamo mai toccarne il valore. Potremmo parlare qui di una nozione che non esiste in PHP, di variabile costante (const).
1
2
3
4
5
6
7
8
9
10
11
12
|
la percezione function foo() {
$un = 1;
$a = barra($a);
eco $a; //$a è 2
}
la percezione function bar($arg) {
$arg = 2;
ritorno $arg;
}
|
Sono sicuro che hai già scritto metodi come questo. In questo caso, è chiaramente un errore. Ovviamente la sintassi è perfettamente vera, ma è stata effettuata un'allocazione per la copia del parametro ed è stata effettuata la deallocazione di una variabile. Chiamate di memoria costose e tempo di elaborazione non necessario. Il modulo sottostante sarebbe equivalente ma molto più veloce.
1
2
3
4
5
6
7
8
9
10
11
|
la percezione function foo() {
$un = 1;
barra($a);
eco $a; //$a è 2
}
la percezione function bar(&$arg) {
$arg = 2;
}
|
Qui abbiamo effettuato esattamente lo stesso trattamento ma è semplicemente più veloce e meno dispendioso.
Questa ottimizzazione è molto semplice da eseguire e può persino rendere il tuo codice più facile da leggere. Esistono diverse sintassi da conoscere per eseguire il passaggio per riferimento.
- Parametro che passa per riferimento (che abbiamo appena visto)
- Funzione restituita per riferimento (non dettagliato qui, perché il motore PHP ottimizza questa parte e non ho sentito alcun guadagno convincente)
- Copia di una variabile per riferimento, poco utile ma formidabile. Ti lascio leggere la documentazione PHP che è estremamente dettagliata. Potresti facilmente imparare cose che ho omesso 🙂
Altre ottimizzazioni
Grazie alle tre ottimizzazioni di cui sopra, non dovresti avere problemi ad accelerare l'elaborazione. Tuttavia, ci sono altri elementi. Ti elenco gli articoli interessanti sulle diverse ottimizzazioni che puoi fare.
- PHP String Concat vs Array Implode
Conclusione
Le tre parti che ti ho appena dettagliato costituiscono una buona alternativa per i miei gusti per ottimizzare i suoi script ed evitare di riavviare gli sviluppi in modo linguaggio più veloce come linguaggio compilato.
Ho fatto questi tre passaggi su uno dei miei copioni e, tra l'altro, ci sono riuscito ridurre il consumo di memoria di 3 circa e ottenere un guadagno di velocità di 2.