ugrás a tartalomhoz

Három kiterjesztés a Zend motor működésének megismeréséhez

Balogh Tibor · 2005. Dec. 11. (V), 19.15
Három kiterjesztés a Zend motor működésének megismeréséhez
Három olyan modulról szeretnék írni, amik segítségével jobban megismerhetjük a PHP nyelvet, betekintést kaphatunk a Zend motor működésébe, és elrejthetjük a forráskódunkat a kíváncsi szemek elől. A cikk szerzője egy összefogott leírást szeretne adni a modulokról, de ez az írás nem helyettesítheti a kiterjesztések kézikönyvét, csupán bevezető lehet a használatukhoz.

Ez a három modul a Vulcan Logic Disassembler, az Advanced PHP Debugger és az eAccelerator. A cikk írását az indította el, hogy mindhárom modulnál átírtam egy-egy részt, amik összességében jelentősebb módosítások lettek, amit esetleg érdemes lehet közzétenni. A kiterjesztések szabadon használhatóak, a licencelésükre vonatkozó feltételek nem változtak.

Vulcan Logic Disassembler v0.8

Minden processzor bizonyos utasításkészlettel rendelkezik, ezek alacsony színtű utasítások. Például egy C compiler az adott gépnek megfelelő utasításkészletre fordítja le a forráskódunkat, amit a processzor az operációs rendszeren keresztül közvetlenül végre tud hajtani. A weblapok programozásakor az egyik problémát az jelentheti, hogy nem tudjuk, milyen gépen fog futni a programunk. Hiába készítenénk otthon futtatható állományt, ha az otthoni gépünkön elkészített program nem működhetne a szerveren.

Ennek az egyik megoldása az, hogy az általunk készített, még le nem fordított programot nem közvetlenül a processzor hajtja végre, hanem közbeiktatunk egy programot, ami azt értelmezi és végrehajtja. Ezt a programot nevezzük virtuális gépnek, azért mert ugyanúgy saját, alacsony színtű utasításokkal és végrehajtási móddal rendelkezik, mint a valódi számítógép.

Ha telepítve van a programunknak megfelelő virtuális gép az adott számítógépen, a programunk végrehajtható azon a számítógépen is. Ilyen virtuális gép például a Java Virtual Machine és a Zend Engine is.

A Java Virtual Machine olyan kódot hajt végre, amit már a Java Development Kit-tel lefordítottunk Java bájtkódra. Ellentétben a Zend Engine-nel, aminél a végrehajtható fájl maga a forrásprogram. Az ilyen szöveges, végrehajtható állományt hívjuk szkriptnek.

Ezzel, hogy közbeiktattunk a gép és a programunk közé egy virtuális gépet, a kód hordozható lett, és lehetővé vált, hogy a forrásprogramunkon végrehajtott változtatásokat azonnal ellenőrizzük. Mindezért ellenben azzal kell fizetnünk, hogy a szkriptünk feldolgozása és értelmezése futásidőben történik.

A PHP értelmező

A PHP elnevezés egy programozási nyelvet jelöl, a PHP utasításokat a PHP-hez készített virtuális gép, a Zend Engine hajtja végre. A Zend Engine és PHP nevek összeforrottak, de nem egyenlők egymással. Amikor futtatunk egy PHP kódot, a Zend motor értelmezi és hajtja végre az általunk alkotott kódot. A programunk betöltése után, első lépésben a kód értelmezése történik. Az értelmező rész többféle dolgot is elvégez.

  • Az értelmező program a számára felesleges részeket eltávolítja a kódból (pl. megjegyzések, plusz határoló jelek stb.). Ténylegesen nem távolítja el, inkább figyelmen kívül hagyja ezeket a részeket.
  • A kifejezéseket a végrehajtó rész által értelmezhető formára hozza.
  • És még számos lexikai elemzést elvégez, aminek eredményeképp, ha a szkript nem tartalmaz szintaktikai hibát, a műveletsor végére az utasításokat átalakítja a virtuális gép által végrehajtható kódsorrá.

Az értelmező rész végén előáll a szkriptünkből egy, a Zend Engine végrehajtó része által futtatható, háromcímes utasításokat tartalmazó bájtkód. A Zend terminológiában ezeket az utasításokat hívják opcode-nak, mely az operation code (műveleti kód) rövidítése. A Zend motor végrehajtó része ezt a bájtkódot hajtja végre.

A Vulcan Logic Disassembler-rel megnézhetjük, hogy a Zend motor feldolgozó része milyen bájtkódot állít elő. Most már, hogy van némi képünk az interpreter működéséről, rácsodálkozhatunk arra, hogy mit is látunk a VLD futtatásakor.

Telepítés

A telepítéshez szükséges fájlok a cikk mellékleteként letölthetőek. A csomag tartalmazza a Windows rendszeren való telepítéséhez szükséges dll-t is.

Másoljuk be a php_vld.dll fájlt a kiterjesztéseket (extensions) tartalmazó mappába, és a php.ini fájlt egészítsük ki a következővel.
[VLD]
extension = php_vld.dll
Egy kicsit átírtam a VLD forráskódját, így már nem csak parancssorból, hanem böngészőből is használható, és olvashatóbb kimenetet hoz létre. A php.ini beállítása után, parancssorból azonnal tudjuk használni a kiterjesztést, böngészőből való használathoz újra kell indítani a webkiszolgálót.

Megjegyzendő, hogy a parancssoros használathoz érdemes a Path környezeti változóba a C:\Program Files\PHP\cli;C:\Program Files\PHP könyvtárakat ebben a sorrendben felvenni. Ha nem tudjuk, hogyan kell, keressünk rá a súgóban a környezeti változók kifejezésre, vagy nézzük meg a Weblabor PHP telepítésről szóló cikkét.

