Algunas cargas de trabajo (trading, servidores de juego, VoIP, control industrial) no se miden por la latencia media sino por la peor: un pico de varios milisegundos cada pocos segundos basta para estropearlas. Esos picos suelen venir del propio sistema operativo: cambios de frecuencia de la CPU, interrupciones, el tick del planificador u otros procesos que se cuelan en el mismo núcleo. En este tutorial medirás la latencia de planificación de Ubuntu 24.04 con cyclictest, aplicarás un perfil de tuned, reservarás núcleos de CPU solo para tu aplicación, sacarás de ellos las interrupciones y ejecutarás la aplicación con prioridad de tiempo real, comprobando la mejora después de cada cambio.
Requisitos previos
Para seguir esta guía necesitas:
- Un servidor con Ubuntu 24.04 LTS y un usuario no root con privilegios
sudo. - Al menos 4 CPU, idealmente 8 o más: vas a reservar varias para la aplicación y dejar el resto para el sistema.
- Acceso a la consola del servidor por si un parámetro de arranque impide el acceso por SSH.
- Una aplicación que quieras optimizar. En los ejemplos se llama
/opt/miapp/bin/miapp; sustitúyela por la tuya.
NotaEn un VPS los núcleos virtuales comparten hardware con otras máquinas y el hipervisor puede pausar la CPU virtual, así que los ajustes de esta guía reducen el jitter del sistema invitado pero no eliminan el del host. Para latencias estables por debajo de las decenas de microsegundos usa un servidor dedicado (bare metal) y ajusta también la BIOS: perfil de rendimiento, estados C profundos desactivados.
Los ejemplos suponen 8 CPU (0 a 7), reservando las CPU 4 a 7 para la aplicación. Adapta los rangos a tu hardware.
Paso 1: Instalar las herramientas de medición
Instala rt-tests (que incluye cyclictest), tuned para aplicar perfiles de sistema y numactl para ver la topología de memoria:
sudo apt update
sudo apt install rt-tests tuned numactl
Revisa la topología de la CPU. Te interesa saber qué CPU lógicas son hilos del mismo núcleo físico (Hyper-Threading), porque conviene reservar núcleos completos:
lscpu -e=CPU,CORE,SOCKET,NODE
CPU CORE SOCKET NODE
0 0 0 0
1 1 0 0
2 2 0 0
3 3 0 0
4 0 0 0
5 1 0 0
6 2 0 0
7 3 0 0
En este ejemplo las CPU 0 y 4 son el mismo núcleo físico. Si reservas la 4 pero dejas la 0 para el sistema, la carga del sistema seguirá afectando a tu aplicación a través del núcleo compartido. En ese caso, reserva pares completos (por ejemplo 2, 3, 6 y 7) o desactiva Hyper-Threading en la BIOS.
Paso 2: Medir la latencia de partida
Antes de cambiar nada, mide cuánto tarda el sistema en despertar un hilo de tiempo real. cyclictest crea un hilo por CPU, le pide dormir 200 microsegundos una y otra vez y registra cuánto se retrasa cada despertar:
sudo cyclictest -m -S -p 80 -i 200 -D 5m -q
Las opciones hacen lo siguiente: -m bloquea la memoria en RAM, -S lanza un hilo fijado a cada CPU, -p 80 usa prioridad de tiempo real 80, -i 200 fija el intervalo en microsegundos, -D 5m hace que la prueba dure cinco minutos y -q muestra solo el resumen final. Genera una carga parecida a la real mientras se ejecuta, porque en reposo casi cualquier sistema da buenos números.
# /dev/cpu_dma_latency set to 0us
T: 0 ( 2310) P:80 I:200 C:1499874 Min: 3 Act: 6 Avg: 5 Max: 412
T: 1 ( 2311) P:80 I:200 C:1499851 Min: 3 Act: 5 Avg: 5 Max: 389
...
T: 7 ( 2317) P:80 I:200 C:1499803 Min: 3 Act: 7 Avg: 6 Max: 531
Los valores están en microsegundos. Fíjate sobre todo en Max: es la peor latencia observada y la que quieres reducir. Guarda esta salida para compararla al final.
Paso 3: Aplicar el perfil network-latency de tuned
tuned agrupa en perfiles los ajustes típicos de rendimiento. El perfil network-latency incluye latency-performance (gobernador de CPU en performance e impide estados de reposo profundos mediante /dev/cpu_dma_latency) y añade ajustes de red y memoria para baja latencia, como el sondeo activo de sockets y la desactivación del balanceo automático NUMA.
Activa el servicio y el perfil:
sudo systemctl enable --now tuned
sudo tuned-adm profile network-latency
Comprueba que el perfil está activo y aplicado correctamente:
tuned-adm active
sudo tuned-adm verify
Current active profile: network-latency
Verification succeeded, current system settings match the preset profile.
Comprueba algunos de los valores que ha cambiado:
sysctl kernel.numa_balancing net.core.busy_poll net.core.busy_read
kernel.numa_balancing = 0
net.core.busy_poll = 50
net.core.busy_read = 50
En un VPS es normal que tuned no pueda cambiar el gobernador de CPU, porque la máquina virtual no controla la frecuencia real del procesador.
Paso 4: Aislar núcleos de CPU para la aplicación
Por defecto el planificador reparte cualquier proceso en cualquier CPU, y cada CPU recibe un tick periódico del temporizador y trabajo del subsistema RCU. Tres parámetros de arranque del kernel eliminan esas fuentes de ruido en las CPU reservadas:
| Parámetro | Efecto |
|---|---|
isolcpus=managed_irq,domain,4-7 | El planificador no coloca procesos en las CPU 4-7 salvo que se fijen de forma explícita, y las interrupciones gestionadas por el kernel las evitan |
nohz_full=4-7 | Detiene el tick del temporizador en esas CPU cuando solo ejecutan una tarea |
rcu_nocbs=4-7 | Mueve los callbacks de RCU a las CPU del sistema |
Además, el kernel genérico de Ubuntu 24.04 admite preempt=full, que hace el kernel totalmente expropiable y reduce el tiempo que una tarea de alta prioridad espera a que el kernel termine lo que estaba haciendo.
Edita la configuración de GRUB:
sudo nano /etc/default/grub
Añade los parámetros a GRUB_CMDLINE_LINUX_DEFAULT, conservando los que ya hubiera:
GRUB_CMDLINE_LINUX_DEFAULT="isolcpus=managed_irq,domain,4-7 nohz_full=4-7 rcu_nocbs=4-7 preempt=full"
Regenera la configuración y reinicia:
sudo update-grub
sudo reboot
Tras el reinicio, comprueba que el kernel ha aplicado el aislamiento:
cat /sys/devices/system/cpu/isolated /sys/devices/system/cpu/nohz_full
4-7
4-7
Y que el modo de expropiación es full:
sudo dmesg | grep -i 'dynamic preempt'
[ 0.051234] Dynamic Preempt: full
ConsejoSi necesitas garantías de tiempo real más estrictas, Ubuntu 24.04 ofrece un kernel PREEMPT_RT a través de Ubuntu Pro (gratuito para uso personal en hasta cinco máquinas). Se instala con
sudo pro attachysudo pro enable realtime-kernel. Repite la medición del paso 2 para comprobar si compensa en tu caso.
Paso 5: Sacar las interrupciones de los núcleos aislados
Las interrupciones de la tarjeta de red y del disco interrumpen lo que esté ejecutando la CPU que las atiende. irqbalance reparte las interrupciones entre CPU; en versiones recientes ya evita las CPU de isolcpus y nohz_full, pero conviene prohibirlas de forma explícita.
Edita su configuración:
sudo nano /etc/default/irqbalance
Añade esta línea:
IRQBALANCE_BANNED_CPULIST=4-7
Reinicia el servicio:
sudo systemctl restart irqbalance
Comprueba a qué CPU va cada interrupción de la red. Primero localiza sus números de IRQ en /proc/interrupts (en un VPS suelen llamarse virtio0-input.0 o similar):
grep -E 'virtio|eth|ens|enp|mlx' /proc/interrupts | awk '{print $1, $NF}'
24: virtio0-config
25: virtio0-input.0
26: virtio0-output.0
Consulta la afinidad efectiva de una de ellas:
cat /proc/irq/25/effective_affinity_list
1
Ninguna interrupción de dispositivo debería apuntar a las CPU 4 a 7. En la tabla de /proc/interrupts, los contadores de esas columnas deberían quedarse prácticamente quietos salvo en líneas como LOC o RES, que son internas del kernel.
Paso 6: Ejecutar la aplicación en los núcleos reservados con prioridad de tiempo real
Con las CPU aisladas, solo se ejecuta en ellas lo que fijes expresamente. La forma más fiable de hacerlo en producción es un servicio de systemd, que aplica afinidad, política de planificación y límites cada vez que arranca la aplicación.
Crea la unidad:
sudo nano /etc/systemd/system/miapp.service
Añade este contenido, ajustando la ruta y el usuario:
[Unit]
Description=Aplicación de baja latencia
After=network-online.target
Wants=network-online.target
[Service]
User=miapp
ExecStart=/opt/miapp/bin/miapp
CPUAffinity=4-7
CPUSchedulingPolicy=fifo
CPUSchedulingPriority=80
LimitMEMLOCK=infinity
Restart=on-failure
[Install]
WantedBy=multi-user.target
CPUSchedulingPolicy=fifo usa SCHED_FIFO: el hilo no cede la CPU hasta que se bloquea o aparece otro de mayor prioridad. LimitMEMLOCK=infinity permite a la aplicación llamar a mlockall() para que su memoria no se pagine a disco, lo que evitaría fallos de página en mitad de una operación.
Carga la unidad y arranca el servicio:
sudo systemctl daemon-reload
sudo systemctl enable --now miapp
Comprueba en qué CPU se ejecutan los hilos y con qué política:
ps -L -o tid,psr,cls,rtprio,comm -p "$(systemctl show -p MainPID --value miapp)"
TID PSR CLS RTPRIO COMMAND
1843 4 FF 80 miapp
1851 5 FF 80 miapp
PSR es la CPU donde se está ejecutando el hilo y FF indica SCHED_FIFO. Si tu aplicación tiene varios hilos con funciones distintas, lo ideal es que ella misma fije cada hilo crítico a una CPU concreta (con pthread_setaffinity_np()) en lugar de dejar que el planificador los mueva entre la 4 y la 7.
AdvertenciaNo desactives el límite de tiempo real del kernel (
kernel.sched_rt_runtime_us). Por defecto reserva un 5 % de cada segundo a tareas normales, lo que evita que un hiloSCHED_FIFOen bucle deje colgado el sistema.
Para pruebas rápidas sin crear un servicio, puedes lanzar un proceso con la misma configuración usando taskset y chrt:
sudo taskset -c 4-7 chrt -f 80 /opt/miapp/bin/miapp
Paso 7: Reducir la latencia de la tarjeta de red (opcional)
Muchas tarjetas agrupan varias interrupciones de recepción para ahorrar CPU (interrupt coalescing), lo que añade decenas de microsegundos a cada paquete. Consulta la configuración actual, sustituyendo eth0 por el nombre de tu interfaz (ip -br link lo muestra):
sudo ethtool -c eth0
Desactiva el ajuste adaptativo y la espera en recepción:
sudo ethtool -C eth0 adaptive-rx off rx-usecs 0
Si el comando responde Operation not supported, el driver no permite cambiar este valor, algo habitual en tarjetas virtuales. Si funciona, el cambio se pierde al reiniciar; para hacerlo permanente, crea una unidad de systemd:
sudo nano /etc/systemd/system/nic-coalescing.service
Añade este contenido:
[Unit]
Description=Desactivar coalescing de interrupciones en eth0
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/sbin/ethtool -C eth0 adaptive-rx off rx-usecs 0
[Install]
WantedBy=multi-user.target
Carga la unidad y actívala:
sudo systemctl daemon-reload
sudo systemctl enable --now nic-coalescing
En la aplicación, desactiva el algoritmo de Nagle en los sockets TCP con la opción TCP_NODELAY, que evita que el kernel retenga mensajes pequeños esperando a juntar más datos.
Paso 8: Medir de nuevo en los núcleos aislados
Detén tu aplicación para que no compita con la prueba y lanza cyclictest solo en las CPU reservadas, con un hilo por CPU:
sudo systemctl stop miapp
sudo cyclictest -m -p 80 -i 200 -D 5m -q -t 4 -a 4-7
# /dev/cpu_dma_latency set to 0us
T: 0 ( 2604) P:80 I:200 C:1499990 Min: 2 Act: 3 Avg: 3 Max: 14
T: 1 ( 2605) P:80 I:200 C:1499989 Min: 2 Act: 3 Avg: 3 Max: 12
T: 2 ( 2606) P:80 I:200 C:1499987 Min: 2 Act: 3 Avg: 3 Max: 15
T: 3 ( 2607) P:80 I:200 C:1499986 Min: 2 Act: 2 Avg: 3 Max: 11
Compara el Max con el del paso 2, con la misma carga en el resto del sistema. En un servidor dedicado bien ajustado es normal pasar de cientos de microsegundos a valores de un solo dígito o decenas bajas. En un VPS la mejora suele ser menor y los picos que quedan vienen del host.
Cuando termines, vuelve a arrancar la aplicación:
sudo systemctl start miapp
Solución de problemas
Maxsigue alto en un VPS. Comprueba el tiempo robado por el hipervisor convmstat 1(columnast). Si no es cero de forma sostenida, la latencia viene del host y solo la resuelve un servidor dedicado.- Picos periódicos cada pocos segundos. Busca procesos que se ejecuten en las CPU aisladas con
ps -eLo psr,comm | awk '$1>=4 && $1<=7'. Además de tu aplicación, solo deberían aparecer hilos del kernel por CPU comoksoftirqd/4,migration/4ocpuhp/4. - El sistema va lento tras el aislamiento. Todo lo demás se ejecuta ahora en las CPU 0 a 3. Si se quedan cortas, reserva menos CPU para la aplicación.
tuned-adm verifyfalla. Revisa/var/log/tuned/tuned.log: suele indicar qué ajuste no se ha podido aplicar, normalmente el gobernador de CPU en máquinas virtuales.- La aplicación no arranca con
SCHED_FIFO. Revisajournalctl -u miapp. Si el error es de permisos al fijar la política, asegúrate de que la usas desde la unidad de systemd y no desde el propio programa sin privilegios.
Conclusión
Has medido la latencia de planificación de Ubuntu 24.04, has aplicado el perfil network-latency, has reservado núcleos para la aplicación sin tick ni interrupciones y la ejecutas con prioridad de tiempo real, comprobando la mejora con cyclictest. Como siguientes pasos, reserva huge pages para reducir fallos de TLB, fija los hilos críticos de la aplicación a CPU concretas desde el código y, si tu carga es de red, valora el bypass del kernel con DPDK.
