PHP: Cum să scrieți un script de înaltă performanță
Agenție web » Știri digitale » PHP: Cum să scrieți un script de înaltă performanță

PHP: Cum să scrieți un script de înaltă performanță

PHP poate fi un limbaj asemănător unui script și criticat pentru că este lent, dar rămâne unul dintre cele mai utilizate limbi pentru servere web. De-a lungul timpului, marile companii web care folosesc PHP au căutat să optimizeze motorul. Introducerea conceptului de Colector de gunoi în versiunea 5.3 a fost un progres notabil. Dar există multe moduri de a„optimizați performanța unui script.

Folosite în principal în cadrul generării simple a paginii, aceste optimizări, deși pot aduce un anumit câștig, nu sunt suficient de vizibile pentru utilizator. Optimizarea bazelor de date, serverelor web cu utilizarea Nginx, optimizarea motorului cu sosirea HHVM sau chiar HippyVM vă va permite pur și simplu să accelerați redarea paginilor și să optimizați timpul de răspuns la întrebările dvs. într-un mod mai simplu. În ciuda acestui fapt, uneori dezvoltăm scripturi PHP cu scopul de a face tratament greu, sau serverele web nu pot face nimic.

În această postare, voi detalia trei domenii de optimizare pe care l-am aplicat recent unui script care trebuia să proceseze fișiere CSV sau XLS care conțineau o cantitate mare de informații. Cantitatea de memorie folosită a ajuns la 1 GB fără griji și ar putea dura mai mult de 1/2 oră.

Înainte să vă prezint cele trei noțiuni PHP care vă pot permiteoptimizati timpul de executie precum și cantitatea de memorie luată, să știți că înainte de a vă plânge de un limbaj X, algoritmul scriptului dvs. este responsabil pentru o bună parte a procesării dvs. C++ poate fi infinit mai rapid decât PHP, dar dacă algoritmii dvs. C++ sunt proai, nu vă va rezolva imediat problemele.

Dealocați variabilele sale, limitați consumul de memorie

Înainte de lansarea PHP 5.3, problema PHP a fost consum excesiv de memorie. În plus, am auzit adesea că consumul de memorie al unui script PHP nu ar putea fi niciodată redus... Dacă asta a fost adevărat pentru o vreme, din fericire, nu mai este adevărat. Acest management folosește noțiunea de Garbage Collector pe care o vom vedea puțin mai jos.

Un obicei bun pierdut...

În unele limbi, acest lucru era obligatoriu. În PHP, această noțiune a fost complet uitată! Această mică funcție nativă unset(), este too utilitate formidabilă. Este echivalent cu funcția free() din C++ și permite dezalocarea și deci de eliberează imediat memoria utilizat de variabilă. Variabila este complet distrusă. Acest lucru eliberează inițial PHP de variabilele neutilizate. Acest lucru are un alt interes, ajutându-ne să ne structurem mai bine algoritmii. Desigur, atunci când o metodă se termină, variabilele sunt dealocate automat, dar veți vedea mai jos că acest lucru vă permite să optimizați timpul pe care „colectorul de gunoi” îl petrece lucrând sau pur și simplu făcându-și treaba în cazul în care este dezactivat. Există așadar și un câștig în ceea ce privește viteza. Și credeți-mă, GC consumă o mulțime de resurse pentru a elibera memorie.

După cum am spus în paragraful anterior, la sfârșitul unei funcții sau metode, PHP elimină variabilele neutilizate. Dar în cazul unui script care prelucrează date în masă, este posibil ca o funcție să le administreze pe celelalte. Ca și în unele limbi, dacă este o funcție principală, probabil că veți avea tendința de a stoca variabile înapoi din funcție înainte de a le trece la alte funcții. Puteți ajunge rapid cu o cantitate mare de date. De îndată ce nu mai este folosit, nu are rost să-l „carăm” în jur, îl dealocam.

Recent, în timp ce scriam un script în care extrageam un întreg fișier mai mare de 15 MB, odată ce l-am folosit, l-am șters și am permis să câștiga memorie !

Înțelegerea colectorului de gunoi

În timpul cercetărilor despre managementul memoriei, am dat peste articolul unui coleg. În acest articol care acum este cam vechi, Pascal Martin explică noutatea care nu mai este una, Garbage Collector.

Ce este Garbage Collector?