Mivel a cikkben szereplő modulok mindegyike használható parancssorból, érdemes készíteni egy parancsikont az Asztalra. Majd állítsuk be a parancsikon tulajdonságainál, az Elrendezés lapon a nekünk megfelelő képernyőpuffert. A képernyőpuffer magassága azt adja meg, hogy mennyi sort tudunk visszalapozni, nyugodtan adjunk meg elég nagy számot. A sorhossz pedig azt határozza meg, hogy hanyadik karakter után fog sort törni a konzol.

A disassembler használata parancssorból:
php -d vld.active=1 -d vld.execute=0 <phpszkript>
A -d kapcsolóval módosíthatunk vagy állíthatunk be egy php.ini direktívát. A -d vld.execute=0 beállítást elhagyhatjuk, ekkor a szkript kimenete is meg fog jelenni. Ha nem abban a könyvtárban vagyunk, ahol az ellenőrizendő szkript van, akkor a szkript nevét az elérési úttal együtt kell megadnunk.

A böngészőből való használatnál, Apache webkiszolgáló esetén, abban a könyvtárban, ahol a szkript helyett a VLD kimenetet szeretnénk látni, a .htaccess fájlt ki kell egészítenünk a következővel.
php_value vld.active   1 #Aktívvá teszi a VLD modult.
php_value vld.execute  0 #Ha be van kapcsolva, a szkript kimenete is látszik.

Néhány példa

A VLD használatával beleshetünk egy kicsit a Zend motor bugyraiba. Nézzünk meg néhány PHP program bájtkódját.
Feltételek
<?php
  $e = 5;
  if ($e == true) ;
  if ($e == 5) ;
  if ($e) ;
?>
A három ellenőrzés bizonyos esetekben ugyanazt az eredményt hozza, és természetesen a helyzettől függően ezek az ellenőrzések felcserélhetők. Megjegyzem, az ($e == true) és az ($e) ellenőrzések teljesen ekvivalensek. Érdemes a kisebb bájtkódot eredményező megoldást használni, feltéve, ha az nem gátolja a forráskódunk későbbi megértését. A szkript futtatása után a következő kimenetet fogjuk kapni.
C:\www\vld>php -d vld.active=1 -d vld.execute=0 info.php
filename:       C:\www\vld\info.php
function name:  (null)
number of ops:  15

line     # opcode                       fetch          ext  res  operands
-----------------------------------------------------------------------------
   2     0 FETCH_W                     |local         |    |$0  |string:'e'
		 1 ASSIGN                      |              |    |$1  |$0, int:5
   3     2 FETCH_R                     |local         |    |$2  |string:'e'
		 3 FETCH_CONSTANT              |              |    |$3  |string:'true'
		 4 IS_EQUAL                    |              |    |$4  |$2, ~3
		 5 JMPZ                        |              |    |    |~4, ->7
		 6 JMP                         |              |    |    |->7
   4     7 FETCH_R                     |local         |    |$5  |string:'e'
		 8 IS_EQUAL                    |              |    |$6  |$5, int:5
		 9 JMPZ                        |              |    |    |~6, ->11
		10 JMP                         |              |    |    |->11
   5    11 FETCH_R                     |local         |    |$7  |string:'e'
		12 JMPZ                        |              |    |    |$7, ->14
		13 JMP                         |              |    |    |->14
   6    14 RETURN                      |              |    |    |int:1
A line a forráskódunk sorszámait adja meg. Láthatjuk, hogy az értelmező egy-egy utasítást több műveleti kódra fordít le. A fetch oszlop az adott változó hatóköréről és élettartamáról ad információt. Az ext oszlop jelöli függvényhívás esetén, hogy az adott függvény hány paramétert igényel. A res oszlop azt adja meg, hogy ha az adott műveleti kód visszatérési értékkel rendelkezik, akkor az a verem melyik részére kerül. Az operands oszlop az adott műveleti kód operandusait, paramétereit tartalmazza, maximum kettő lehet. Elsőre elég zavarosnak tűnhet ez a Zend Engine opkód, de hámozzuk ki belőle azt, ami minket érdekel.

Az ($e == true) kifejezés bájtkódja:
$2 FETCH_R string:'e'
$3 FETCH_CONSTANT string:'true'
$4 IS_EQUAL $2, ~3
Az ($e == 5) kifejezés bájtkódja:
$5 FETCH_R string:'e'
$6 IS_EQUAL $5, int:5
Az ($e) kifejezés bájtkódja:
$7 FETCH_R string:'e'
Egy kis magyarázat a kódhoz: a fetch_r a jobb oldali értéket a verembe másolja, fetch_constant az adott konstanst a verembe másolja, az is_equal összehasonlít két verembeli értéket, és az eredményt a verembe teszi. Láthatjuk, hogy a fönti, hasonló ellenőrzést végző kódok, más-más bájtkódot eredményeztek, nem meglepő módon, a tömörebb fogalmazás tömörebb bájtkóddal, és gyorsabb végrehajtással jár.
Függvényhívás
<?php
  function isInt($str){
    return $str == (string)(int)$str;
  }
  is_int("10");
  isInt ("10");
?>
Egy kis meglepetést okoz a két függvény - a belső is_int és a saját isInt függvény - hívásának bájtkódja:

   SEND_VAL string:'10'
$0 DO_FCALL string:'is_int', int:0
   SEND_VAL string:'10'
$1 DO_FCALL string:'isint', int:0
A saját és a belső függvény hívásának ugyanaz a bájtkódja, a Zend motor nem tesz különbséget a két függvényhívás között. Mindez addig igaz, míg a függvény deklarálása a függvényhívás előtt szerepel, és nem utána. Ha a függvénydeklaráció a hívás után szerepel, a Zend Engine egy kicsit összetettebb kódot hoz létre.
Nyelvi elemek
A PHP kézikönyvben többek között az echo leírásánál azt olvashatjuk, hogy az tulajdonképpen nyelvi elem. De mit jelenthet ez a rejtélyes kifejezés? Próbáljuk ki, hogy milyen bájtkódot eredményez a következő forráskód.
<?php
  echo  ("1.sor");
  print ("2.sor");
  printf("3.sor");
