Leccion 48 de 75 12 min de lectura

Mejoras de Rendimiento en PHP 8.x

PHP 8.x trae mejoras significativas de rendimiento gracias al compilador JIT, optimizaciones internas del motor y nuevas caracteristicas como preloading. Conoce como aprovecharlas.

El compilador JIT

JIT (Just-In-Time) es la mejora mas importante de PHP 8.0. Compila el codigo PHP a instrucciones de maquina en tiempo de ejecucion, eliminando la interpretacion para codigo que se ejecuta frecuentemente.

INI
; Habilitar JIT en php.ini
; Requiere OPcache habilitado

opcache.enable=1
opcache.jit_buffer_size=100M
opcache.jit=1255

; Modos JIT disponibles:
; 0 = deshabilitado
; 1205 = modo tracing (recomendado para web)
; 1255 = modo tracing agresivo
; 1235 = modo function

Cuando beneficia el JIT

El JIT mejora significativamente operaciones CPU-intensivas:

PHP
<?php

declare(strict_types=1);

// Operaciones matematicas intensivas: GRAN mejora con JIT
function calcularFibonacci(int $n): int
{
    if ($n <= 1) {
        return $n;
    }
    return calcularFibonacci($n - 1) + calcularFibonacci($n - 2);
}

// Procesamiento de imagenes, criptografia, ML: GRAN mejora

// Aplicaciones web tipicas: mejora MODERADA
// La mayoria del tiempo se va en I/O (base de datos, red)

// Verificar si JIT esta habilitado
if (function_exists('opcache_get_status')) {
    $status = opcache_get_status();
    if (isset($status['jit']['enabled'])) {
        echo 'JIT habilitado: ' . ($status['jit']['enabled'] ? 'Si' : 'No');
    }
}
JIT y aplicaciones web

Para aplicaciones web tipicas, el JIT ofrece mejoras del 5-10%. El beneficio real esta en tareas CPU-intensivas donde puede ser 2-3x mas rapido.

OPcache: la base del rendimiento

OPcache almacena el bytecode compilado de PHP en memoria compartida, evitando recompilar en cada peticion. Es fundamental para produccion:

INI
; Configuracion recomendada para produccion
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000

; En produccion: no revalidar archivos
opcache.validate_timestamps=0

; En desarrollo: revalidar cada 2 segundos
; opcache.validate_timestamps=1
; opcache.revalidate_freq=2
PHP
<?php

declare(strict_types=1);

// Verificar estado de OPcache
$status = opcache_get_status();

echo 'Memoria usada: ' . $status['memory_usage']['used_memory'];
echo 'Scripts cacheados: ' . $status['opcache_statistics']['num_cached_scripts'];
echo 'Hit rate: ' . $status['opcache_statistics']['opcache_hit_rate'] . '%';

// Limpiar cache (necesario despues de deploy)
opcache_reset();

Preloading (PHP 7.4+)

Preloading carga clases y funciones en memoria al iniciar PHP-FPM, haciendo que esten disponibles para todas las peticiones sin necesidad de cargarlas desde disco:

INI
; En php.ini
opcache.preload=/var/www/app/preload.php
opcache.preload_user=www-data
PHP
<?php

// preload.php - Se ejecuta una vez al iniciar PHP-FPM

// Precargar clases frecuentes
$archivos = [
    __DIR__ . '/src/Core/App.php',
    __DIR__ . '/src/Core/Router.php',
    __DIR__ . '/src/Core/Request.php',
    __DIR__ . '/src/Core/Response.php',
];

foreach ($archivos as $archivo) {
    if (file_exists($archivo)) {
        opcache_compile_file($archivo);
    }
}

// O usar el autoloader de Composer
require __DIR__ . '/vendor/autoload.php';

// Precargar clases especificas
$clases = [
    App\Core\App::class,
    App\Core\Router::class,
];

foreach ($clases as $clase) {
    if (class_exists($clase)) {
        // La clase ya esta cargada
    }
}

Optimizaciones internas de PHP 8.x

PHP 8.x incluye numerosas optimizaciones automaticas:

PHP
<?php

declare(strict_types=1);

// 1. Match es mas rapido que switch
// PHP optimiza match a tabla de saltos

$estado = match ($codigo) {
    200 => 'OK',
    404 => 'Not Found',
    500 => 'Server Error',
    default => 'Unknown',
};

