Maërlyn

HHVM

2014. 03. 30.

A HHVM a Facebook alternatív PHP interpretere, ami azzal az ígérettel érkezik, hogy jelentősen gyorsabb a klasszikusnál. A héten tartottam erről előadást a budapest PHP meetupon, alant ennek összefoglalója jön.

Történeti összefoglaló

Az első változat, akkor még HPHP néven, 2008-ban jelent meg. Ez egy php -> c++ fordító volt, közel a teljes 5.2-es nyelvet ismerte, a két főbb kivétel az eval és a create_function. A saját szervereiken eddigre már ezt használtál élesben, drámai sebességnövekedést tapasztalva. Komoly hátránya volt az előállt, esetenként többgigás binárist rövid idő alatt kiszórni a szerverekre.

A HPHPc (compiler) nem volt alkalmas hibakeresésre, ennek megoldására jött létre a HPHPd (debugger) változat, ebben lehetett watch-okat és breakpointokat beállítani, mint ma egy modern IDE + xdebug kombinációban lehetséges.

2010-ben kezdődött a munka a következő változaton, ez már HHVM néven futott. Ahogy a neve is mondja, ez egy virtuális gép, ami a PHP kódot egy köztes bytekódra fordítja, majd ebből just-in-time készít natív x64 kódot, amit futtat végül. Hatalmas előnye a HPHPc-hez képest, hogy itt nincs hatalmas bináris, amit terjeszteni kéne; viszont a teljesítménybeli javulás nagyrészt megmaradt. Ráadásul az időigényes fordítást sem kell kivárni. A HHVM ismeri közel a teljes 5.4-es nyelvet, beleértve az eval és create_function függvényeket.

2013-ban a HPHPc deprecated lett. (Van erre értelmes magyar kifejezés?)

2014-ben bemutatták a Hack nyelvet, ami egy erősen bővített PHP, erről később írok bővebben.

Telepítés

A HHVM hivatalosan csak linuxon támogatott. OSX-re és FreeBSD-re vannak nemhivatalos fordítási leírások.

A github wikijén számos disztróra találunk forgatási útmutatót, mindenféle verzióra. Debianra (tehát Mintre és Ubuntura is) valamint Centosra vannak előre elkészített csomagok. Az én laptopomon ennyi volt az egész:

wget http://dl.hhvm.com/conf/hhvm.gpg.key
sudo apt-key add hhvm.gpg.key
echo deb http://dl.hhvm.com/ubuntu saucy main | 
    sudo tee /etc/apt/sources.list.d/hhvm.list
sudo apt-get update
sudo apt-get install hhvm

Az utolsó parancs végeztével rögtön elérhető a hhvm parancs:

$ hhvm -–version
HipHop VM 2.4.2 (rel)
Compiler: tags/HHVM-2.4.2-0-g432ecffa04b21c60953bb236a9db8278f4650537
Repo schema: 1be260b29a71097b5d1f78c6e4dcbb981ba03bde

$ hhvm -m d
Welcome to HipHop Debugger!
Type "help" or "?" for a complete list of commands.
hphpd> phpinfo()
HipHop

Aki a saját rendszerén nem szeretne turkálni, vagy nem támogatott oprendszert használ, létezik vagrant box, ami egy ubuntu guestre forrásból forgatja a legfrissebb HHVM-et: javier/hhvm-vagrant-vm

Aki pedig azon gondolkodik, hol tudja ezt saját szerver nélkül hostolni: van már Heroku buildpack , ami push után HHVM-el szolgál ki.

Extensionök

A legfontosabb core extensionök out-of-the-box jönnek a HHVM-el beépítve, és várhatóak újak. [Itt a teljes lista][extensions], a szerintem legfontosabbak pedig:

És persze adott a lehetőség sajátok írására.

Konfiguráció