?>
A három sor bájtkódja:

   ECHO string:'1.sor'
$0 PRINT string:'2.sor'
   FREE ~0
   SEND_VAL string:'3.sor'
$1 DO_FCALL string:'printf', int:0
Az echo és print PHP utasításokhoz létezik nekik megfelelő műveleti kód. A program feldolgozásakor nem kell az utasítást tovább bontani, mint pl. a printf függvénynél, ezért ezek az utasítások gyorsabban végrehajthatók. Bár a print együtt jár legalább egy free opkóddal is, mivel a print által visszaadott értékkel kell kezdeni valamit.

Érdemes lehet kisebb kódok, kódrészletek nézegetése csak a kíváncsiság kedvéért is. De nagyobb kódok megnézése pont annyira érdekes, mint például egy bináris, futtatható állomány megtekintése disassemblerben. A modul készítője, Derick Rethans egy encodert akart létrehozni, végül a disassembler elkészítéséig jutott.

A VLD modulról további információkat találhatunk még a következő helyeken: Derick Rethans honlapja, VLD a PECL webhelyen.

Advanced Php Debugger v0.9.2

Az ADP modullal a PHP szkriptünkről készíthetünk profile-t. Azaz információt kaphatunk arról, hogy a programunk utasításai mennyi erőforrást használnak, melyik függvényt hányszor hívjuk meg. Igazán hasznos lehet, ha javítani szeretnénk egy-egy programunk futásán. Nem kell találgatnunk, hogy hol is kezdjük a fejlesztést. A profile elkészítése után azonosíthatjuk a lassan működő részeket.

Ezen kívül a PHP nyelvvel való ismerkedésünk bizonyos fokán eljutunk arra a pontra, mikor elkezd érdekelni bennünket, hogy ez vagy az a megoldás a gyorsabb. Ez azért is van így, mert a PHP nyelv rengeteg függvénye miatt egy feladatra számos megoldás adódik. Az összehasonlításhoz nem kell bajlódnunk például a microtime függvénnyel vagy PEAR csomagokkal, egyszerűen használjuk az APD lehetőségeit. Arról nem is beszélve, hogy pontosabb eredményt is kapunk.

Telepítés

A telepítéshez szükséges fájlok a cikk mellékleteként letölthetőek. A csomag tartalmazza a Windows rendszeren való telepítéséhez szükséges dll-t is.

Másoljuk be a bővítményeket tartalmazó mappába a php_apd.dll fájlt, és egészítsük ki a következővel a php.ini fájlt:
[APD]
zend_extension_ts = "C:\Program Files\PHP\extensions\php_apd.dll"
apd.dumpdir = "C:\apd"
Az APD egy Zend kiterjesztés. Előfordulhat, hogy nem működik együtt egyéb Zend kiterjesztéssel, például a Zend Optimizer-rel. Fontos, hogy az APD modul betöltéséhez teljes elérési úttal kell megadnunk a dll-t, és az APD használatához meg kell adni egy munkakönyvtárat is, amit nem árt, ha létre is hozunk. A példában ez a C:\apd mappa. Ezután másoljuk ebbe a mappába a pprofp.php fájlt is.

Az APD használata

Amelyik szkriptnél használni akarjuk a profilekészítést, meg kell hívnunk az apd_set_pprof_trace függvényt. Ezután az APD egy fájlt hoz létre a munkakönyvtárába, ami tartalmazza az apd_set_pprof_trace függvény utáni futási információkat. A fájl neve pprof.xxxxx.yy lesz, ahol xxxxx a php.exe vagy a webkiszolgáló folyamatazonosítója, az yy pedig egy sorszám.

Az APD által előállított fájlra sok minden mondható, csak az nem, hogy ember által olvasható formátumban tartalmazza a mérési adatokat. Az adatok megjelenítéséhez használjuk parancssorból a pprofp.php szkriptet. Telepítéskor azért másoltuk az APD munkakönyvtárába, hogy ne kelljen külön keresni. A használat módja:

php pprofp.php [kapcsolók] pprof.xxxxx.yy
A megadott kapcsolók szerint összesíthetjük, és jeleníthetjük meg az elkészített profile adatait. A pprofp.php használja a PEAR Console\Getop csomagot, ezért a futtatása előtt ezt is telepítenünk kell. A PEAR telepítéséhez futassuk parancssorból a PHP mappában lévő go-pear.bat fájlt, a telepítéshez internetkapcsolat kell.

A ppprofp.php szkriptet átírtam, így olvashatóbb eredményt szolgáltat Windows parancssorban is. Néhány kapcsoló nem, vagy hibásan működött, ezeket kijavítottam, ezen kívül jelentősen gyorsabban dolgozza fel az APD által készített fájlt.

A pprofp szkript kapcsolói:

Adatok rendezése:
  • a - Rendezés az eljárások neve szerint
  • l - Rendezés az eljárások hívásszáma szerint
  • m - Rendezés a memóriahasználat szerint. (A memóriaadatok megjelenítéséhez az APD-t és a PHP-t is az --enable-memory-limit kapcsolóval kellene lefordítani.)
  • r - Rendezés a valós idő szerint.
  • R - Rendezés a valós idő szerint (tartalmazza a gyermekhívásokat)
  • s - Rendezés a rendszeridő szerint
  • S - Rendezés a rendszeridő szerint (tartalmazza a gyermekhívásokat)
  • u - Rendezés a felhasználói idő szerint.
  • U - Rendezés a felhasználói idő szerint. (tartalmazza a gyermekhívásokat)
  • v - Rendezés az eljárásokban töltött átlagos idő szerint.
  • z - Rendezés a felhasználói + rendszeridő szerint. (alapértelmezett)
  • Z - Rendezés a felhasználói + rendszeridő szerint (tartalmazza a gyermekhívásokat)

Megjelenítési kapcsolók:
  • t - Tömörített hívási fát jelenít meg.
  • T - Tömörítetlen hívási fát jelenít meg.
  • c - A hívási fánál megjeleníti a valós időt is.
  • i - A beépített függvényeket nem jeleníti meg.
  • m - A hívási fában megjeleníti a fájl, sor és memóriahasználati információkat. Ha más szerint szeretnénk rendezni az eljárásokat, a rendezési kapcsolót ezután adjuk meg.
  • O <szám> - Megjelenített eljárások legnagyobb száma (alapértelmezett: 20)

Példa

A profile készítéséről a PHP fejlesztés felsőfokon című könyv 18. fejezetében részletesen olvashatunk. A példában azt szeretném bemutatni, hogyan használhatjuk az APD-t különböző programozási megoldások összehasonlítására. Nemrég a PHP levelezési listán volt szó arról, hogy mennyire gyorsak a preg függvények. A listán előkerült függvények összehasonlítására a következő szkriptet használtam.
<?php
  $iteration = 10000;
  $str       = 'mr akarki (AB)';
  $pattern   = '!\((.*)\)$!';

  function _preg_str($str){
    for ($i = 0; $i < $GLOBALS['iteration']; ++$i){
      preg_match($GLOBALS['pattern'], $str);
    }
  }
  function _simple_substr($str){
    for ($i = 0; $i < $GLOBALS['iteration']; ++$i){
      substr($str, -3, 2);
    }
  }
  function _complex_substr($str){
    for ($i = 0; $i < $GLOBALS['iteration']; ++$i){
      $start = strpos($str, "(") + 1;
      substr($str, $start, strpos($str, ")") - $start);
    }
  }

  apd_set_pprof_trace();
  _complex_substr($str);
  _simple_substr($str);
  _preg_str($str);
?>
A program futtatásakor az APD elkészíti a szkript futási információit tartalmazó fájlt a munkakönyvtárába. Az eredmény megtekintéséhez futtassuk parancssorból a pprofp.php szkriptet. A következő ábra, a példa kedvéért, a saját mérési eredményeimet mutatja be.

C:\apd>php \www\apd\test.php

C:\apd>php pprofp.php -iR pprof.03028.0

Trace for C:\www\apd\test.php
Total Elapsed Time =  0.50s
Total System Time  =  0.09s
Total User Time    =  0.41s

			Real         User        System              secs/   cumm
 Time%  (excl/cumm)  (excl/cumm)  (excl/cumm)   Calls    call   s/call   Memory Usage  Name
-------------------------------------------------------------------------------------------
 100.0   0.00  0.50   0.00  0.41   0.00  0.09        1  0.0000  0.5010              0  main
  56.5   0.28  0.28   0.25  0.25   0.03  0.03        1  0.2831  0.2831              0  _complex_substr
  23.0   0.12  0.12   0.10  0.10   0.02  0.02        1  0.1153  0.1153              0  _preg_str
  20.3   0.10  0.10   0.06  0.06   0.04  0.04        1  0.1016  0.1016              0  _simple_substr
   0.2   0.00  0.00   0.00  0.00   0.00  0.00        1  0.0010  0.0010              0  apd_set_pprof_trace
Az R kapcsoló használatával a függvények a gyermekhívások idejét is tartalmazzák, ezért került a legelső helyre a main függvény. Az i kapcsolóval pedig kizártuk a listából a beépített függvényeket. Láthatjuk, hogy mennyire részletes listát kapunk arról, hogy melyik függvény végrehajtása mennyi időt vett igénybe. Az oszlopok a következők:
  • Time - A felhasznált idő százalékban az egész program futási idejére levetítve.
  • Real - A valós idő, az excl oszlop a végrehajtás ideje, a cumm oszlop a végrehajtás ideje a gyermekhívásokkal együtt.
  • Calls - Az adott függvényt hányszor került meghívásra.
  • secs/call - Egy hívás átlagos végrehajtási ideje.
  • cumm s/call - Egy hívás átlagos végrehajtási ideje gyermekhívásokkal együtt.
  • Memory Usage - Az adott függvény által felhasznált memória. Ez a lehetőség az APD készítői szerint sem ad megbízható mérési eredményt, és csak bizonyos esetekben működik.

Még meg kell említenem, hogy jelen mérés lehet, hogy téves eredményt ad, mert a preg függvények gyorsítótárazást használnak. Lehet, hogy pontosabb eredményt kapnánk, ha a méréshez előállítanánk egy tömböt. De jelen esetben csak a példa kedvéért szerepel a szkript, amin jól látszik, hogy mennyire megkönnyíti az APD a különböző futási eredmények összehasonlítását, ennek csak az a feltétele, hogy a mérendő dolgokat külön-külön függvénybe tegyük.

Érdemes odafigyelni arra, hogy ne adjunk meg túl nagy ismétlési értéket, különben ennek az lehet az eredménye, hogy a szkript futása közben az operációs rendszer elkezd swap-elni, amivel teljesen téves eredményeket kaphatunk. A Windows operációs rendszereket néha kifejezetten nehéz rávenni arra, hogy időlegesen hagyják abba ezirányú tevékenységüket. Remélhetőleg, azt meg sem kell említenem, hogy mérés közben nem internetezünk, nem játszunk, és lehetőleg semmi olyat nem teszünk, ami téves mérési eredményt okozhat.

Az Advanced Php Debugger modulról további információkat kaphatunk még a következő helyeken: APD honlapja, APD a PECL webhelyen.

eAccelerator v0.9.3

Mivel a PHP egy szkriptnyelv, időről-időre előkerül az az igény, hogy hogyan védhetnénk le szkriptjeinket. Alapállapotban sem maga a PHP nyelv, sem a Zend Engine nem ad megoldást erre. Kulcsot a Zend Engine annyiban nyújt, hogy készíthető hozzá olyan modul, ami a belső végrehajtási sorba ékelődik.
Ilyen modulok a következők:

Ezek a betöltő modulok ingyenesen letölthető és használható Zend Engine kiegészítések. A Zend Optimizer, az ionCube Loader és az eAccelerator Loader olyan modulok, amik segítik a szkriptek végrehajtását. Ha még emlékszünk a Vulcan Logic Disassembler résznél leírtakra, a szkript végrehajtási menete: forrásszöveg beolvasása, forrásszöveg feldolgozása (a bájtkód elkészítése), bájtkód végrehajtása.

A felsorolt betöltők gyorsítótárazzák az elkészített bájtkódot, így a szkriptek feldolgozása összességében kevesebb időt vesz igénybe. Az ilyen betöltő modulok használatával a Zend Engine feldolgozó része azzal bővül, hogy a modul ellenőrzi, hogy a gyorsítótárában szerepel-e a szkript bájtkódja, és az megfelelő-e, ha igen, akkor az adott bájtkód azonnal végrehajtható.

Nem kell a Zend Engine értelmező részének feldolgoznia a programunkat, ha a modul gyorsítótárában rendelkezésre áll a szkript lefordított változata, akkor ennek betöltésével elkezdődhet a bájtkód végrehajtása. Így ezek a gyorsítók önmagukban is, anélkül, hogy hozzányúlnánk a PHP programokhoz, gyorsítják a végrehajtás menetét. Ha nincs meg, vagy nem megfelelő a rendelkezésre álló bájtkód, akkor a Zend Engine feldolgozó része előállítja azt, majd a betöltő modul elmenti az elkészített kódot, és továbbadja a Zend motor végrehajtó részének.

Biztos feltűnt, hogy ezen a folyamaton tovább gyorsíthatnánk, ha már eleve lefordított PHP szkripteket tudnánk betölteni. Így a Zend Engine feldolgozó részét nem kellene meghívni. Szerencsénk van, mert mind a három betöltőhöz létezik bájtkódot előállító program.

A Zend SafeGuard, a Zend Encoder és az ionCube PHP Encoder fizetős alkalmazások, míg az eAccelerator nyílt forrású és ingyenesen használható program. Ezekkel a programokkal további gyorsítást érhetünk el, mivel a betöltő modulnak nem kell meghívnia a Zend motor feldolgozó részét, ráadásul többféle kódoptimalizációt is tartalmaznak. Sajnos a probléma az, hogy a fönt felsorolt betöltők csak a nekik megfelelő bájtkóddal képesek dolgozni.

Mi lehet a probléma? Hisz a Zend Engine által feldolgozható bájtkód egy és ugyanaz. Nos, ezeket a programokat főleg pont e miatt a problémát jelentő dolog miatt használják. Mindegyik valamiféle védelmet is alkalmaz az előállított kódon, ezért csak a saját betöltője tudja kicsomagolni a védett bájtkódot.

A Zend SafeGuard, a Zend Encoder és az ionCube PHP Encoder esetén a titkosítási algoritmus nem nyilvános. Az eAccelerator esetén nem igazán beszélhetünk titkosításról, nyílt forrású programról van szó. Néhány utasítás után megkaphatjuk a bájtkódot, igaz a bájtkód még ember által nem olvasható formátum. Az eAccelerator által a bájtkódon alkalmazott módosítások inkább azt célozzák, hogy a betöltő modul minél gyorsabban és biztosan azt a kódot kapja vissza, amit az eAccelerator létrehozott.

Ezért a Zend Optimizer és ionCube Loader képes lehetne kezelni az eAccelerator által előállított kódot, de valószínűleg üzleti megfontolásokból ezt nem teszik.

Mindenesetre az itt felsorolt betöltők és bájtkód előállítók a szerverüzemeltetők nagy barátai. A hosztingcégeknek mindenképpen ki kellene választaniuk egyet, és azt a lehető legjobban támogatniuk kellene. Használatukkal komoly terheket vehetnek le a szervereikről, kis ráfordítással jelentős teljesítménynövekedést érhetnek el. Mivel ezek a betöltők a Zend Engine feldolgozó részét próbálják kiváltani, ezért nem szükséges mindegyik betöltő használata, halmozásukkal nem lesz gyorsabb a PHP szkriptek végrehajtása.

Ha olyan szerveren van a weblapunk ahol elérhető az eAccelerator, készítsük el az általa feldolgozható bájtkódot. Ne azért, mert ezzel a szerverüzemeltetőnknek segítünk, hanem azért, mert a weblapunk kódja gyorsabban fog végrehajtódni. Ezen kívül nem elhanyagolható tény, hogy ha nem is bombabiztos, de legalább minimális védelmet tudunk alkalmazni a PHP programunkon.

Telepítés

A telepítéshez szükséges fájlok a cikk mellékleteként letölthetőek. A csomag tartalmazza a Windows rendszeren való telepítéséhez szükséges dll-t is.

Másoljuk be az eAccelerator.dll fájlt a PHP modulokat tartalmazó mappába, és egészítsük ki a php.ini fájlt a következő sorokkal.
[eAccelerator]
zend_extension_ts = "C:\Program Files\PHP\extensions\eAccelerator.dll"
eaccelerator.cache_dir = "C:\php_eaccelerator"
eaccelerator.shm_size = "2"
eaccelerator.enable = "1"
eaccelerator.optimizer = "1"
eaccelerator.filter = ""
Fontos, hogy a kiterjesztés megadásakor teljes elérési utat adjunk meg, és hozzuk létre a cache_dir direktívánál megadott könyvtárat.
Az előbbi beállítási direktívák:
  • eaccelerator.cache_dir - Annak a könyvtárnak a helye, amit az eAccelerator lemezes gyorsítótárként használhat. Ez a példában a C:\php_eaccelerator mappa, de megadhatunk más könyvtárat is.
  • eaccelerator.shm_size - A megosztott memória gyorsítótár mérete megabájtban. Fejlesztői gépen ennek nem kell nagy értéknek lennie.
  • eaccelerator.enabled - 1 esetén engedélyezzük az eAccelerator működését, 0 esetén tiltva van.
  • eaccelerator.optimizer - 1 esetén az eAccelerator belső kódoptimalizációt is használ, 0 esetén tiltva van az optimalizálás.
  • eaccelerator.filter - Azokat a PHP fájlkiterjesztéseket adhatjuk meg, amiket az eAccelerator gyorsítótárazzon. Üres sztring esetén minden PHP szkriptet gyorsítótáraz. Fontos, hogy kettős idézőjelet használjunk, az eAccelerator fórumán olvasottak szerint, egyszeres idézőjel használatakor probléma léphet fel a modul működésében.