// 2. Named arguments: sin penalizacion
// El compilador optimiza a llamadas posicionales

function crearUsuario(string $nombre, string $email, bool $activo = true): array
{
    return compact('nombre', 'email', 'activo');
}

// Esto se optimiza internamente
$user = crearUsuario(nombre: 'Ana', email: 'ana@example.com');

// 3. Nullsafe operator: optimizado
// Evita multiples comprobaciones

$ciudad = $usuario?->direccion?->ciudad;

// 4. Constructor promotion: mismo rendimiento
// Solo azucar sintactico, sin overhead

class Producto
{
    public function __construct(
        public readonly string $nombre,
        public readonly float $precio,
    ) {}
}

Mejoras en tipos y validacion

Los tipos estrictos mejoran el rendimiento al evitar conversiones implicitas:

PHP
<?php

declare(strict_types=1);

// Con tipos: PHP sabe exactamente que esperar
function calcular(int $a, int $b): int
{
    return $a + $b;
}

// Sin tipos: PHP debe inferir y posiblemente convertir
function calcularSinTipos($a, $b)
{
    return $a + $b; // ¿Son numeros? ¿Strings? PHP debe verificar
}

// Union types (PHP 8.0): eficientes
function procesar(int|float $valor): float
{
    return $valor * 1.5;
}

// Intersection types (PHP 8.1): validacion en compilacion
interface Serializable {}
interface Jsonable {}

function exportar(Serializable&Jsonable $objeto): string
{
    // PHP verifica tipos en compilacion, no en runtime
    return '';
}

Mejoras en arrays

PHP
<?php

declare(strict_types=1);

// Spread operator: optimizado para arrays grandes
$base = [1, 2, 3];
$extendido = [...$base, 4, 5, 6];

// array_is_list: O(1) en muchos casos
// PHP trackea internamente si un array es lista
$lista = [1, 2, 3];
$esLista = array_is_list($lista); // Muy rapido

// Unpacking con claves string (PHP 8.1)
$defaults = ['color' => 'azul', 'size' => 'M'];
$custom = ['color' => 'rojo'];
$merged = [...$defaults, ...$custom]; // ['color' => 'rojo', 'size' => 'M']

Buenas prácticas de rendimiento

PHP
<?php

declare(strict_types=1);

// 1. Usa readonly para inmutabilidad
// PHP puede optimizar mejor datos que no cambian
readonly class Config
{
    public function __construct(
        public string $appName,
        public string $environment,
    ) {}
}

// 2. Prefiere funciones nativas sobre regex simple
$texto = 'Hola mundo';

// Lento
if (preg_match('/mundo/', $texto)) {}

// Rapido
if (str_contains($texto, 'mundo')) {}

// 3. Evita crear objetos innecesarios en bucles
// Malo
foreach ($items as $item) {
    $formatter = new Formatter(); // Objeto nuevo cada iteracion
    echo $formatter->format($item);
}

// Mejor
$formatter = new Formatter();
foreach ($items as $item) {
    echo $formatter->format($item);
}

// 4. Usa generators para grandes conjuntos de datos
function leerArchivo(string $path): Generator
{
    $handle = fopen($path, 'r');
    while (($linea = fgets($handle)) !== false) {
        yield trim($linea);
    }
    fclose($handle);
}

// Procesa linea por linea, sin cargar todo en memoria
foreach (leerArchivo('datos.txt') as $linea) {
    procesar($linea);
}

Medir el rendimiento

PHP
<?php

declare(strict_types=1);

// Medir tiempo de ejecucion
$inicio = hrtime(true);

// ... codigo a medir ...

$fin = hrtime(true);
$nanosegundos = $fin - $inicio;
$milisegundos = $nanosegundos / 1e6;

echo "Tiempo: {$milisegundos}ms";

// Medir memoria
$memoriaInicial = memory_get_usage();

// ... codigo a medir ...

$memoriaFinal = memory_get_usage();
$diferencia = $memoriaFinal - $memoriaInicial;

echo "Memoria usada: " . ($diferencia / 1024) . " KB";

// Pico de memoria
echo "Pico: " . (memory_get_peak_usage() / 1024 / 1024) . " MB";

// Reiniciar pico (PHP 8.2)
memory_reset_peak_usage();

