Googles V8-Javascript-Engine kommt u.a. in Chrome und NodeJS zum Einsatz und ist anerkanntermaßen sauschnell. Umgesetzt wird der Sprachstandard ECMA-262 (5th edition), was aktuell „bleeding edge“ ist. Nun gibt es die Möglichkeit, v8 zu experimentellen Zwecken auch mal aus PHP heraus zu bedienen. Dazu liegt v8js als PECL-Paket vor und kann recht schmerzfrei installiert werden. Aber step by step.
v8js installieren
Habe eben unter Ubuntu 12.04 erfolgreich folgendes Prozedere durchgeführt:
sudo apt-get install php5-dev php-pear libv8-dev build-essential sudo pecl install pecl install channel://pecl.php.net/v8js-0.1.3 sudo echo extension=v8js.so >>/etc/php5/cli/php.ini
Dabei ist 0.1.3 die derzeit aktuellste v8js-Version, also einfach nachschauen was gerade aktuell ist. Soll auch die Apache-PHP-Version v8 abbekommen:
sudo echo extension=v8js.so >>/etc/php5/apache2/php.ini
Testen tun wir das ganze per phpinfo() bzw. mit
php -m | grep v8
Ich habe hier PHP 5.4.4.4 verwendet, sollte aber auch gut unter PHP 5.3 funktionieren.
Die PHP-Dokumentation von v8js ist übrigens noch sehr spärlich.
Code!
$a = new V8Js(); $a->executeString('var x = function(a,b,c) { return a+b+c }; print(x(1,2,3));'); //6
Die Methode executeString ist die Hauptanlaufstelle für Arbeiten mit v8js, da kommt dann einfach der Javascript-Code rein. Hier wird die Zahl 6 einfach ausgegeben, alternativ können wir aber auch einen Wert aus executeString zurückgeben, und zwar den letzt instanziierten. Beispiel hierzu:
$res = $a->executeString('var x = function(a,b,c) { return a+b+c }; var ret = x(1,2,3); ret;'); echo $res; //6
Die Variable ret, die das Ergebnis der Addition beinhaltet, wird an PHP zurückgegeben.
Interaktion von PHP und JS
$a = new V8Js(); $a->func = function ($a) { echo "Closure with param $a\n"; }; $a->executeString("print(PHP.func);"); //[object Closure] $a->executeString("PHP.func(1);"); //Closure with param 1
Hier greift JS auf eine vorher definierte PHP-Closure zu. Smooth!
OOP?
class Testing { public $pub = 'pub'; private $priv = 'priv'; protected $prot = 'prot'; public function test($a, $b, $c) { var_dump($a, $b, $c); } } $a = new V8Js(); $a->testing = new Testing(); $a->executeString("PHP.testing.test(PHP.testing.pub, PHP.testing.priv, PHP.testing.prot);"); /* string(3) "pub" NULL NULL */
Man sieht: Kein Zugriff auf protected oder private-Variablen.
Arrays?
$a = new V8Js(); $a->testing = function ($arr) { echo array_sum($arr); }; $a->executeString("PHP.testing([1,2,3]);"); //6
Alles kein Problem, Arrays können schonungslos weiterverarbeitet werden.
Extensions
V8 bietet es an, mittels der Methode registerExtensions eine Erweiterung zu registrieren, die dann später wieder aufgerufen werden kann. Dazu die Signatur der Methode vorab:
public static bool V8Js::registerExtension ( string $extension_name , string $script [, array $dependencies = array() [, bool $auto_enable = FALSE ]] )
Ein Beispiel verdeutlicht das Vorgehen:
$extension_name = "foo_extension"; $extension_code = 'var foo=123;'; $dependencies = array(); $auto_enable = false; V8Js::registerExtension($extension_name, $extension_code, $dependencies, $auto_enable); $extension_name = "bar_extension"; $extension_code = 'var bar=foo+1;'; $dependencies = array("foo_extension"); $auto_enable = true; V8Js::registerExtension($extension_name, $extension_code, $dependencies, $auto_enable);
Wir registrieren also 2 Extensions, die erste der beiden wird nicht automatisch geladen. Allerdings baut die zweite Extension auf der ersten auf, sodass Extension 1 automatisch aktiviert wird.
print_r(V8JS::getExtensions());
verrät uns: foo_extension und bar_extension sind registriert. Mit folgendem Testcode sehen wir den Effekt:
$v8 = new V8Js(); $v8->executeString('if (bar == 124) print("works!");'); //works
Extensions sind also super für wiederkehrende Aufgaben, die immer initial definiert sein sollten. Vorstellbar sind hier bspw. Javascript-Libraries oder Erweiterungen der Sprache, bspw. Array.prototype.contains, was man ja sehr häufig vorfindet.
Benchmark!
Aber ist der Spaß denn auch schnell? Das darf man von der gefeierten V8-Engine ja wohl erwarten. Hierzu habe ich ein nutzloses Beispielscript geschrieben, welches 10000000 mal den String abcd an ein Array anhängt, das Array dann zu einem String macht und abschließend iterativ die Anzahl der a’s ermittelt – keine große Sache.
function testing_php() { $start = microtime(true); $array = array(); $count = 10000000; while ($count--) $array[] = "abcd"; $string = implode("", $array); $a_count = 0; for ($i = 0; $i < strlen($string); $i++) if ($string[$i] == "a") $a_count++; echo "counted $a_count a's in php\n"; echo "php took " . (microtime(true) - $start) . "s\n"; } function testing_v8() { $start = microtime(true); $v8 = new V8Js(); $js_code = 'var array = [], count = 10000000; while (count--) array.push("abcd"); var string = array.join(""); var a_count = 0; for (var i = 0; i < string.length; i++) if (string[i] == "a") a_count++; print("counted " + a_count + " a\'s in js\\n");'; $v8->executeString($js_code); echo "v8 took ". (microtime(true) - $start) . "s\n"; } testing_php(); testing_v8();
Ergebnisse?
counted 10000000 a's in php php took 31.238667011261s counted 10000000 a's in js v8 took 2.8664078712463s
Ist sicher nicht der ultimative Beweis der Performanz-Überlegenheit von v8 gegenüber PHP, gibt aber trotzdem zu denken. Javascript in PHP ist schneller als PHP selbst – ok! Zusammenfassend lässt sich wohl sagen, dass das ein Projekt mit einigem Potential ist. Sogar Datenbankzugriff ist durch PHP aus V8 heraus möglich.
Abschließend sei noch auf einen v8 – Githubaccount verwiesen, in dem sich nette Beispiele finden.
„Javascript in PHP ist schneller als PHP selbst“
Was zum Teufel?! Evtl. weil das Javascript-Array nicht wie das PHP-Array im RAM zur Verfügung steht?
Woher kommt eigentlich die Javascript-Funktion „print“? Ist die von V8 reserviert? Und was ist wenn mein JS-Code auch eine Funktion mit diesem Namen enthält? Was passiert , wenn man document.write() macht oder ähnliches, wird das dann auch zurückgegeben?