Az eAcceleratornak két dll-je van, az egyik az eLoader.dll, a másik az eAccelerator.dll. Az eLoader.dll csak a betöltési funkciókat tartalmazza, használatának csak szerveren van jelentősége. Az eAccelerator.dll modul tartalmazza a loader funkcióit is, de lehet vele bájtkódot is előállítani. Nekünk most az eAccelerator.dll lehetőségeire is szükségünk van, ezért ezt telepítsük.

A PHP programjaink kódolása

A bájtkódot az eAccelerator modul eaccelerator_encode függvényével tudjuk előállítani. Ez így elég nehézkes lenne, ezért a függvény használatához nyújt segítséget az encoder.php szkript, ami egy felületet ad az eaccelerator_encode függvényhez. Az encoder.php használható mind böngészőből, mind parancssorból is.

Az encoder.php szkriptben volt egy hiba, a kódolt szkript végére beszúrt egy felesleges sortörés karaktert. Először csak ezt módosítottam, majd elkezdtem átnézni a szkriptet, aminek az lett az eredménye, hogy az egészet újraírtam. Osztály alapú kódot használ, amivel esetleges továbbfejlesztése remélhetőleg könnyebb lesz, és Windows rendszer alatt jobban kezeli a fájlneveket. A szkript jelenleg magyar nyelvű, de könnyen fordítható más nyelvre is.

A parancssoros használathoz szükséges, hogy telepítve legyen a PEAR Console\Getop csomag. Webes felület használatához ez a csomag nem kell, de böngészőből való használathoz másoljuk a mappába a main.css fájlt is. A stíluslap olyan beállításokat is tartalmaz, amit az Internet Explorer nem vagy nem jól értelmez, ezért a webes felület használatához más böngésző ajánlott.

A PHP programjaink kódolásához elegendő a forrás és a célfájl megadása. A forrásfájl az a PHP szkript amit kódolni szeretnénk, a forrásnak megadhatunk könyvtárat is. Az előállított, kódolt szkript a megadott célfájl nevén kerül elmentésre. Ha a forrásnak könyvtárat adunk meg, akkor a célnak is könyvtárat kell megadnunk, a célfájlnak vagy könyvtárnak nem szükséges léteznie. A kiterjesztéshez adjuk meg azokat a fájlkiterjesztéseket, amiket kódolni szeretnénk. Ha nem adunk meg kiterjesztést, minden fájl kódolásra kerül.
Az encoder.php szkript kapcsolói
  • Alkönyvtárak kódolása - Ha forrásnak könyvtárat adunk meg, a kapcsoló bejelölésével az alkönyvtárakat is kódolni tudjuk.
  • A nem kódolható fájlok átmásolása - Ha bejelöljük, akkor a nem kódolt fájlokat (pl. képeket, stílusfájlokat, stb.) is a célmappába másolhatjuk.
  • Létező fájlok felülírása - Ha bejelöljük, akkor a célmappában a már létező fájlokat felül fogja írni. A kapcsoló csak a fájlokra van hatással, a mappákra nem.
  • eAccelerator ellenőrzésének mellőzése - Ha nem jelöljük be, akkor a kódolt szkript tartalmazni fog egy ellenőrzést, hogy telepítve van-e a szerveren az eAccelerator. Ha nincs telepítve, akkor a szkript futtatásakor egy figyelmeztető üzenet fog megjelenni arról, hogy a szükséges modul hiányzik. Szerintem ez az ellenőrzés egy jól beállított szerveren felesleges.
  • Minden üzenet kiírása - Ha bejelöljük, akkor konvertálás alatt minden üzenet meg fog jelenni, ellenkező esetben csak a hibaüzeneteket fogjuk látni. Néha zavaró lehet, ha az összes üzenetet látjuk, de követhetjük vele a konvertálás menetét.

Kezdetnek esetleg próbáljuk ki az encodert a PEAR csomagokon. Nálam a telepített csomagok a következők voltak: Archive_Tar, Auth_SASL, Console_Getopt, DB, Mail, Mail_Mime, Mail_Queue, Net_SMTP, Net_Socket, PEAR, PHPUnit, PHP_Compat, XML_RPC. Ezt nem mint referenciát soroltam fel, csak viszonyítási alapul szolgálhat egyéb forrásfájlok kódolásához. Az encoder futtatása böngészőből:


A kódolás alatt néhány szkriptben az eaccelerator_encode függvény hibát fedezett fel. A hibás szkripteket az encoder nem menti el, ezért nézzük meg, hogy miért jelzett hibát ezeknél a fájloknál!

A Mail\Queue.php fájlban a hibát az okozza, hogy a Mail_Queue_Error metódus deklarálásánál a paraméterlistában a következő szerepel: $file=__FILE__, $line=__LINE__. Ezt ki tudjuk javítani, ha a $file és $line paraméterek alapértelmezetten NULL értéket kapnak, és a metóduson belül írjuk át az értéküket, ha szükséges.

A PEAR\Exception.php fájl fordítása azért nem sikerült, mert a szkript a PHP 5 verziójától elérhető, új objektum orientált programozási lehetőségeket használja. A szkript kódolásához PHP 5 és a hozzá lefordított eAccelerator modul szükséges.

