El profiling es el proceso de medir qué partes de tu código consumen más tiempo y memoria. En PHP, las herramientas principales son Xdebug en modo profiling, Blackfire.io y SPX. Antes de optimizar, mide: la intuición sobre el cuello de botella falla más de la mitad de las veces.
microtime() y memory_get_peak_usage() para medición puntual
<?php
function medirTiempo(callable $fn, string $etiqueta): void
{
$inicio = microtime(true);
$memInicio = memory_get_usage();
$fn();
$fin = microtime(true);
$mem = memory_get_peak_usage(true);
printf(
"[%s] Tiempo: %.4f s | Memoria pico: %sn",
$etiqueta,
$fin - $inicio,
number_format($mem / 1024 / 1024, 2) . ' MB'
);
}
medirTiempo(function () {
$datos = array_map(fn($i) => $i * $i, range(1, 100_000));
sort($datos);
}, 'array_map + sort');
?>
Xdebug en modo profiling
Xdebug genera ficheros Cachegrind que analizan herramientas como KCachegrind (Linux) o QCacheGrind (macOS/Windows).
[xdebug] zend_extension=xdebug.so xdebug.mode=profile xdebug.output_dir=/tmp/xdebug-profiles xdebug.profiler_output_name=cachegrind.out.%p.%t
Cada petición genera un fichero cachegrind.out.*. Abre ese fichero en KCachegrind para ver el árbol de llamadas, el tiempo inclusive/exclusivo y el número de invocaciones por función.
Activar el profiling solo en peticiones concretas
xdebug.mode=profile xdebug.start_with_request=trigger
Con este ajuste, Xdebug solo genera el perfil cuando la petición incluye el trigger:
# GET con query string curl 'http://localhost/lento.php?XDEBUG_PROFILE=1' # Cookie (para sesiones autenticadas) curl -b 'XDEBUG_PROFILE=1' http://localhost/dashboard
Blackfire.io
Blackfire es un servicio de profiling continuo con una interfaz web muy visual. Instala el agente y la extensión:
# Instalar agente curl -sS https://packages.blackfire.io/gpg.key | sudo tee /etc/apt/trusted.gpg.d/blackfire.asc sudo apt install blackfire blackfire-php # Perfilar una URL blackfire curl http://localhost/mi-endpoint # Comparar dos versiones blackfire curl --reference 1 http://localhost/mi-endpoint # línea base # (Haz cambios en el código) blackfire curl http://localhost/mi-endpoint # comparativa
Blackfire muestra un grafo de llamadas interactivo, marca los cuellos de botella en rojo y proporciona una puntuación de rendimiento. Su modo --samples 5 ejecuta 5 peticiones y promedia los resultados para reducir el ruido.
SPX (Simple PHP eXtension)
SPX es una alternativa open source a Blackfire con interfaz web local, sin cuenta externa:
git clone https://github.com/NoiseByNorthwest/php-spx cd php-spx && phpize && ./configure && make && sudo make install
extension=spx.so spx.http_enabled=1 spx.http_key="clave-secreta" spx.http_ip_whitelist="127.0.0.1"
Accede a http://localhost/?SPX_KEY=clave-secreta&SPX_UI_URI=/ para ver la interfaz. Activa el profiling con el parámetro SPX_ENABLED=1 en la URL.
Interpretando los resultados
- Wall time (tiempo de pared): tiempo real transcurrido, incluye esperas de I/O.
- CPU time: tiempo de procesador activo, excluye esperas.
- Self time vs. inclusive time: el self time excluye el tiempo en funciones llamadas desde esa función. Si una función tiene alto inclusive pero bajo self, el cuello de botella está en sus hijos.
- Memoria: picos altos en funciones de array o de strings grandes.
Patrones de optimización habituales
<?php
// LENTO: consulta dentro de un bucle (N+1)
foreach ($usuarios as $usuario) {
$pedidos = $pdo->query("SELECT * FROM pedidos WHERE user_id = {$usuario['id']}")->fetchAll();
}
// RÁPIDO: una sola consulta con IN
$ids = array_column($usuarios, 'id');
$in = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM pedidos WHERE user_id IN ($in)");
$stmt->execute($ids);
$pedidos = $stmt->fetchAll();
// Agrupa por user_id en PHP
$pedidosPorUsuario = [];
foreach ($pedidos as $pedido) {
$pedidosPorUsuario[$pedido['user_id']][] = $pedido;
}
?>
OPcache como primera optimización
opcache.enable=1 opcache.memory_consumption=256 opcache.interned_strings_buffer=16 opcache.max_accelerated_files=20000 opcache.validate_timestamps=0 ; en producción
Errores comunes
- Perfilar en desarrollo, no en producción: el overhead de Xdebug en modo profile puede ser 10×-50×. Usa Blackfire con
--samplesbajo o SPX con trigger para producción. - Optimizar lo que no importa: una función llamada 1000 veces que tarda 0,01 ms da 10 ms en total. Enfócate en las que tienen alto inclusive time en el árbol principal.
- No comparar antes y después: guarda el perfil de referencia antes de cambiar el código; sin comparativa, no sabes si mejoró o empeoró.