Az előadásban erről túl sok szót nem ejtettem, és okkal: annyi beállítási lehetőség van, hogy azok megérnének több saját előadást. Itt is csak a szerintem legfontosabbakról írok. A teljes és részletes leírást Runtime options címen githubon találjátok.

Log {
    NoSilencer = false
    AlwaysLogUnhandledExceptions = true
    InjectedStackTrace = true
    NativeStackTrace = true
}

A NoSilencer kapcsolóval lehet logoltatni a @ operátorral elnyomott hibákat is. Az AlwaysLogUnhandledExceptions akkor is logolja a fatal errorokat, ha arra van user-defined error handler. Az utolsó kettő pedig a logba írt stack tracke-ek formátumát szabályozza, illetve hogy a php vagy a c++ kódé legyen-e.

Eval.Debugger {
  EnableDebugger = true                                          
  EnableDebuggerServer = true
  Port = 8089
  DefaultSandboxPath = path to source files
}

Alapból a debugger (hphpd) nem figyel, így lehet bekapcsolni. Indítás után a logból is látszik, hogy várja a kapcsolatokat.

HHVM mint FCGI

Amikor az előadást tartottam akkor még lehett önálló szerverként futtatni a HHVM-et, nem kellett elé külön http szerver. Ezzel járt az, hogy rengeteg konfigurációs paraméter az egyéb file-okkal foglalkozott, és nem a php értelmezővel. 2013 decemberében bemutatták az új, FastCGI felületét, amivel ezt a problémát megoldották, és a HHVM már tényleg csak a php kód kiszolgálásáért volt felelős.

Időközben kijött a 3.0-ás verzió, amiben ez a működési mód már nem létezik. Jóideje ez volt az ajánlás, és production/staging környezetben valószínűleg nem okoz meglepetést, debug során viszont olyan hasznos volt, mint a php beépített szervere.

A FCGI mód bekapcsolásához ezt kell beleírnunk a konfigunk Server blokkjába:

Server {
  // ...
  Type = fastcgi
  Post = 9000
  // vagy
  FileSocket = /var/run/hhvm.sock
}

Nginx példa:

root /var/www;
fastcgi_pass  unix:/var/run/hhvm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME$document_root$fastcgi_script_name;
include       fastcgi_params;

És hogy milyen teljesítménybeli javulást hoz ez? A gyári tesztek szerint egy stock wordpress telepítéssel ennyit (ab-val tesztelve):

PHP-FPM