A PHP\Compat\Function\version_compare.php fájlban a következő sor okozza a hibát: elseif (is_numeric($i2])). Ami egyértelműen szintaktikai hiba, de a csomag készítője nem vette észre. A szkript meggyógyításához egyszerűen töröljük a felesleges ] karaktert, a kódolás ezután hiba nélkül végrehajtódik.

Láthattuk, hogy az encoder használatával néhány rejtett hibára is rálelhetünk. Ezen kívül az előállított szkriptek sokkal kevesebb helyet foglalnak, a példában a kódolt fájlok mérete kevesebb, mint a forrásfájlok negyede. Mondhatnánk azt, hogy ez mindegy, hisz elég tárhelyünk van, de a programok végrehajtásakor ezek a memóriából vesznek el helyet, ebből pedig mindig kevés szokott lenni. A programok beolvasása is ezzel arányosan kevesebb időt fog igénybe venni, és a lemezműveleteknél csak egyetlen egy költségesebb művelet van, a távoli lemezműveletek. Ezért, ha a szerveren telepítve van az eAccelerator, érdemes kódolnunk a szkriptjeinket.

Búcsúzóul

Remélem, minden érdeklődőnek sikerül beüzemelnie az előbb megismert modulokat, és hasznos kiegészítőknek fogja találni őket.
 
1

Szép munka

mefi · 2005. Dec. 12. (H), 18.25
Szép munka, gratulálok :)

Csak megjegyzés, a feltételek rész alatt található táblázat szét van csúszva kicsit.

Üdv:
mefi
http://mefi.be
2

elcsúszva?

Hojtsy Gábor · 2005. Dec. 12. (H), 18.42
Mi van rajta elcsúszva? Itt jelenik meg úgy, vagy eleve úgy van generálva? Szerintem egyik sem (fix szélességű betűtípus, és görgetősávos túlcsordulás megjelenítés miatt nem hiszem, hogy bármi elromlik nálad a megjelenítésben).
6

Már nincsen

mefi · 2005. Dec. 13. (K), 18.09
Furcsa, már nincsen elcsúszva. Pedig tegnap a teljes archívum táblázatocska szépen bemászott a cikk alá.

üdv:
mefi
http://mefi.be
3

mefi!

Balogh Tibor · 2005. Dec. 12. (H), 20.03
Köszönöm! Tényleg jól esnek az ilyen elismerések! És mindenek ellenére arra biztathatnak, hogy további cikkeket is írjon az emberfia. Azért írom így, mert ez nem csak rám vonatkozik, hanem minden további cikkíróra is.

A három modul közül kipróbáltad valamelyiket? A cikk melyik része tetszett?
4

Szépséghiba

presidento · 2005. Dec. 13. (K), 13.18
A cikk nagyon tetszik!!!
Egy kissé zavaró szépséghiba, hogy a ,,kimenetek'' esetén az egyszerű megjelenítés (nyomtatási nézet) esetén a háttér fehérre vált, viszont a betűk is fehérek maradnak. ;)


FARKAS Máté
5

Cikk

Bártházi András · 2005. Dec. 13. (K), 13.46
A design problémát javítani fogjuk, köszönjük a jelzést. :)

-boogie-
7

hmm

connor · 2005. Dec. 13. (K), 21.32
Vajon a VLD akkor is működik ha a php bináris?

--
connor
8

Nagyon jó kérdés!

Balogh Tibor · 2005. Dec. 14. (Sze), 15.31
Tessék kipróbálni! Én egy Zend SafeGaurd-dal kódolt szkripten futólag kipróbáltam, de nem az eredeti szkript kódját adta vissza, ionCube-bal nem próbálkoztam.
Lehet, hogy rosszul csináltam, mert a VLD az Optimizer előtt volt regisztrálva a php.ini-ben. De én úgy csinálnám a helyükben, és valószínűleg így is teszik, hogy a végrehajtó részbe is beépüljön a betöltő modul. Ha sikerül a visszafejtés, akkor is egy obfuszkált program bájtkódját kapnád vissza.
Ha esetleg valakit Java bináris visszafejtése érdekel: DJ Java Decompiler
9

Fantasztikus

sayusi · 2005. Dec. 22. (Cs), 00.14
Most jutottam el odáig,hogy elolvassam ezt a cikket. Hát fantasztikus! Egyrészt a stílus nagyon tetszik nekem másrészt a téma nagyon hasznos.
Gondolom vannak még ilyen "meglepetések" az egész nyelvben. Várom őket.

Bízzál Istenben és tartsd szárazon a puskaport!" -Cromwell
10

compile

Anonymous · 2005. Dec. 28. (Sze), 22.04
Hello!

How can I compile VLD to windows binary php_vld.dll? I want to make some changes in source code. Thank you!
12

compiling under windows

Balogh Tibor · 2006. Jan. 4. (Sze), 00.43
Use MSVC++ [Microsoft Visual C++] for compiling. For example, using the GUI:
- Read the part titled "Building from source code" in the chapter "Installation on Windows system" from the php manual.(php manual) Get the all the source files required and follow the manual. The manual contains all information that you need for compiling.
- Extract vld source to /php-x.z.y/ext/vld directory. (vld source from weblabor)
- Open the vld project file. Run the MSVC++ and choose Open workspace menu item from File menu and select the vld.dsp file from vld's directory. Rebuild All project after modifications.
13

<Nincs cím>

Anonymous · 2006. Jan. 4. (Sze), 20.28
thank you very much!
11

Néhány javítás

Balogh Tibor · 2006. Jan. 4. (Sze), 00.40
A következő kisebb frissítések történtek a cikkhez tartozó letölthető fájloknál:
  • vld - A mostani forrás Derick Rethans cvs szerveréről való, és annak a módosítása.
  • eAccelerator - Néhány hibát kijavítottam az encoder.php szkriptben.
    Nem lehetett beállítani, hogy a kiterjesztés nélküli fájlokat is kódolja. Ekkor a kiterjesztéshez pontot (.) kell megadni. Szükség lehet rá a rövid webcímekhez. Hiba jelentkezett akkor is, ha a megadott forráskönyvtár nem volt része a célkönyvtárnak, csak a neve részben azonos volt azzal, vagy vice-versa. Néhány metódust szétbontottam, hogy átláthatóbb legyen.
