...

Afinidad de IRQ en Linux en sistemas multiprocesador: guía práctica para un ajuste óptimo de la red

Mostraré de forma práctica cómo la afinidad de IRQ en sistemas multiprocesador asigna de forma específica las interrupciones de red a los núcleos de la CPU, reduce la latencia y aumenta el rendimiento. Mediante pasos claros, ejemplos y una tabla, explico cómo selecciono las máscaras hexadecimales, tengo en cuenta la arquitectura NUMA y asigno los procesos de forma adecuada.

Puntos centrales

  • Afinidad IRQ dirige las interrupciones de hardware de forma específica a las CPU y reduce la sobrecarga.
  • Afinidad de la CPU Fijar los servicios a los mismos núcleos permite mantener las cachés de forma local.
  • NUMA Ten en cuenta que el adaptador y los núcleos deben pertenecer al mismo nodo.
  • irqbalance Valorar: distribuir automáticamente o ajustar manualmente con precisión.
  • Monitoreo y el ajuste iterativo garantiza unas latencias estables.

Entender la afinidad de IRQ: conceptos básicos y efectos

En los servidores Linux, los paquetes entrantes, las operaciones de E/S de disco y los temporizadores IRQs que el núcleo distribuye entre los núcleos de la CPU. Yo determino, a través de los archivos /proc/irq//smp_affinity y .../smp_affinity_list, qué CPU pueden acceder a una fuente. Una máscara estándar amplia parece flexible a primera vista, pero, en condiciones de carga, genera fallos de caché, costosos cambios de contexto y SoftIRQ dispersas a lo largo de muchos CPUs. Asigno las colas críticas de cada tarjeta de red a núcleos definidos, alivio la carga en los puntos de congestión y mantengo las rutas de datos cortas. Esta gestión mejora notablemente la estabilidad en cuanto hay muchos flujos activos al mismo tiempo.

Ajustar la afinidad de IRQ y CPU: mantener los datos de forma local

He conectado correctamente el IRQ-Asignación de las colas NIC a la CPU a la que pertenecen los trabajadores correspondientes. Para ello, asigno los hilos del servidor web o del proxy mediante hoja de ruta o Afinidad CP= en systemd a aquellos núcleos que también gestionan las interrupciones RX/TX. De este modo, las líneas de caché permanecen locales y reduzco al mínimo la comunicación entre CPU, lo que mejora el Latencia se alisa. Precisamente los backends de API, los servicios en tiempo real y las pilas virtualizadas se benefician de esta coherencia. Pruebo la integración bajo carga de producción hasta que los flujos, las SoftIRQ y el espacio de usuario encajan perfectamente entre sí.

Automatización frente a ajuste fino: cómo interpretar correctamente «irqbalance»

El servicio irqbalance Distribuye automáticamente las interrupciones entre los núcleos disponibles, lo que funciona bien en servidores de uso general. Sin embargo, en configuraciones de red con alta carga, esta distribución reduce la localidad de la caché y dificulta la asignación selectiva. Limito el uso de irqbalance o lo desactivo de forma selectiva cuando determinadas colas necesitan núcleos fijos. Para comprender los conceptos básicos y encontrar los perfiles adecuados, me resulta útil esta guía sobre Configurar irqbalance. En definitiva, el sistema automático se encarga de las interrupciones no críticas, mientras que yo asigno manualmente las IRQ sensibles.

Paso: Mostrar las IRQ relevantes

Empezaré echando un vistazo a /proc/interrupciones y filtro por el nombre del dispositivo, como ens192, eno1 o eth0. Los adaptadores modernos crean varias colas de recepción (RX) y transmisión (TX), por lo que busco un grupo de números de IRQ asignados a la misma tarjeta de red. Presto atención a los valores de los contadores para detectar rápidamente los puntos críticos y priorizar primero las colas más saturadas. Reviso esta vista periódicamente durante las pruebas de carga, para garantizar que la asignación sea sostenible a largo plazo. Además, compruebo las denominaciones de los controladores, ya que proporcionan indicaciones sobre las capacidades RSS y la descarga de carga.

# Ver todas las interrupciones
cat /proc/interrupts

# Mostrar solo las líneas relevantes para la tarjeta de red (ejemplo: ens192)
grep -i ens192 /proc/interrupts