// Funcion simple de benchmark
function benchmark(callable $fn, int $iteraciones = 1000): float
{
    $inicio = hrtime(true);

    for ($i = 0; $i < $iteraciones; $i++) {
        $fn();
    }

    $fin = hrtime(true);
    return ($fin - $inicio) / 1e6 / $iteraciones;
}

$tiempoPromedio = benchmark(fn () => str_contains('texto', 'ext'));
echo "Promedio: {$tiempoPromedio}ms";

Ejercicios

Ejercicio 1: Comparar rendimiento

Crea un script que compare el tiempo de ejecucion de strpos() !== false vs str_contains() para 100,000 iteraciones.

Ver solucion
PHP
<?php

declare(strict_types=1);

$texto = 'Este es un texto de prueba para buscar palabras';
$buscar = 'prueba';
$iteraciones = 100000;

// Metodo antiguo: strpos
$inicio = hrtime(true);
for ($i = 0; $i < $iteraciones; $i++) {
    $encontrado = strpos($texto, $buscar) !== false;
}
$tiempoStrpos = (hrtime(true) - $inicio) / 1e6;

// Metodo nuevo: str_contains
$inicio = hrtime(true);
for ($i = 0; $i < $iteraciones; $i++) {
    $encontrado = str_contains($texto, $buscar);
}
$tiempoContains = (hrtime(true) - $inicio) / 1e6;

echo "strpos: {$tiempoStrpos}ms\n";
echo "str_contains: {$tiempoContains}ms\n";
echo "Diferencia: " . ($tiempoStrpos - $tiempoContains) . "ms";

Ejercicio 2: Generador vs Array

Compara el uso de memoria entre cargar un millon de numeros en un array vs usar un generador.

Ver solucion
PHP
<?php

declare(strict_types=1);

$cantidad = 1000000;

// Con array: carga todo en memoria
function crearArray(int $n): array
{
    $numeros = [];
    for ($i = 0; $i < $n; $i++) {
        $numeros[] = $i;
    }
    return $numeros;
}

// Con generador: usa memoria minima
function crearGenerador(int $n): Generator
{
    for ($i = 0; $i < $n; $i++) {
        yield $i;
    }
}

// Medir array
$memoriaInicio = memory_get_usage();
$array = crearArray($cantidad);
$suma = array_sum($array);
$memoriaArray = memory_get_usage() - $memoriaInicio;
unset($array);

// Medir generador
memory_reset_peak_usage();
$memoriaInicio = memory_get_usage();
$suma = 0;
foreach (crearGenerador($cantidad) as $numero) {
    $suma += $numero;
}
$memoriaGenerador = memory_get_usage() - $memoriaInicio;

echo "Array: " . ($memoriaArray / 1024 / 1024) . " MB\n";
echo "Generador: " . ($memoriaGenerador / 1024) . " KB";

Ejercicio 3: Verificar OPcache

Crea una funcion que muestre el estado de OPcache y JIT de forma legible.

Ver solucion
PHP
<?php

declare(strict_types=1);

function mostrarEstadoOpcache(): void
{
    if (!function_exists('opcache_get_status')) {
        echo "OPcache no esta disponible\n";
        return;
    }

    $status = opcache_get_status(false);

    if ($status === false) {
        echo "OPcache esta deshabilitado\n";
        return;
    }

    echo "=== OPcache ===\n";
    echo "Habilitado: Si\n";

    $memoria = $status['memory_usage'];
    $usada = $memoria['used_memory'] / 1024 / 1024;
    $libre = $memoria['free_memory'] / 1024 / 1024;
    echo "Memoria usada: " . round($usada, 2) . " MB\n";
    echo "Memoria libre: " . round($libre, 2) . " MB\n";

    $stats = $status['opcache_statistics'];
    echo "Scripts cacheados: {$stats['num_cached_scripts']}\n";
    echo "Hit rate: " . round($stats['opcache_hit_rate'], 2) . "%\n";

    if (isset($status['jit'])) {
        echo "\n=== JIT ===\n";
        $jit = $status['jit'];
        echo "Habilitado: " . ($jit['enabled'] ? 'Si' : 'No') . "\n";
        if ($jit['enabled']) {
            echo "Buffer: " . ($jit['buffer_size'] / 1024 / 1024) . " MB\n";
        }
    }
}

mostrarEstadoOpcache();

Has completado el modulo PHP 8.x

Continua con el manejo de errores y excepciones.

Descubrir cursos premium