14

Miért lett lassabb?

Flatron · 2006. Jan. 23. (H), 00.40
Kipróbáltam az eAcceleratort, igaz, hogy nem innen szedtem le, hanem XAMPP-hoz már ott volt, igaz 5.0.5, úgyhogy az ittenit nem is tudtam volna felhasználni, de nem ez a lényeg.
Lefuttatom egyik scriptemen. A script elején ez van:
$time_start = microtime(true);
A végén meg ez:
$time_end = microtime(true);
$time = $time_end - $time_start;

És mi ad vissza?
Optimizált: 0.0421240329742
Eredeti: 0.00737500190735
Aki tudja, hogy ezt hogy sikerült összehoznom, annak várom a válaszát.
17

Miért lett lasabb?

Balogh Tibor · 2006. Júl. 10. (H), 20.54
Számos dolgot fölvet, amit írtál:
1. Az eAcceleratornak még nincs stabil változata az 5.x PHP változatokhoz. A 0.9.4 eAccelerator kisebb hibajavításokat tartalmaz, nem működik jól együtt az 5.x PHP verziókkal.
2. Az hogy önnön magában mérted a szkript futását, nem befolyásolhatta igazán, hogy milyen szkript volt. Mivel amikor fut, már a bájtkód hajtódik végre, és annak a futását mérted.
[beolvasás]=>[fordítás]=>[végrehajtás]

A [végrehajtás] egy részét mérted. A különböző betöltés gyorsítók az első két műveletre vannak hatással. Ettől függetlenül természetesen előfordulhat, amit írsz. Hogy miért, ahhoz teljes egészében ismerni kellene az eAccelerator működését.

Próbáld meg esetleg mérni a szkript futását az APD modullal is. Azt nem írtad, hogy rendszeresen ez az eredmény jött ki, vagy csak egyszeri eset volt. Hibás méréshez, éppen elég, ha megmozdítod az egeret, vagy az oprendszer úgy "gondolja", hogy a szkript futása alatt még csinál valamit. Méréshez készíts egy batch-fájlt, ami parancssorból futtatja a mérni kívánt szkripteket.
15

Mondj nemet a Zend Optimizerre

vbence · 2006. Júl. 9. (V), 10.41
Közben tárgytalanná vált a post... így csak azt mondom óvatosan vele.
16

Miért is?

Balogh Tibor · 2006. Júl. 10. (H), 20.21
Közben tárgytalanná vált a post...

András írja néha, hogy milyen gyorsan jelennek meg új dolgok a webfejlesztés területen. No, de ennyire? Leírod a hozzászólás címét, és mire a szövegnél tartasz, máris érvénytelen az egész? :) Mégis, ha már elvetetted a kétséget, legalább megindokolhatnád!
18

Uninstall

vbence · 2006. Júl. 10. (H), 22.04
A leszedéssel voltak problémák. Nincs uninstall szkript hozzá, én meg elszúrtam egykét dolgot.. aztán végül is megdolódott, úgyhogy módosítottam a postot. (Azért nem békültem ki vele tökéletesen..)

Kérdés az okosokhoz: a sebességnövekedésnek mikor (melyik fázisban) kéne megjelennie? Én azt tapasztalom, hogy a RINIT és RSHUDOWN (request init, ~ shutdown) az optimizerrel több idő telik el, mint nélküle. Azt el tudom képzelni, hogy normál esetben a fordítás a RINIT előtt történik, és a mostani cache-elés viszont a RINIT után, és eztért látszik hosszabbnak a futásidő, ami valójában rövidebb.

Tud erről valaki valamit?
19

ZendOptimizer

Balogh Tibor · 2006. Júl. 11. (K), 11.58
Bár a kérdés nem nekem jött, azért válaszolnék rá:
- Mivel a betöltők a lefordított szkriptet gyorstárazzák ezért elsősorban a betöltés, értelmezés részt gyorsítják. De ha végeznek kódoptimalizációt is, akkor a végrehajtási részt is gyorsíthatják. Nem felejtkezve meg arról, hogy magának a gyorsítótárazásnak is van erőforrásigénye.

Egyébként a cikknek nem volt célja, hogy a Zend Engine C-forráskód szintjéig menjen. Egy általános rátekintés akart lenni a PHP belső működésére.

A többieknek: A PHP_RINIT és a PHP_RSHUTDOWN: makrók a Zend Engine forráskódjában szabványos felület kialakításához. Zend kiterjesztések készítésekor lehet rájuk szükség.

Tud erről valaki valamit?

Hogy pontosan hogyan működik a Zend Optimizer?
Fejtsd meg! Ha megvan, már csak egy bájtkód-forráskód konvertálót kell megírnod, és kezdheted is a kódolt szkriptek visszafejtését.
20

Nem annyira az optimizer

vbence · 2006. Júl. 13. (Cs), 13.56
Én naívan azt hittem, hogy olyan triviális dolgok, mint hogy a cikus melyik fázisában dolgozik az optimizer nem képeznek hétpecsétes titkot. Amúgy az Optimizer névből elsősorban optimlizására és nem cache-elsére gondolna az ember, ezért volt furcsa, hogy a futásidő növekedett.
A cache-elés eredményét nem is vártam, hogy ebben a fázisban megjelenik, de lassulásra (bár kis mértékű), még ennyire sem számítottam.
21

Help plz!!!

Zeratul · 2006. Dec. 24. (V), 03.09
I greet all.
Who does nibud' know English? Can you help with the translation of this article?
Or whether there are services of translation by which it is possible to translate this article into English.

PS: I am very interested by the question of decoding of zend, but I found this article and does
not even know on what she language :(