Aprovechar al máximo la topología NUMA

En servidores con varios nodos, prefiero reasignar las IRQ a los núcleos de aquellos NUMA-Nodo al que está conectado físicamente el NIC. Lo compruebo con lscpu y numactl --hardware y selecciono los conjuntos de CPU adecuados para la posterior creación de máscaras. Los procesos que utilizan estas rutas de red también los asocio al mismo nodo y, mediante políticas de memoria, me aseguro de que se almacenen localmente Memoria-Asignaciones. Así evito costosos accesos remotos a través de los enlaces QPI/UPI. Esta disciplina aporta ventajas rápidamente cuantificables en las pruebas de latencia.

Cómo seleccionar máscaras de bits de forma segura: la lógica hexadecimal de un vistazo

El expediente smp_affinity admite máscaras de bits hexadecimales que se corresponden directamente con los ID de núcleo y que también cubren sistemas de gran tamaño. Suelo empezar con patrones sencillos: la CPU0 es 0x1, la CPU1 es 0x2, la CPU2 es 0x4, la CPU3 es 0x8, etc., mientras que 0xF abarca los núcleos 0 a 3. En equipos con muchos núcleos, escribo varios bloques de 32 bits, separados por comas, para que el Máscara muestra claramente todos los ID. Este resumen me ayuda a realizar asignaciones sin errores y a evitar traslados involuntarios. Utilizo a menudo la siguiente tabla como ayuda memoria.

Número de CPU Bit (binario) Máscara de Hex Nota
0 …0001 0x1 CPU0 A menudo hay que reducir la carga y utilizarlos con moderación.
1 …0010 0x2 IRQ en CPU1 fijar.
2 …0100 0x4 Conectar el IRQ a la CPU2.
3 …1000 0x8 Conectar el IRQ a la CPU3.
0–3 …1111 0xF Si se activan los cuatro núcleos, la latencia suele aumentar ligeramente.
0–7 11111111 0xFF Distribución amplia, lo que afecta a la localidad de la caché.

Configurar la afinidad de IRQ: cómo asignar colas a los núcleos

Una vez identificadas las IRQ de cola de la tarjeta de red, las distribuyo entre Núcleos como 1, 2 y 3, para escalar correctamente el procesamiento paralelo. De este modo, cada cola de recepción/transmisión permanece en su núcleo, lo que evita la interferencia cruzada y mantiene la coherencia en el funcionamiento de las SoftIRQ. Durante el ajuste fino, comparo el rendimiento y la latencia hasta que la distribución funciona de forma fiable. Para profundizar más en el tema, me resulta útil un breve Guía práctica con variantes según el adaptador. Ejecuto los comandos deliberadamente durante las ventanas de mantenimiento y los protejo mediante un script de arranque.

Ejemplo de #: asignar tres IRQ de cola a las CPU 1 a 3
echo 2 > /proc/irq/181/smp_affinity   # CPU1 (0x2)
echo 4 > /proc/irq/182/smp_affinity   # CPU2 (0x4)
echo 8 > /proc/irq/183/smp_affinity   # CPU3 (0x8)

Fijación de procesos: asignar servicios a los mismos núcleos

Asigno los trabajadores de aplicación a aquellos CPUs, que gestionan las IRQ correspondientes, para que las rutas de datos sean cortas. En systemd utilizo CPUAffinity=1 2 3 o empieza una sola vez con taskset -c 1-3. En el caso de los servidores con múltiples trabajadores, asigno un número fijo de núcleos a cada grupo de trabajadores para evitar que se produzcan conflictos de competencia. Esta configuración permite mantener unos valores constantemente más bajos Latencias, porque las cachés de la CPU se mantienen bien llenadas. Tras realizar cambios, compruebo los hilos, los sockets y las SoftIRQ con htop, ss y perf.

Optimización de la red: cómo combinar RSS, RPS/RFS y los parámetros del núcleo

Muchas tarjetas de red distribuyen paquetes a través de RSS sobre las colas, que luego vinculo a los núcleos mediante la afinidad de IRQ. Resumo los detalles y los principios de funcionamiento en Escalado del lado receptor de forma compacta. Además, controlo RPS/RFS de manera que las SoftIRQ no interfieran con la lógica de fijación de pines. Al mismo tiempo, ajusto los búferes a través de net.core.rmem_max y net.core.wmem_max y comprueba las opciones TCP como tcp_timestamps. Estos componentes contribuyen a un mismo objetivo: baja latencia con alta Velocidad de bits.

