>_ DevTrendses

Idioma

Inicio

Lenguajes

Secciones

Frontend Backend Móvil DevOps AI / ML GameDev Blockchain Embebidos Seguridad
PHP

Compila PHP en un binario nativo sin intérprete

Hace unos quince años, Facebook intentó resolver los problemas de rendimiento de su monolito con el compilador HPHPc, que convertía PHP a C++. Más tarde, abandonaron este enfoque en favor de la máquina virtual HHVM, y PHP 8 introdujo un JIT integrado. Parecía que la idea de un compilador puro Ahead-Of-Time para PHP había quedado en la historia para siempre. El equipo de Swoole decidió revisarlo y lanzó el proyecto TypePHP.

Este es un compilador AOT completo escrito en PHP. Traduce código fuente PHP 8.4+ a C++17, y luego lo compila mediante GCC o Clang en código máquina nativo. Sin opcodes en tiempo de ejecución, sin calentamiento JIT, y sin intérprete en el runtime.

El compilador es completamente autoalojado. La utilidad tpc se construye a sí misma desde fuentes PHP, sin ningún código C de pegamento dentro del propio compilador.

Cómo funciona TypePHP

La compilación ocurre en dos etapas. Primero, el analizador procesa el código del proyecto, recolecta metadatos sobre clases y funciones, y construye una tabla de símbolos completa. En la segunda etapa, los cuerpos de funciones se traducen a C++17. En rutas frecuentes, el compilador genera C++ estático rápido, mientras que para construcciones dinámicas como reflexión o llamadas a funciones del sistema, se conecta al runtime de Zend a través de la capa PHPX.

La salida puede ser en uno de tres formatos:

  • Binario ejecutable (bin). Un archivo independiente para utilidades de consola y demonios. Requiere un punto de entrada main().
  • Extensión PHP (ext). Un módulo regular .so o .dll que puede cargarse en php.ini estándar.
  • Biblioteca dinámica (lib). Un archivo binario con archivos .stub.php generados para llamar desde otros proyectos.
  • Componente WASI para ejecutar en un entorno WebAssembly.

Qué cambia en la sintaxis

TypePHP no intenta soportar todos los trucos dinámicos del PHP estándar. Los creadores apostaron por la tipificación estricta donde se necesita máxima velocidad.

Tipos escalares nativos

La directiva use native_types le indica al compilador que mapee int, float y bool directamente a tipos C++ (int64_t, double, bool). Una variable con tal tipo ya no puede cambiar de tipo de repente en medio de la ejecución de una función. Pero el procesador ejecuta instrucciones máquina puras sin desempaquetar zval.

<?php
use native_types;

function fib(int $n): int
{
    if ($n == 1 || $n == 2) {
        return 1;
    }
    return fib($n - 1) + fib($n - 2);
}

function main(int $argc, array $argv): void
{
    $n = (int)$argv[1];
    $begin = microtime(true);
    echo fib($n) . "\n";
    echo "Time: " . (microtime(true) - $begin) . "\n";
}

Se construye con un solo comando:

bin/tpc.php fib.php -O3 -o fib
./fib 35

Contenedores con tipificación estricta

Los arrays regulares de PHP son universales pero consumen mucha memoria y son lentos debido a las tablas hash. TypePHP añade estructuras std::vector, std::array, std::map y std::ordered_map.

<?php
use native_types;

function main(): void
{
    $vector = std::vector(Type::Int);
    $vector[] = 10;
    $vector[] = 20;
    $vector[] = 30;

    $sum = 0;
    foreach ($vector as $val) {
        $sum += $val;
    }

    echo "Sum: " . $sum . "\n";
    
    $map = std::ordered_map(Type::String, Type::Int);
    $map["alpha"] = 1;
    $map["beta"] = 2;
}

En las pruebas de los desarrolladores, un bucle que actualizaba elementos en std::array tomó 6.4 segundos frente a 67.6 segundos para un array PHP regular con JIT habilitado. La velocidad esencialmente igualó la de un vector C++ escrito a mano (6.2 segundos).

Métodos en primitivos

En lugar de un montón de funciones como strlen(), strtoupper() o in_array(), puedes llamar métodos directamente en tipos base. El compilador resuelve estas llamadas en tiempo de compilación y las convierte en llamadas directas a funciones C sin sobrecarga de tabla virtual:

<?php
function main(): void
{
    $str = "hello world";
    echo $str->upper() . "\n";
    echo $str->substr(0, 5) . "\n";

    $items = [1, 3, 5, 7];
    var_dump($items->contains(3));
}

Generación de código de plantilla mediante atributos

Para evitar escribir docenas de getters y setters a mano, TypePHP procesa atributos personalizados durante la compilación:

<?php
#[Printer(fields: ['id', 'name'])]
#[Arrayable(fields: ['id', 'name'])]
final class User
{
    #[Constructor, Getter, With]
    public int $id;

    #[Constructor, Getter, Setter]
    public string $name = 'guest';
}

function main(): void
{
    $user = new User(1);
    $user->setName('Ivan');

    $copy = $user->withId(2);
    echo $user->getId() . "\n"; // 1
    echo $copy->getId() . "\n"; // 2
    echo $user . "\n";          // User(id=1, name=Ivan)
}

El atributo #[With] genera un método que clona el objeto, cambia el campo y devuelve una nueva instancia. Esto es conveniente para DTOs inmutables.

Integración directa con C++

Si algún algoritmo falta en PHP, puedes escribirlo en C++ y colocarlo cerca. La vinculación ocurre mediante un archivo stub con un cuerpo vacío:

// math.cpp
#include <phpx.h>

using namespace php;

Int php_fast_sum(Int a, Int b) {
    return a + b;
}
<?php
// math.stub.php
function fast_sum(int $a, int $b): int {}
<?php
// main.php
function main(): void
{
    echo fast_sum(10, 20) . "\n";
}

Rendimiento en benchmarks

Según las mediciones de los autores en pruebas sintéticas estándar del repositorio php-src (bench.php y micro_bench.php con el flag -O3):

  • bench.php se completa en 0.603 s frente a 5.034 s del intérprete estándar (aproximadamente 8x más rápido);
  • micro_bench.php se ejecuta en 2.021 s frente a 13.045 s (6.5x más rápido).

Los números son los esperados para compilación AOT de operaciones matemáticas y ramificación. En aplicaciones web reales, la mayor parte del tiempo se va en operaciones de E/S y base de datos, así que la mejora será más modesta allí. Pero para tareas computacionales y workers en segundo plano, la diferencia es notable.

Limitaciones y compromisos

Todavía no puedes portar un proyecto Laravel o Symfony existente a TypePHP. Hay una serie de reglas estrictas:

  • El código ejecutable en ámbito global está prohibido, todo el código debe vivir dentro de funciones o métodos.
  • Se requiere PHP 8.4 o 8.5 con la biblioteca libphp.so compilada (embed SAPI) para construir un binario.
  • Necesitas GCC 9+ con soporte para C++17, CMake y las bibliotecas GMP/MPFR para cálculos precisos.
  • Algunas características dinámicas del lenguaje, como el cambio libre de tipos o las referencias complejas, no están soportadas intencionalmente.

El archivo de configuración project.yml ayuda a gestionar dependencias y flags de optimización en proyectos grandes:

name: myapp
mode: bin
php-version: "8.5"
optimize: 2
job: 8
build-dir: build
cxx-std: c++17

sources:
  - src
  - cpp-src

link-libs:
  - curl

Quién se beneficiaría de este proyecto

TypePHP está en desarrollo activo. Tiene menos de mil estrellas en GitHub hasta ahora, pero el proyecto cuenta con el respaldo del experimentado equipo de Swoole.

Tiene sentido probar el proyecto si necesitas:

  • Construir una utilidad CLI ligera o microservicio como un binario único sin necesidad de instalar PHP en el servidor destino.
  • Ocultar el código fuente de la aplicación al entregar a un cliente on-premise, ya que los binarios son significativamente más difíciles de descompilar que el bytecode.
  • Escribir una extensión PHP con cálculos pesados sin sumergirse profundamente en C y la API de Zend.
  • Acelerar módulos computacionales aislados, como parsers, empaquetadores de datos o algoritmos de puntuación.

Para comenzar, solo clona el repositorio, construye un script de prueba mediante bin/tpc.php app.php y mira el C++ generado en el directorio build.

Proyectos relacionados