Este es el primer post sobre una serie de posts que quiero escribir sobre creación de compiladores y generación de código.

Para esta serie de posts he creado un proyecto en github con una pequeña implementación de PHP que permite ejecutar algunos programas pequeños.

Proyecto en github:

Submódulos míos usados:

Proyectos externos usados:

Para inventar pegarme una matada con parsers y lexers como ya ha hecho muchas veces: https://github.com/soywiz/as3evalhttps://github.com/soywiz/csharputils/tree/master/CSharpUtils/CSharpUtils.Templateshttps://github.com/soywiz/atpl.js he decidido usar Irony, un proyecto genial que permite definir gramáticas de una forma muy sencilla. Así que este tutorial irá dedicado a usar Irony y a la generación de código.

Inicialmente iba a usar goldparser que era el que conocía (y que además permitía importar las sintaxis del YACC), pero los tiempos de compilación que vi eran nefastos y probé irony, que es muy rápido (aunque la inicialización es algo lenta la verdad). Conservo las pruebecitas del goldparser aquí: https://github.com/soywiz/nphp/tree/master/sandbox

En la actualidad hay un compilador de PHP para .NET: Phalanger.

El propósito de este proyecto es diferente. Phalanger compila código PHP en un ejecutable .NET. La generación de código es muy lenta y es más bien una compilación estática. Los resultados son espectaculares, pero para ejecutar scripts sencillos de forma volátil no sirve. Además la implementación es muy grande y compleja.

Así que he creé NPhp como una microimplementación de PHP, que permitirá analizar cómo implementar ciertos aspectos de PHP. Y además tiene unos tiempos de generación de código muy rápidos y la ejecución en ocasiones es bastante superior a la de PHP nativo.

Algunos números antes de empezar:

Ejemplo 1 (bucle vacío en ámbito global)

test.php:
<?php
$start = microtime(true);
for ($n = 0; $n < 10000000; $n++) { }
$end = microtime(true);
$elapsed = $end - $start;
$version = PHP_VERSION;
echo "Time {$version}: {$elapsed}";
?>

nphp test.php
Time 5.4.0-NPHP: 1.43905878067017
php test.php
Time 5.3.5: 2.0970261096954
php test.php
Time 5.4.0RC8: 0.53435206413269
phalanger\test.exe
Time 5.2.6: 2.16012358665466

Ejemplo 2 (bucle con llamada a función nativa y local)

<?php
function test($a, $b, $c) {
 return $a + $b * $c;
}
function func() {
 $start = microtime(true);
 $str = 'Hello World!';
 $sum = 0;
 for ($n = 0; $n < 1000000; $n++) { $sum += strlen($str); test($n, $n, 3); }
 $end = microtime(true);
 $elapsed = $end - $start;
 $version = PHP_VERSION;
 echo "Time {$version}: {$elapsed}";
}
func();
?>

nphp test.php
Time 5.4.0-NPHP: 1.43308210372925
php test.php
Time 5.3.5: 2.6657180786133
c:\dev\php54\php test.php
Time 5.4.0RC8: 0.5342710018158
phalanger\test.exe
Time 5.2.6: 0.117006540298462

Lo que más llama la atención sin duda es phalanger. Que se comporta “TAN mal” (como PHP 5.3) en el primer ejemplo y TAN BIEN en el segundo siendo 5 veces más rápido que PHP 5.4.

El motivo es simplemente que phalanger no se la juega con variables globales que pueden ser accedidas desde cualquier ámbito, mientras que en un ámbito local donde no se pueden acceder a la variable si no es con formulas dinámicas de $var o mediante get_defined_vars. Imagino que si no detecta ninguna de esas dos cosas, hará un análisis estático de las variables para ver tipos. También está ahora el DLR, que debería mejorar también el tema.

En cualquier caso podemos observar que mi implementación es un poco más rápida que PHP 5.3 en Windows cosa remarcable teniendo en cuenta que no he hecho ninguna optimización sustancial y que he hecho el compilador en escasos días.

En el próximo post, que espero escribir hoy, trataré expresiones y la estructura de control IF.