Interpretar correctamente MSI-X y la estructura de colas

Las tarjetas de red modernas utilizan MSI-X y crean IRQ propias para cada cola de recepción (RX) y transmisión (TX). Primero compruebo cuántas colas y canales tiene activados actualmente el controlador y lo adapto al presupuesto de núcleos del nodo NUMA correspondiente. De este modo, evito que se concentren demasiadas colas en muy pocos núcleos o, por el contrario, que quede capacidad sin aprovechar.

Comprobar y ajustar el número de colas y canales de #
ethtool -l ens192 # límites actuales (RX/TX/combinados)
ethtool -L ens192 combined 4  #, por ejemplo, activar 4 colas

#: revisar la indirección RSS y la configuración del hash
ethtool -x ens192 #: mostrar la tabla de indirección y la clave de hash

Muchos controladores asignan nombres descriptivos a las IRQ (por ejemplo,. ens192-TxRx-0). Mantengo la tabla de indirección coherente con la asignación del núcleo, para que los flujos se dirijan de forma estable a „su“ cola. Si la distribución del hardware difiere, se producen desplazamientos innecesarios de las SoftIRQ.

Utilizar correctamente RPS/RFS y XPS

RPS/RFS puede distribuir los paquetes a nivel de software entre los núcleos, lo cual es útil para tarjetas de red que no tienen muchas colas, pero resulta contraproducente si ya he establecido una vinculación precisa mediante RSS y afinidad de IRQ. Por lo tanto, tomo una decisión deliberada: o bien una vinculación rígida mediante RSS + afinidad de IRQ y RPS desactivado, o unas pocas colas de hardware y RPS de forma específica a. Además, configuro XPS para la dirección TX, de modo que los paquetes salientes se envíen desde los núcleos „correctos“.

IF=ens192

Desactivar completamente el RPS de # (si la asignación de IRQ a través de RSS es correcta)
for q in /sys/class/net/$IF/queues/rx-*/rps_cpus; do echo 0 > "$q"; done
echo 0 > /proc/sys/net/core/rps_sock_flow_entries

# Alternativa: activar RPS de forma selectiva (ejemplo: CPU 1-3)
for q in /sys/class/net/$IF/queues/rx-*/rps_cpus; do echo 0-0,0-0,0-0,0-0 > /dev/null; done
# Mejor: utilizar una sintaxis similar a la de smp_affinity_list:
for q in /sys/class/net/$IF/queues/rx-*/rps_cpus; do echo 1-3 > "$q"; done
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
echo 32768 > /proc/sys/net/ipv4/tcp_rfs_sock_flow_entries

Establecer # XPS de acuerdo con las CPU de trabajo seleccionadas (rutas TX)
for q in /sys/class/net/$IF/queues/tx-*; do echo 1-3 > "$q"/xps_cpus; done

Lo importante es la coherencia: el núcleo RX-IRQ, la carga de ksoftirqd y el hilo de trabajo deben estar en el mismo núcleo (o par de núcleos). De este modo, desaparecen muchos efectos de rebote entre núcleos.

Tener en cuenta SMT/Hyper-Threading y los pares de núcleos

En sistemas con SMT, a menudo resulta conveniente reservar un núcleo físico para una RX-IRQ y asignar el trabajador correspondiente al Hilo relacionado asignarlos —o separarlos deliberadamente— cuando la carga de trabajo requiera un gran esfuerzo de cálculo. Determino los pares de subprocesos a partir de la topología y, a continuación, tomo una decisión clara en lugar de optar por una distribución aleatoria.

Identificar pares de hermanos #
for c in /sys/devices/system/cpu/cpu*/topology/thread_siblings_list; do
  echo "$(basename "$(dirname "$c")") : $(cat "$c")"
done

Si distribuyo el RX-IRQ y los trabajadores del espacio de usuario en el mismo núcleo físico (diferentes subprocesos SMT), se consigue una buena localidad L1/L2, pero pueden producirse cuellos de botella en cargas de trabajo limitadas por la CPU. Como alternativa, distribuyo el IRQ en el núcleo X y el worker en el núcleo Y del mismo nodo NUMA para conseguir un paralelismo real. Realizo pruebas A/B con ambas variantes y elijo la que ofrezca una latencia más estable.