Requests per second: 23.17 [#/sec] (mean)
Time per request:    2157.579 [ms] (mean)
Time per request:    43.152 [ms] (mean, across all concurrent requests)
Transfer rate:       275.42 [Kbytes/sec] received

HHVM + FastCGI

Requests per second: 184.71 [#/sec] (mean)
Time per request:    270.689 [ms] (mean)
Time per request:    5.414 [ms] (mean, across all concurrent requests)
Transfer rate:       2194.38 [Kbytes/sec] received

Már itt hatalmas a javulás, de aztán amikor rendesen beindul a JIT, még látványosabb:

Requests per second: 949.21 [#/sec] (mean)
Time per request:    52.676 [ms] (mean)
Time per request:    1.054 [ms] (mean, across all concurrent requests)
Transfer rate:       11276.46 [Kbytes/sec] received

Bár teljesen mesterséges teszt, a Fibonacci-számolás szépen kihozza az interpretált és a natív (JIT-fordított) kód teljesítménye közti különbséget:

PHP-FPM

Requests per second: 13789.24 [#/sec] (mean) Fib(5)
Requests per second: 3202.31 [#/sec] (mean)  Fib(15)
Requests per second: 118.94 [#/sec] (mean)   Fib(25)
Requests per second: 8.40 [#/sec] (mean)     Fib(30)

HHVM + FastCGI

Requests per second: 8842.70 [#/sec] (mean) Fib(5)
Requests per second: 8892.66 [#/sec] (mean) Fib(15)
Requests per second: 5581.37 [#/sec] (mean) Fib(25)
Requests per second: 737.56 [#/sec] (mean)  Fib(30)

Hack

Kicsit több, mint egy hete jelentették be a HHVM-re épülő Hack nyelvet. Ez egy jelentősen kibővített PHP, ezekkel a fontos újításokkal:

Skaláris típusok

Objektumokra, interfészekre, tömbökre most is lehet typehintelni, de skalárokra nem. A Hack ezt lehetővé teszi osztályváltozókra, konstansokra, függvényparaméterekre és -visszatérési értékekre:

<?hh

class MyClass {
  const int MyConst = 0;
  
  private string $x = '';
  
  public function increment(int $x): int {
    $y = $x + 1;
    return $y;
  }
}

Vegyük még észre, hogy a megszokott <?php helyett <?hh a nyitótag.

Genericek

Részben a típusosságból következik, hogy a C++ template-es osztályaival analóg módon tudunk általánosan, típusfüggetlenül saját osztályokat leírni, aztán JIT-fordítás időben létrejön belőle a konkrét típushoz való változat:

<?hh

class Box<T> {
    protected T $data;
    
    public function __construct(T $data) {
        $this->data = $data;
    }

    public function getData(): T {
        return $this->data;
    }
}

Collection-ök

A php tömbjei nagyon sokoldalúak, de pont emiatt bizonyos célokra nem ideálisak. A Hack behozta a C++ STL-jéből ismert Vector, Map, Set és Pair osztályokat, melyek egy része bár elérhető SPL-ből, ezek az új változatok mégis jóval többet tudnak - a típuskényszerítésen kívül is. Részletes bemutatás a HHVM dokumentációjában.

Új lamda syntax

Ez csak syntax sugar, de jóval egyszerűbben lehet névtelen függvényeket definiálni, mint php-ban:

<?hh

function foo(): (function(string): string) {
  $x = 'bar';
  return $y ==> $x . $y;
}
function test(): void {
  $fn = foo();
  echo $fn('baz'); // barbaz
}

Shape-ek

PHP-ban nincs struct típus. Ennek megoldására jön a shape, ami fix kulcsos tömböket jelent - ha valamely hiányzik vagy plusz van, akkor az hiba:

<?hh

type my_shape = shape(
    "field1" => int,
    "field2" => bool,
);

function first_shape(): my_shape {
    return shape("field1" => 42, "field2" => false);
}

Async függvényhívások

Ezt még nem sikerült teljesen felfognom. A lényege annyi, hogy aszinkron függvényhívást tesz lehetővé, amely a futása után meghív egy Callbacket. A node.js-re hasonlít, de további kísérletezést igényel. Részletes leírás.

A Hackkel további ismerkedéshez ajánlom az interaktív tutorialjukat.

Mire jó ez ma?

Célként tűzték ki, hogy minnél több frameworkkel legyenek kompatibilisek, az aktuális állás megtekinthető itt: frameworks. Eszerint például laravelt vagy codeignitert ma már nyugodtan futtathatunk rajta, nem fogunk hibába futni. Van azonban valami, ami a mindennapjainkat nagyban javíthatja:

Composer HHVM-el

$ alias hhvm-composer="hhvm -v ResourceLimit.SocketDefaultTimeout=30 
    -v Http.SlowQueryTreshold=30000 
    -v Eval.Jit=false /usr/local/bin/composer`

Az intl-függő csomagokkal probléma lehet, de ezen túllépve négy-ötszörös sebességnövekedést tapasztaltam. A beállítások a lassú hálózaból fakadó problémák elkerülésére szolgálnak, your mileage may vary.

PHPUnit HHVM-el

$ composer global require "phpunit/phpunit=4.0.*"

$ alias hhvm-phpunit="hhvm -v Eval.Jit=false ~/.composer/vendor/bin/phpunit"

Nagyon csábító dolog, de valamire nagyon figyelni kell: ekkor a HHVM stack ellen teszteljük a kódunkat.