Pentru cei care nu știu sau care au auzit deja de el, dar nu au avut niciodată ocazia să-l folosească, Garbage Collector, care este tradus ca „recoltare firimituri” în franceză, funcționalitate de nivel scăzut care la intervalul X, oprește procesul de rulare și efectuează o scanare a memoriei alocate de script pentru a detecta care variabile nu mai sunt accesibile pentru a le șterge.

Nu înțeleg, mai devreme mi s-a spus că la sfârșitul unei metode PHP distruge variabilele.
Acest lucru nu este în întregime adevărat. La fel ca C sau C++ (părinte al PHP), la sfârșitul unei metode, PHP iese și pierde referința pe care o avea asupra obiectului. În C există doar noțiunea de pointer, în C++ noțiunile de pointer și referință coexistă.

Odată ce referința la obiect a fost pierdută și odată ce Garbage Collector pornește, acesta din urmă va determina că variabila nu mai este accesibilă și o va distruge.

Acest concept este foarte prezent în JAVA, precum și în alte limbi mai recente și, în consecință, de nivel superior.

Ce a adus Gunoiul?

GC a oferit o flexibilitate considerabilă de dezvoltare. Este evident că dezvoltarea în C este mai tehnică decât în ​​Java, PHP sau altele. În ciuda acestui fapt, unele fațete sunt acum uitate, cum ar fi gestionarea memoriei și codul nostru este cel care uneori empatizează. Dacă acest lucru a adus flexibilitate în dezvoltarea noastră, a încetinit și execuția programelor noastre.

Cu aceste instrumente, ceea ce nu trebuie să uităm este că putem întotdeauna controla memoria într-un fel. Chestiune de voință sau de necesitate...

În sfârșit, cred că acest tip de instrumente sunt foarte practice, dar nu pot înlocui instrucțiunile date de dezvoltatori. Din partea mea, constat că noțiunea C++ de shared_ptr este mult mai interesant și ar putea oferi câștiguri uriașe de performanță în viitor dacă Zend decide să folosească această metodă în unele cazuri.

Și în PHP?

Începând cu PHP 5.3, au fost adăugate patru funcții.

  • gc_enable
  • gc_activat
  • gc_disable
  • gc_collect_cycles
    Vă las liber să citiți documentația, dar să fiți rapid, vă permit să activați GC, să știți dacă este activ, să îl dezactivați și să porniți manual colecția.

Pascal Martin a publicat în postarea pe care v-am legat-o mai sus, un script în care efectuează o baterie de test. Putem vedea clar în raportul de testare că versiunea fără GC atinge un consum enorm de memorie, ajungând chiar la limită și se prăbușește.

Benchmark

Aici este codul executat. Acesta a fost preluat de pe blogul lui Pascal Martin și mai ales din articolul său.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
clasă Nod {
public $parentNode;
public $childNodes = mulțime,
funcţie Nod() {
$ asta->nodeValue = str_repeat(„0123456789”, 128);
}
}
funcţie createRelationship() {
$părinte= nou nodul();
$copil = nou nodul();
$parent->childNodes[] = $copil;
$copil->parentNode = $părinte;
}
pentru ($i = 0; $i 500000; $i++) {
createRelationship();
// Această parte este executată în timpul celui de-al doilea test
if ($opțiuni[„gc_manual”]) {
gc_collect_cycles();
}
}

Eu efectuez trei teste separat:

  1. Eu efectuez acest test cu opțiunile de bază. Garbage Collector este activat și PHP îl rulează în mod regulat. Scenariul are durata 7.55 secondes iar memoria folosită a ajuns 20.98 Mo.
  2. Eu efectuez același test dezactivând Garbage Collector și apelându-l la fiecare rotire a buclei. Scriptul durează 3.79 secunde iar memoria folosită a atins apogeul 244.77 KB.
  3. Efectuez un al treilea test dezactivând Garbage Collector și niciodată colectarea manuală. Prin urmare, memoria trebuie să se umple puternic. Scriptul durează 4.46 secunde iar amintirea a ajuns 1.98 Go.

Cu acest test, putem vedea clar importanța gestionării memoriei. În exemplul nostru, care este dus la extrem, putem vedea clar importanța. Pe de o parte, Garbage Collector lucrează mult la consumul de memorie, dar asta va tinde să încetinească execuția scriptului. O putem vedea foarte bine comparând primul test (cu GC) și al treilea test fără management.