Utilizar smp_affinity_list, effective_affinity y los valores predeterminados

Además de las máscaras hexadecimales, me gusta escribir en smp_affinity_list, ya que de este modo se abordan ámbitos como 1-3,6,8-9 se puede colocar cómodamente. Para comprobarlo, reviso afinidad_efectiva respectivamente lista_de_afinidades_efectivas, ya que el núcleo o los controladores pueden excluir determinadas CPU (por ejemplo, núcleos fuera de línea o „interrupciones gestionadas“).

# Asignación legible para el usuario
echo 1-3 > /proc/irq/181/smp_affinity_list

# Verificar la afinidad efectiva
cat /proc/irq/181/effective_affinity_list

Para evitar que las IRQ nuevas o las que se han añadido tras una recarga de controladores se distribuyan de nuevo de forma dispersa, configuro, si es necesario, /proc/irq/default_smp_affinity a un valor base razonable (por ejemplo, todos los núcleos del nodo NUMA relevante, pero sin la CPU0). A continuación, anulo de forma selectiva determinadas IRQ críticas.

Garantizar la persistencia tras los reinicios y las recargas

La configuración de Affinity es volátil. Las guardo mediante una unidad «oneshot» de systemd, que se ejecuta tras el objetivo de inicialización de la red, o mediante un pequeño script que determina y asigna dinámicamente las listas de IRQ. De este modo, las asignaciones se conservan incluso tras actualizaciones del kernel y reinicios de enlaces.

# /usr/local/sbin/net-irq-pin.sh (ejemplo)
#!/bin/bash
set -euo pipefail
IF=${1:-ens192}
CPUS="1-3"   CPUs de destino (seleccionar las compatibles con NUMA)
for irq in $(grep -i "$IF" /proc/interrupts | awk '{print $1}' | tr -d ':'); do
  echo "$CPUS" > /proc/irq/$irq/smp_affinity_list || true
done

Unidad de systemd # (esbozada)
# /etc/systemd/system/net-irq-pin.service
[Unit]
Description=Fijar las IRQ de la tarjeta de red
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/net-irq-pin.sh ens192

[Install]
WantedBy=multi-user.target

Importante: Vuelvo a ejecutar el script cuando se recarga el controlador o se modifica el número de colas, ya que en esos casos cambian los números de IRQ.

Virtualización y contenedores: pensar en el host y el invitado de forma conjunta

En entornos KVM, monto en el Anfitrión las IRQ físicas de las tarjetas de red a los núcleos del nodo NUMA correspondiente. Al mismo tiempo, conecto en paralelo vhost-net‑Threads y el proceso QEMU (o vCPU individuales) también deben estar allí, para que las rutas de datos por parte del host sean cortas. En el Invitado Configuraré la afinidad de IRQ de las vNIC para que se asigne a aquellas vCPU que haya emparejado, en el lado del host, con núcleos físicos. Las cargas de trabajo de contenedores (cgroups/cpuset) se benefician cuando las CPU permitidas de los contenedores se solapan con los núcleos RX/TX del host; de lo contrario, se producen accesos remotos que se podrían evitar.

Profundizar en el análisis: SoftIRQ, NAPI y detección de atascos

Además de /proc/interrupciones miro en /proc/softirqs, para ver si hay mucho trabajo en el contexto de ksoftirqd en lugar de realizarse directamente en el controlador de IRQ, lo que indica una carga elevada y prolongada. Con napi_defer_hard_irqs (Depende del kernel) y mediante asignaciones limpias de colas, regulo la intensidad con la que NAPI agrupa los datos. ethtool -S Me proporciona estadísticas por cola sobre pérdidas, estados de ocupación y tasas de paquetes; así puedo detectar colas desequilibradas y ajustar la afinidad o la indirección RSS en consecuencia.

# Resumen rápido de la distribución de SoftIRQ
cat /proc/softirqs | egrep 'NET_RX|NET_TX'

# Consultar las estadísticas del controlador y de la cola
ethtool -S ens192 | egrep -i 'rx|tx|drop|busy'

