| 07.01 2010 |
PHP skripti mälukasutuse optimiseerimine: ringsuunamine |
| Postitas Klemens Arro kategooriasse PHP, Kõik |
Iga koodi kirjutamisel tuleb arvestada ka raudvaraga. Kui kood on kirjutatud ilma optimiseerimata, siis võib tekkida suuri probleeme selle jooksutamisega. Esmapilgul ei pruugi see isegi välja paista, kuid mida rohkem inimesi seda sama koodi korraga kasutama hakkab, seda rohkem annavad need vead tunda. Kui koodi kirjutamisel ei ole arvestatud sääraste ohtudega, võib selle tulevikus ümber kirjutamine osutuda väga keeruliseks, kui mitte võimatuks.
Väga hea omadusena on PHP-sse sisse ehitatud prügikorjaja, tänu millele ei pea jälgima objektide viiteid, eraldama objektidele mälu ega eemaldama objekte kui neid ei ole enam vaja. Prügikorjaja hävitab kõik objektid, mis on väljaspool kasutusala ja kõik, millele teised objektid ei viita. Kuid see ei kõrvalda kaugeltki mitte kõiki ohtusid.
Näiteks üks oht, mida paljud ei tea, on see, et PHP prügikorjaja jääb hätta klassides omanik-alam ringsuunamisega (Circular reference), mistõttu võib ka lihtne PHP skript nõuda palju serveri mälu.
Võtame näiteks sellise lihtsa koodi:
class Klass {
public $omanikObjekt;
public $alamObjektid = array();
function Meetod() {
$this->objektVaartus = str_repeat('0123456789', 128);
}
}
function looSuhe() {
$omanik = new Klass();
$alam = new Klass();
$omanik->alamObjektid[] = $alam;
$alam->omanikObjekt = $omanik;
}
Nüüd vaatame kui palju mälu see skript kasutab pärast 10 000 korda looSuhe() funktsiooni esile kutsumist.
echo 'Algselt: ' . number_format(memory_get_usage(), 0, '.', ',') . " baitin";
for($i = 0; $i < 10000; $i++) {
looSuhe ();
}
echo 'Kõrgpunkt: ' . number_format(memory_get_peak_usage(), 0, '.', ',') . " baitin";
echo 'Lõpus: ' . number_format(memory_get_usage(), 0, '.', ',') . " baitin";
Tulemus:
Algselt: 62,224 baiti
Kõrgpunkt: 34,905,216 baiti
Lõpus: 34,905,016 baiti
Nagu näidisest näha, suudab niivõrd lihtne skript ära kasutada 34Mb mälu. Serveris, kus on 1-2Gb mälu, oleks võimalik 30-60 sarnast skripti korraga jooksutada. Kuid näiteks virtuaalserveris, kus on samaaegselt mitmed PHP skriptid ning kus neid skripte kasutavad samaaegselt paljud inimesed ... võid ise ette kujutada, mis siis serveri mäluga toimub ;)
PHP prügikorjajale on ringi suunavate objektide leidmine väga keeruline. Selleks tuleks analüüsida kõiki objekte, mis mälus on, kuid see on väga aeganõudev tegevus. Selle asemel on PHP arendajad otsustanud hävitada kõik objektid skripti lõppedes, neid teistele skriptidele jagamata.
Nagu eelnevalt sai mainitud, hävitab prügikorjaja ainult need objektid, millele teised objektid ei viita. Meie näites viitas objekt $omanik objektile $alam ja vastupidi, mistõttu PHP prügikorjaja ei hävita neid objekte.
Lahenduseks on luua sellesse klassi veel üks meetod, mida saab esile kutsuda pärast kasutamist.
class Klass {
public $omanikObjekt;
public $alamObjektid = array();
function Meetod() {
$this->objektVaartus = str_repeat('0123456789', 128);
}
function havita() {
$this-> omanikObjekt = null;
$this-> alamObjektid = array();
}
}
function looSuhe () {
$omanik = new Klass();
$alam = new Klass();
$omanik->alamObjektid[] = $alam;
$alam->omanikObjekt = $omanik;
$omanik ->havita ();
}
Nüüd uuesti seda koodi testides on juba parem tulemus:
Algselt: 63,848 baiti
Kõrgpunkt: 69,688 baiti
Lõpus: 65,168 baiti
Nagu testki näitas, kukkus mälu kasutus umbes 500 korda. See on juba tõsine kokkuhoid või mis?
Jõudu koodimisel! :)