Unde se face munca noastră de performanță este la testul 2. Fără gestionare automată a memoriei, management manual optimizat în cadrul acestui script. Reușim să accelerăm viteza de procesare de aproape două ori (7.55 s / 3.79 s = 1.99). În acest fel, de asemenea, ne-am limitat memoria la 245 KB contra 21 MB pentru management automat. Acesta este un coeficient de aproape 88 ( (20.98 * 1024) / 244.77 = 87.77).

Concluzie

Deși exemplul de mai sus împinge gestionarea memoriei la limită, acest test are scopul de a ne arăta cât de mult poate fi. important să studiem managementul memoriei în scripturile noastre. Plățile pot fi impresionante în unele cazuri. Timpul de procesare, memoria și tot ce este însoțit de el pot fi salvate.

Înțelegeți și utilizați referințele

După cum v-am spus puțin mai devreme, PHP implementează trecerea variabilelor prin referință și, așa cum v-am spus mai sus, acest tip de trecere este mai mult sau mai puțin echivalent cu trecerea prin indicator.

  • Înțelegerea trecerii prin referință
  • Înțelegerea trecerii indicatorului
    Aceasta este o parte importantă, nu numai pentru PHP, deoarece această noțiune a existat cu mult înainte, ci și pentru IT. Practic, atunci când declari o funcție, trecerea se face prin copiere.

    // Treceți prin funcția de copiere publică foo($arg) {…}

    Asta implică unele argumente pro și contra in functie de codul tau. Obiectul este copiat, ceea ce înseamnă că memoria alocată va crește cu greutatea obiectului trecut. Acest lucru poate fi ridicol dacă treceți un boolean, dar poate fi mult mai important dacă treceți o matrice, de exemplu.

1
2
3
4
5
6
7
8
9
10
11
public funcţie foo() {
$a = 1;
bar($a);
ecou $a; //$a este 1
}
public funcţie bar($arg) {
$arg = 2;
}

În acest caz, vedem că valoarea nu a fost modificată, acest lucru poate fi interesant dacă folosim variabila $a ca bază și nu vrem niciodată să-i atingem valoarea. Am putea vorbi aici de o notiune care nu exista in PHP, de variabila constanta (const).

1
2
3
4
5
6
7
8
9
10
11
12
public funcţie foo() {
$a = 1;
$a = bar($a);
ecou $a; //$a este 2
}
public funcţie bar($arg) {
$arg = 2;
reveni $arg;
}

Sunt sigur că ai scris deja metode ca aceasta. În acest caz, este clar o greșeală. Desigur, sintaxa este perfect adevărată, dar s-a făcut o alocare pentru copia parametrului și s-a făcut dealocarea unei variabile. Apeluri de memorie costisitoare și timp de procesare inutil. Formularul de mai jos ar fi echivalent, dar mult mai rapid.

1
2
3
4
5
6
7
8
9
10
11
public funcţie foo() {
$a = 1;
bar($a);
ecou $a; //$a este 2
}
public funcţie bar(&$arg) {
$arg = 2;
}

Aici am efectuat exact același tratament, dar pur și simplu este mai rapid și mai puțin consumator.

Această optimizare este foarte simplu de efectuat și poate chiar face codul mai ușor de citit. Există mai multe sintaxe de știut pentru a face trecerea prin referință.

  1. Parametru care trece prin referință (pe care tocmai l-am văzut)
  2. Returul funcției prin referință (nu este detaliat aici, deoarece motorul PHP optimizează această parte și nu am simțit niciun câștig convingător)
  3. Copierea unei variabile prin referință, nu foarte util, dar formidabil. Vă las să citiți documentația PHP care este extrem de detaliată. Ai putea învăța cu ușurință lucruri pe care le-am omis 🙂

Altă optimizare

Datorită celor trei optimizări de mai sus, nu ar trebui să aveți nicio problemă în accelerarea procesării. Cu toate acestea, există și alte articole. Vă enumerez articolele interesante despre diferite optimizări pe care le puteți face.

  • PHP String Concat vs Array Implode

Concluzie

Cele trei părți pe care tocmai vi le-am detaliat constituie o alternativă bună pentru gustul meu pentru a-și optimiza scripturile și a evita repornirea dezvoltărilor într-un limbaj mai rapid ca limbaj compilat.

Am făcut aceste trei treceri pe unul dintre scenariile mele și, printre altele, am reușit reduce consumul de memorie cu 3 aproximativ și obține un câștig de viteză cu 2.

★ ★ ★ ★ ★