PHP: Hur man skriver ett högpresterande skript
PHP kan mycket väl vara ett skriptliknande språk och kritiseras för att vara långsamt, men det är fortfarande ett av de mest använda webbserverspråken. Med tiden har de stora webbföretagen som använder PHP försökt optimera motorn. Införandet av begreppet Skräp samlare i version 5.3 var ett anmärkningsvärt framsteg. Men det finns många sätt att'optimera prestandan för ett skript.
Dessa optimeringar används huvudsakligen inom ramen för den enkla genereringen av sidan, även om de kan ge en viss vinst, men de är inte tillräckligt synliga för användaren. Optimeringen av databaser, webbservrar med användning av Nginx, motoroptimering med ankomsten av HHVM eller till och med HippyVM kommer att tillåta dig att helt enkelt påskynda renderingen av dina sidor och optimera tidpunkten för svar på dina frågor på ett enklare sätt. Trots detta utvecklar vi ibland PHP-skript med syftet att göra tung behandling, eller så kan webbservrar inte göra någonting.
I det här inlägget kommer jag att detaljera tre områden för optimering som jag nyligen tillämpade på ett skript som var tvungen att bearbeta CSV- eller XLS-filer som innehöll en stor mängd information. Mängden minne som användes nådde 1 GB utan bekymmer och kunde hålla i mer än 1/2 timme.
Innan jag presenterar för dig de tre PHP-begrepp som kan tillåta digoptimera exekveringstiden samt mängden minne som tas, vet att innan du klagar på ett X-språk är algoritmen i ditt skript ansvarig för en stor del av din bearbetning. C++ kan vara oändligt mycket snabbare än PHP, men om dina C++-algoritmer är dåliga kommer det inte att lösa dina problem omedelbart.
Deallokera dess variabler, begränsa minnesförbrukningen
Innan PHP 5.3 släpptes var PHPs problem överdriven minnesförbrukning. Dessutom har jag ofta hört att minnesförbrukningen för ett PHP-skript aldrig skulle kunna minskas... Om det var sant ett tag så stämmer det lyckligtvis inte längre. Den här ledningen använder begreppet Garbage Collector som vi kommer att se lite längre ner.
En god vana förlorad...
På vissa språk var detta obligatoriskt. I PHP har denna uppfattning glömts bort helt! Denna lilla inbyggda funktion unset(), är tillen formidabel nytta. Det motsvarar funktionen free() i C++ och tillåter det deallokera och därför av frigör omedelbart minnet används av variabeln. Variabeln är helt förstörd. Detta frigör till en början PHP från oanvända variabler. Detta har ett annat intresse och hjälper oss att bättre strukturera våra algoritmer. Naturligtvis, när en metod slutar deallokeras variablerna automatiskt, men du kommer att se nedan att detta låter dig optimera den tid som "sopsamlaren" spenderar på att arbeta eller helt enkelt göra sitt jobb i händelse av att den avaktiveras. Det finns därför också en vinst i form av hastighet. Och tro mig, GC:n förbrukar mycket resurser för att frigöra minne.
Som jag sa i föregående stycke, i slutet av en funktion eller metod, tar PHP bort oanvända variabler. Men i fallet med ett skript som bearbetar massdata är det möjligt för en funktion att administrera de andra. Som på vissa språk, om det är en huvudfunktion, tenderar du förmodligen att lagra variabler tillbaka från funktionen innan du skickar dem till andra funktioner. Vi kan snabbt få en stor mängd data. Så fort den inte används längre är det ingen idé att "bära" runt den, vi deallokerar det.
Nyligen, när jag skrev ett skript där jag extraherade en hel fil större än 15 MB, när jag väl hade använt den, tog jag bort den och tillät vinna minne !
Förstå sopsamlaren
Det var när jag undersökte minneshantering som jag stötte på en kollegas artikel. I den här artikeln som nu är lite gammal förklarar Pascal Martin nyheten som inte längre är en, Sophämtaren.
Vad är Garbage Collector?
För de som inte vet eller som redan har hört talas om det men aldrig haft möjlighet att använda det, Garbage Collector som översätts som "crumb pick-up" på franska, låg funktionalitet som vid intervall X, stoppar den pågående processen och utför en skanning av minnet som tilldelats av skriptet för att upptäcka vilka variabler som inte längre är tillgängliga för att radera dem.
Jag förstår inte, tidigare fick jag höra att i slutet av en metod förstör PHP variablerna.
Detta är inte helt sant. På samma sätt som C eller C++ (förälder till PHP), avslutas PHP i slutet av en metod och förlorar referensen som den hade på objektet. I C existerar bara begreppet pekare, i C++ existerar begreppen pekare och referens samtidigt.
När referensen till objektet har försvunnit, och när sopsamlaren startar, kommer den senare att avgöra att variabeln inte längre är tillgänglig och kommer att förstöra den.
Detta koncept är mycket närvarande i JAVA såväl som i andra nyare språk och följaktligen på högre nivå.
Vad kom sopsamlaren med?
GC gav avsevärd utvecklingsflexibilitet. Det är uppenbart att utveckling i C är mer tekniskt än i Java, PHP eller andra. Trots detta är vissa aspekter nu bortglömda såsom minneshantering och det är vår kod som ibland känner empati. Om detta gav flexibilitet i vår utveckling, bromsade det också genomförandet av våra program.
Med dessa verktyg får vi inte glömma att vi alltid kan styra minnet på något sätt. Fråga om vilja eller nödvändighet...
Slutligen tror jag att den här typen av verktyg är väldigt praktiska men de kan inte ersätta instruktionerna från utvecklarna. För min del tycker jag att C++ begreppet shared_ptr
är mycket mer intressant och skulle kunna erbjuda överdimensionerade prestandavinster i framtiden om Zend bestämmer sig för att använda denna metod i vissa fall.
Och i PHP?
Sedan PHP 5.3 har fyra funktioner lagts till.
- gc_enable
- gc_enabled
- gc_disable
- gc_collect_cycles
Jag låter dig läsa dokumentationen, men för att vara snabb låter de dig aktivera GC, veta om den är aktiv, avaktivera den och starta insamlingen manuellt.
Pascal Martin publicerade i inlägget som jag länkade till dig ovan, ett manus där han utför ett testbatteri. Vi kan tydligt se i testrapporten att versionen utan GC når enorm minnesförbrukning, till och med når gränsen och kraschar.
riktmärke
Här är den körda koden. Detta togs från Pascal Martins blogg och mer specifikt från hans artikel.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
klass Nod {
allmän $parentNode;
allmän $childNodes = array();
fungera Nod() {
$ detta->nodeValue = str_repeat('0123456789', 128);
}
}
fungera skapa Relation() {
$förälder= ny nod();
$barn = ny nod();
$parent->childNodes[] = $child;
$child->parentNode = $parent;
}
för ($i = 0; $i 500000; $i++) {
createRelationship();
// Denna del exekveras under det andra testet
if ($options["gc_manual"]) {
gc_collect_cycles();
}
}
|
Jag utför tre tester separat:
- Jag utför detta test med de grundläggande alternativen. Garbage Collector är aktiverad och PHP kör den regelbundet. Manuset har en varaktighet 7.55 secondes och det använda minnet har nått 20.98 Mo.
- Jag utför samma test genom att inaktivera Garbage Collector och anropa den vid varje slingvarv. Manuset varar i 3.79 sekunder och minnet som användes nådde sin topp 244.77 KB.
- Jag utför ett tredje test genom att inaktivera Garbage Collector och aldrig manuellt samla in. Minnet måste därför fyllas kraftigt. Manuset varar i 4.46 sekunder och minnet har nått 1.98 Go.
Med detta test kan vi tydligt se vikten av minneshantering. I vårt exempel, som tas till det yttersta, kan vi tydligt se vikten. Å ena sidan gör Garbage Collector mycket arbete med minnesförbrukning, men det kommer att tendera att sakta ner skriptexekveringen. Vi kan se det mycket väl genom att jämföra det 1:a testet (med GC) och det 3:e testet utan ledning.
Där vårt prestationsarbete utförs är på test 2. Ingen automatisk minneshantering, manuell hantering optimerad inom detta skript. Vi lyckas accelerera bearbetningshastigheten med nästan två gånger (7.55s / 3.79s = 1.99). På det här sättet, vi begränsade också vårt minne till 245 KB mot 21 MB för automatisk hantering. Det är en koefficient på nästan 88 ( (20.98 * 1024) / 244.77 = 87.77).
Slutsats
Även om exemplet ovan pressar minneshanteringen till det yttersta, är detta test avsett att visa oss hur mycket det kan vara. viktigt att studera minneshantering i våra manus. Utbetalningarna kan vara imponerande i vissa fall. Bearbetningstid, minne och allt som hör till kan sparas.
Förstå och använda referenser
Som jag berättade lite tidigare, implementerar PHP variabel överföring genom referens, och som jag sa ovan är denna typ av överföring mer eller mindre likvärdig med att passera med pekare.
- Förstå pass-by-referens
- Förstå pekarpassering
Detta är en viktig del, inte bara för PHP eftersom denna föreställning funnits långt tidigare, utan för IT. I grund och botten, när du deklarerar en funktion, görs passagen genom att kopiera.// Passera den offentliga kopieringsfunktionen foo($arg) {…}
Det innebär några för- och nackdelar beroende på din kod. Objektet kopieras, vilket innebär att det tilldelade minnet ökar med vikten av det passerade objektet. Detta kan vara löjligt om du klarar en boolean, men det kan vara mycket viktigare om du till exempel klarar en array.
1
2
3
4
5
6
7
8
9
10
11
|
allmän fungera foo() {
$a = 1;
bar($a);
missar $a; //$a är 1
}
allmän fungera bar($arg) {
$arg = 2;
}
|
I det här fallet ser vi att värdet inte har modifierats, detta kan vara intressant om vi använder variabeln $a som bas och vi aldrig vill röra dess värde. Vi skulle här kunna tala om en föreställning som inte finns i PHP, om en konstant variabel (const).
1
2
3
4
5
6
7
8
9
10
11
12
|
allmän fungera foo() {
$a = 1;
$a = bar($a);
missar $a; //$a är 2
}
allmän fungera bar($arg) {
$arg = 2;
avkastning $arg;
}
|
Jag är säker på att du redan har skrivit sådana här metoder. I det här fallet är det helt klart ett misstag. Naturligtvis är syntaxen helt sann, men en allokering har gjorts för kopian av parametern, och avallokeringen av en variabel har gjorts. Dyra minnessamtal och onödig handläggningstid. Formen nedan skulle vara likvärdig men mycket snabbare.
1
2
3
4
5
6
7
8
9
10
11
|
allmän fungera foo() {
$a = 1;
bar($a);
missar $a; //$a är 2
}
allmän fungera bar(&$arg) {
$arg = 2;
}
|
Här genomförde vi exakt samma behandling men det är helt enkelt snabbare och mindre konsumerande.
Denna optimering är mycket enkel att utföra och kan till och med göra din kod lättare att läsa. Det finns flera syntaxer att känna till för att göra pass-by-referens.
- Parameter passerar genom referens (som vi just har sett)
- Funktionsretur genom referens (inte detaljerat här, eftersom PHP-motorn optimerar den här delen och jag kände ingen övertygande vinst)
- Kopiera en variabel genom referens, inte särskilt användbart men formidabelt. Jag låter dig läsa PHP-dokumentationen som är extremt detaljerad. Man kunde lätt lära sig saker som jag utelämnade 🙂
Annan optimering
Tack vare de tre optimeringarna ovan bör du inte ha några problem att påskynda din bearbetning. Det finns dock andra föremål. Jag listar dig de intressanta artiklarna om olika optimeringar som du kan göra.
- PHP String Concat vs Array Implode
Slutsats
De tre delarna som jag just har detaljerat för dig utgör ett bra alternativ för min smak för att optimera dess skript och undvika att starta om utvecklingen i en snabbare språk som ett sammanställt språk.
Jag gjorde de här tre passen på ett av mina manus och det lyckades jag bland annat minska minnesförbrukningen med 3 ungefär och uppnå en hastighetsökning med 2.