Práctica: asignación de 4 colas a un nodo

Una configuración típica que utilizo a menudo: tarjeta de red (NIC) en el nodo NUMA 0 con 4 colas RSS. Evito la CPU0 y asigno las colas a las CPU 1-4. Los trabajadores web o proxy correspondientes también los asigno a las 1-4; asigno el XPS de forma idéntica y el RPS permanece desactivado. De este modo, consigo rutas cortas y consistentes en ambas direcciones.

IF=ens192
QUEUES=(181 182 183 184)   IRQ de ejemplo de # (determinar previamente)
CPUS="1-4"

Establecer la afinidad de IRQ y XPS para #
for i in ${!QUEUES[@]}; do
  echo "$CPUS" > /proc/irq/${QUEUES[$i]}/smp_affinity_list
done
for q in /sys/class/net/$IF/queues/tx-*; do echo "$CPUS" > "$q"/xps_cpus; done

# Fijar el proceso de trabajo (systemd o taskset)
# systemd: CPUAffinity=1 2 3 4
# Una sola vez: taskset -c 1-4

Si la carga sigue aumentando, aumentaré el número de colas (ethtool -L) hasta el número adecuado de núcleos del nodo y distribúyelos de forma fija siguiendo un patrón reconocible (por ejemplo, ID de cola → ID de núcleo), para que los flujos no „emigren“ con el paso del tiempo.

Buenas prácticas para hosts multinúcleo

Alivio la tensión CPU0, ya que allí suelen ejecutarse temporizadores y servicios internos del núcleo que pueden causar interferencias bajo carga. Por eso, prefiero asignar las IRQ críticas a otros núcleos y dejar que la CPU0 se encargue solo de unas pocas fuentes no críticas. En los sistemas NUMA, soy coherente y mantengo los adaptadores, las IRQ, los procesos y los accesos a la memoria en el mismo Nodo. En entornos con gran carga de trabajo, separo los núcleos de E/S de los núcleos de aplicaciones y los aíslo cuando es necesario. Acompaño todos los cambios con mediciones continuas y ajusto las asignaciones de forma iterativa.

Actuar de forma cuantificable: análisis, guiones y plan de contingencia

Antes de realizar cualquier cambio, documento el estado actual mediante mpstat, htop, /proc/interrupciones y mediciones de latencia con iperf3. Configuro scripts que aplican automáticamente los ajustes de afinidad tras un reinicio o una recarga de controladores. Para los rollbacks, tengo preparadas máscaras neutras, de modo que pueda revertir los cambios inmediatamente en caso de que se produzca algún fallo. En el entorno de prueba, pruebo perfiles de carga que se acerquen lo más posible a mi entorno de producción y repito el Medición después de cada ajuste. Solo entonces activo el perfil de forma permanente en el servidor de destino.

Evite limpiamente los escollos habituales

Las máscaras demasiado anchas reparten el trabajo entre demasiadas personas CPUs y ralentizan las cachés, mientras que las máscaras demasiado restrictivas obstruyen las colas. Las particularidades de NUMA que se pasan por alto generan accesos a memoria remota que hacen que los tiempos de respuesta varíen. Un «pinning» estricto a veces entra en conflicto con la configuración de RPS/RFS, por lo que compruebo explícitamente el reparto de carga de SoftIRQ. Tras actualizaciones del kernel o cambios de controladores, vuelvo a validar todos los números de IRQ, ya que las asignaciones pueden cambiar. Con pasos cautelosos y una clara Documental sigo siendo capaz de actuar.

Brevemente resumido

La asignación selectiva de IRQ vincula las interrupciones de red a unas pocas adecuadas Núcleos, reduce la sobrecarga y estabiliza los tiempos de respuesta. Para ello, ajusto la afinidad de IRQ y CPU, tengo en cuenta la NUMA y compruebo el efecto mediante series de mediciones. Cuando basta con el modo automático, dejo que actúe irqbalance; las colas críticas las asigno a núcleos fijos. Con RSS, RPS/RFS y parámetros del kernel adaptados, el ajuste despliega todo su Efecto. Quien aplique estos pasos con rigor conseguirá un rendimiento de red notablemente superior en servidores Linux multinúcleo.

Artículos de actualidad