PHP y rendimiento en 2026: JIT, OPcache, profiling y lo que realmente importa

Hay una pregunta que aparece cada poco tiempo en los foros y en los equipos: ¿cómo hago que mi app PHP vaya más rápida? Y la respuesta más honesta es: depende de dónde pierdas el tiempo. Pero antes de meterte a perfilar nada, hay una cosa que deberías comprobar.

OPcache: lo primero y lo más importante

Si tu servidor PHP no tiene OPcache activo, todo lo demás da igual. OPcache cachea el bytecode compilado de cada script PHP, de manera que el servidor no tiene que recompilar el código en cada petición. Sin él, cada request hace el trabajo desde cero.

La configuración mínima en php.ini:

opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60

El parámetro memory_consumption está en megabytes. Con 256 MB vas bien para la mayoría de proyectos medianos. Si tienes una app grande con muchos archivos, sube a 512 MB.

Para producción, añade esto:

opcache.validate_timestamps=0

Con esta opción desactivada, PHP no comprueba si los archivos han cambiado desde que los cacheó. Es más rápido, pero tienes que invalidar la caché manualmente cuando despliegas. Lo puedes hacer reiniciando PHP-FPM o llamando a opcache_reset() desde un script de despliegue.

Para verificar que OPcache está activo:

php -i | grep opcache

Busca opcache.enable => On => On. Si no aparece, revisa que la extensión esté cargada en tu php.ini o en los ficheros de conf.d/.

OPcache preloading: cargar el código una vez al arrancar

El preloading va un paso más allá. Con esta opción le dices a PHP-FPM que ejecute un script al arrancar y cargue en memoria las clases que vas a usar en todas las peticiones. A partir de ese momento, todos los workers del pool ya tienen esas clases compiladas disponibles.

opcache.preload=/var/www/tu-proyecto/preload.php
opcache.preload_user=www-data

El script de preloading puede ser algo tan sencillo como esto:

<?php
$files = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator('/var/www/tu-proyecto/src')
);
foreach ($files as $file) {
    if ($file->getExtension() === 'php') {
        opcache_compile_file($file->getPathname());
    }
}

En Laravel, las buenas candidatas son las clases del núcleo del framework y los providers que siempre se cargan. El propio Laravel Octane ya se encarga de esto si lo usas.

La ganancia es real: en apps con mucho código de framework puedes ver entre un 10 y un 40% de mejora en tiempo de respuesta solo con el preloading bien configurado.

JIT: qué es y cuándo te ayuda de verdad

El JIT (Just-In-Time compiler) llegó con PHP 8.0 y genera código nativo de CPU en tiempo de ejecución, en vez de interpretar el bytecode. PHP 8.1, 8.2 y 8.3 lo han ido mejorando.

Dicho esto, para una web típica el JIT aporta entre un 1 y un 3% de mejora. El cuello de botella en la mayoría de apps PHP no está en la CPU sino en la I/O: consultas a la base de datos, llamadas a APIs externas, lectura de ficheros. Ahí el JIT no ayuda nada.

Donde sí marca la diferencia es en código CPU-intensivo: procesamiento de imágenes, cálculos matemáticos, generación de PDFs grandes, algoritmos de búsqueda pesados. Si tu app hace ese tipo de trabajo, el JIT puede darte ganancias significativas.

Configurar el JIT

opcache.jit=tracing
opcache.jit_buffer_size=128M

tracing es el modo más agresivo y el que mejor funciona en la práctica. En PHP 8.2 y posteriores, opcache.jit=on es un alias de tracing, así que puedes usar cualquiera de los dos.

jit_buffer_size es la memoria que reservas para el código compilado por JIT. Con 128 MB va bien para la mayoría de casos. Si tienes mucho código CPU-intensivo, sube a 256 MB.

Para la mayoría de apps web: activa el JIT con esta configuración y olvídate. Si algo se rompe, que es muy raro, desactívalo con opcache.jit=off.

Profiling: mide antes de optimizar

Nada de lo anterior tiene sentido si no sabes dónde gasta el tiempo tu app. El profiling te lo dice con datos reales.

Xdebug

Xdebug tiene un modo de profiling que genera ficheros cachegrind:

xdebug.mode=profile
xdebug.output_dir=/tmp/xdebug
xdebug.profiler_output_name=cachegrind.out.%p

Con esto activo, cada petición genera un fichero en /tmp/xdebug/ que puedes abrir con KCachegrind (Linux) o Webgrind (web). Verás exactamente qué funciones consumen más tiempo y cuántas veces se llaman.

Para profilear un comando de Artisan sin tocar la configuración global:

php -d xdebug.mode=profile -d xdebug.output_dir=/tmp artisan route:list

Ojo: no dejes el profiling activo en producción. Genera ficheros por cada petición y eso se acumula rápido.

Blackfire

Blackfire es la alternativa de pago, aunque tiene un plan gratuito limitado que cubre casos básicos. Se integra con la CLI y con una extensión de Chrome/Firefox. La visualización es mucho más cómoda que los ficheros cachegrind: te muestra un grafo de llamadas interactivo con los tiempos de cada función.

Para una sola petición desde la CLI:

blackfire run php artisan command:name

Si trabajas en equipo y el rendimiento es una preocupación constante, Blackfire vale la pena. Si es algo puntual, Xdebug hace el trabajo.

PHP-FPM: ajustar el pool de workers

El número de workers de PHP-FPM determina cuántas peticiones puedes manejar en paralelo. Con la configuración por defecto puedes estar dejando capacidad sin usar o, al contrario, quedándote sin RAM.

En /etc/php-fpm.d/www.conf o donde tengas tu pool:

pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500

Cada worker PHP consume entre 30 y 50 MB de RAM según la app. Si tienes 4 GB dedicados a PHP, el máximo razonable está en torno a 80-100 workers. Más de eso y empezarás a usar swap, que es peor que tener menos workers.

pm.max_requests = 500 reinicia cada worker después de 500 peticiones. Esto evita que pequeñas fugas de memoria se acumulen durante días. No es un parche para código malo, pero sí un colchón práctico.

Redis para caché y sesiones

Guardar sesiones en ficheros funciona, pero Redis es más rápido y escala mejor en entornos con varios servidores. Cambiar a Redis en php.ini:

session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379"

En Laravel, en .env:

CACHE_DRIVER=redis
SESSION_DRIVER=redis

Más allá de las sesiones, Redis te sirve para cachear lo que más tiempo cuesta generar: consultas complejas, fragmentos de HTML, respuestas de APIs de terceros. En Laravel:

$resultado = Cache::remember('productos-destacados', 3600, function () {
    return Producto::with('categoria')->where('destacado', 1)->get();
});

Con 3600 segundos de TTL, esa query solo se ejecuta una vez por hora. El resto de peticiones sacan el resultado de Redis.

Lo que más impacta en la práctica

Después de todo lo anterior, hay un par de cosas que conviene tener claras.

Los índices en la base de datos. Una query sin índice en una tabla grande destruye cualquier ganancia que hayas conseguido con OPcache o JIT. El profiling te lo va a mostrar enseguida: si una función de acceso a datos tarda 800 ms, ahí está el problema.

El lazy loading en Eloquent. Si accedes a relaciones dentro de un bucle sin haberlas cargado con with(), estás haciendo N+1 queries. Con with('relacion') en la query principal, una sola query carga todo.

El autoloader de Composer en producción:

composer install --no-dev --optimize-autoloader

Con --optimize-autoloader Composer genera un classmap en vez de escanear directorios PSR-4 en cada petición. En proyectos grandes la diferencia es apreciable.

Y si usas Laravel, estos comandos al desplegar:

php artisan config:cache
php artisan route:cache
php artisan view:cache

Cachean la configuración, las rutas y las vistas compiladas. Sin ellos, Laravel recarga esos datos en cada petición.

Por último, el HTTP caching. Para respuestas que no cambian entre peticiones, los headers Cache-Control y ETag permiten que el cliente o un proxy sirva la respuesta sin llegar a PHP. Es la optimización más barata que existe: la petición ni llega a tu servidor.

Si quieres profundizar en el lado de Laravel, te puede venir bien echar un vistazo a rendimiento en Laravel: las técnicas que más impactan y a cómo dar el salto de PHP en producción: del rendimiento a la escalabilidad en tiempo real.

Imagen: Pexels / Meet Patel

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP