...

Afinidade de IRQ no Linux em sistemas multiprocessadores: guia prático para um ajuste ideal da rede

Vou demonstrar, de forma prática, como a afinidade de IRQ, em sistemas multiprocessadores, atribui de forma específica as interrupções de rede a núcleos de CPU, reduz a latência e aumenta o débito. Através de passos claros, exemplos e uma tabela, explico como seleciono máscaras hexadecimais, tenho em conta o NUMA e atribuo os processos de forma adequada.

Pontos centrais

  • Afinidade IRQ direciona as interrupções de hardware de forma específica para as CPUs e reduz a sobrecarga.
  • Afinidade da CPU Fixar os serviços nos mesmos núcleos mantém as caches locais.
  • NUMA Tenha em atenção que os adaptadores e os núcleos utilizam o mesmo nó.
  • equilíbrio do Iraque avaliar: distribuir automaticamente ou ajustar manualmente com precisão.
  • Monitorização e o ajuste iterativo garantem latências estáveis.

Compreender a afinidade de IRQ: noções básicas e efeitos

Em máquinas Linux, os pacotes recebidos, as operações de E/S de disco e os temporizadores IRQs que o kernel distribui pelos núcleos da CPU. Eu defino, através dos ficheiros /proc/irq//smp_affinity e .../smp_affinity_list, quais as CPUs que podem servir uma fonte. Uma máscara padrão ampla parece, à primeira vista, flexível, mas, em caso de falhas de cache, gera mudanças de contexto dispendiosas e SoftIRQs dispersas por muitos CPUs. Associo filas críticas de placas de rede individuais a núcleos definidos, alivio a carga nos pontos de congestionamento e mantenho os percursos de dados curtos. Este direcionamento melhora sensivelmente a estabilidade assim que há muitos fluxos ativos em simultâneo.

Ajustar a afinidade de IRQ e CPU: manter os dados localmente

Consegui ligar o IRQ-Atribuição das filas NIC com a afiliação à CPU dos trabalhadores relevantes. Para tal, fixo os threads do servidor Web ou do proxy através de conjunto de tarefas ou CPUAffinity= no systemd para os núcleos que também processam as interrupções RX/TX. Desta forma, as linhas de cache permanecem locais e reduzo a comunicação entre CPUs ao mínimo, o que Latência unifica. São precisamente os back-ends API, os serviços em tempo real e as pilhas virtualizadas que beneficiam desta coerência. Testo a ligação sob carga de produção até que os fluxos, os SoftIRQs e o espaço do utilizador se encaixem perfeitamente.

Modo automático versus ajuste fino: compreender corretamente o irqbalance

O serviço equilíbrio do Iraque distribui automaticamente as interrupções pelos núcleos disponíveis, o que funciona bem em servidores de uso geral. Contudo, em configurações de rede com elevada carga, essa distribuição prejudica a localidade da cache e dificulta o «pinning» direcionado. Limito o irqbalance ou desativo-o seletivamente quando determinadas filas necessitam de núcleos fixos. Para uma compreensão básica e perfis adequados, este guia ajuda-me a Configurar o irqbalance. Em suma, o sistema automático gere as IRQs não críticas, enquanto eu atribuo manualmente as IRQs sensíveis.

Passo: Tornar visíveis as IRQs relevantes

Começo por dar uma vista de olhos em /proc/interrupções e filtrar pelo nome do dispositivo, como ens192, eno1 ou eth0. Os adaptadores modernos criam várias filas RX e TX, pelo que procuro um grupo de números de IRQ atribuídos à mesma placa de rede. Presto atenção aos valores dos contadores para identificar rapidamente pontos de congestionamento e priorizar a alocação das filas mais sobrecarregadas. Verifico regularmente esta perspetiva durante os testes de carga, para garantir que a alocação se mantém a longo prazo. Além disso, verifico as designações dos controladores, pois estas fornecem indicações sobre as capacidades RSS e de offloading.

# Ver todas as interrupções
cat /proc/interrupts

# Mostrar apenas as linhas relevantes para a NIC (exemplo: ens192)
grep -i ens192 /proc/interrupts

Utilizar a topologia NUMA de forma inteligente

Em hosts com vários nós, prefiro reatribuir as IRQs aos núcleos desses NUMA-Nó ao qual a placa de rede está fisicamente ligada. Vou verificar isso com lscpu e numactl --hardware e seleciono conjuntos de CPU adequados para a posterior criação de máscaras. Os processos que utilizam estes caminhos de rede também os ligo ao mesmo nó e, através de políticas de memória, garanto que Memória-Atribuições. É assim que evito acessos remotos dispendiosos através das ligações QPI/UPI. Esta disciplina traz vantagens rapidamente mensuráveis nos testes de latência.

Escolher máscaras de bits com segurança: a lógica hexadecimal num relance

O ficheiro smp_affinity aceita máscaras de bits hexadecimais que correspondem diretamente aos IDs dos núcleos e que também abrangem sistemas de grande dimensão. Costumo começar com padrões simples: a CPU0 é 0x1, a CPU1 é 0x2, a CPU2 é 0x4, a CPU3 é 0x8, etc., enquanto 0xF abrange os núcleos 0–3. Em máquinas com muitos núcleos, escrevo vários blocos de 32 bits, separados por vírgulas, para que o Máscara retrata corretamente todos os IDs. Esta síntese ajuda-me a efetuar atribuições sem erros e a evitar transferências involuntárias. Utilizo frequentemente a tabela seguinte como apoio à memória.

Número da CPU Bit (binário) Máscara Hex Nota
0 …0001 0x1 CPU0 descarregá-los frequentemente e utilizá-los com moderação.
1 …0010 0x2 IRQ em CPU1 fixar.
2 …0100 0x4 Ligar o IRQ à CPU2.
3 …1000 0x8 Ligar o IRQ à CPU3.
0–3 …1111 0xF Ao ativar os quatro núcleos, a latência costuma aumentar ligeiramente.
0–7 11111111 0xFF Distribuição ampla, a localidade da cache é prejudicada.

Definir a afinidade de IRQ: como associar filas aos núcleos

Depois de identificar as IRQs de fila da NIC, distribuo-as por canais dedicados Núcleos como 1, 2 e 3, para escalar corretamente o processamento paralelo. Desta forma, cada fila RX/TX permanece no seu núcleo, o que evita a interferência cruzada e mantém o funcionamento do SoftIRQ consistente. Durante o ajuste fino, comparo a taxa de transferência e a latência até que a distribuição funcione de forma fiável. Para uma introdução mais aprofundada, recorro a um breve Guia prático com variantes consoante o adaptador. Executo os comandos deliberadamente durante as janelas de manutenção e garanto a sua execução através de um script de arranque.

Exemplo #: associar três IRQs de fila às CPUs 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)

Fixação de processos: atribuir serviços aos mesmos núcleos

Estou a atribuir os workers de aplicação a esses CPUs, que processam os IRQs correspondentes, para que os percursos de dados se mantenham curtos. No systemd, utilizo CPUAffinity=1 2 3 ou inicie uma única vez com taskset -c 1-3. No caso de servidores com vários trabalhadores, atribuo conjuntos fixos de núcleos a cada grupo de trabalhadores, para evitar a concorrência. Esta configuração resulta em valores constantemente mais baixos Latências, porque as caches da CPU permanecem bem preenchidas. Após as alterações, verifico os threads, os sockets e os SoftIRQs com o htop, o ss e o perf.

Otimização da rede: combinar RSS, RPS/RFS e parâmetros do kernel

Muitas placas de rede distribuem pacotes através de RSS sobre filas, que depois associo aos núcleos através da afinidade de IRQ. Resumo os detalhes e os princípios de funcionamento em Receive Side Scaling de forma compacta. Além disso, controlo o RPS/RFS de modo a que os SoftIRQs não entrem em conflito com a lógica rígida de fixação de pinos. Paralelamente, ajusto os buffers através de net.core.rmem_max e net.core.wmem_max e verifica as opções TCP, tais como tcp_timestamps. Estes componentes contribuem para o mesmo objetivo: baixa latência com elevada Taxa de bits.

Interpretar corretamente o MSI-X e o Queue-Layout

As placas de rede modernas utilizam o MSI-X e criam IRQs próprias para cada fila de receção/transmissão. Primeiro, verifico quantas filas e canais o controlador tem atualmente ativados e adapto isso ao orçamento de núcleos do nó NUMA correspondente. Desta forma, evito que um número excessivo de filas se concentre num número insuficiente de núcleos ou, inversamente, que haja capacidade ociosa.

Verificar e ajustar o número de filas e canais do #
ethtool -l ens192 # limites atuais (RX/TX/combinado)
ethtool -L ens192 combined 4  #, por exemplo, ativar 4 filas

# Verificar a indireção RSS e a configuração do hash
ethtool -x ens192 # Mostrar a tabela de indireção e a chave de hash

Muitos controladores atribuem nomes descritivos às IRQs (por exemplo,. ens192-TxRx-0). Mantenho a tabela de indireção consistente com a atribuição do núcleo, para que os fluxos sejam encaminhados de forma estável para a „sua“ fila. Se a distribuição do hardware se desviar, ocorrem deslocamentos desnecessários dos SoftIRQs.

Utilizar o RPS/RFS e o XPS de forma coerente

O RPS/RFS pode distribuir os pacotes por meio de software pelos núcleos – o que é bom para placas de rede sem muitas filas, mas contraproducente se eu já tiver definido uma ligação exata através do RSS e da afinidade de IRQ. Por isso, decido conscientemente: ou uma ligação rígida através do RSS + afinidade de IRQ e RPS desligado, ou poucas filas de hardware e RPS de forma específica. Além disso, configuro o XPS para a direção TX, para que os pacotes de saída sejam enviados pelos núcleos „certos“.

IF=ens192

# Desativar completamente o RPS (com atribuição correta de IRQ via RSS)
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: ativar o RPS de forma seletiva (exemplo: CPUs 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
# Melhor: utilizar uma sintaxe semelhante à da 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

Definir o XPS do # de acordo com as CPUs de trabalho selecionadas (caminhos TX)
for q in /sys/class/net/$IF/queues/tx-*; do echo 1-3 > "$q"/xps_cpus; done

O importante é a coerência: o RX-IRQ-Core, a carga do ksoftirqd e o thread de trabalho devem estar no mesmo núcleo (ou par de núcleos). Desta forma, eliminam-se muitos efeitos de «cross-core bounce».

Ter em conta o SMT/Hyper-Threading e os pares de núcleos

Em sistemas com SMT, faz frequentemente sentido reservar um núcleo físico para um RX-IRQ e atribuir o worker correspondente ao Tópico irmão atribuir – ou separar deliberadamente, caso a carga de trabalho exija um grande esforço computacional. Identifico os pares de threads com base na topologia e, em seguida, tomo uma decisão clara, em vez de recorrer a uma distribuição aleatória.

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

Se eu dividir o RX-IRQ e o User-Space-Worker no mesmo núcleo físico (diferentes threads SMT), obtém-se uma boa localidade L1/L2, mas podem ocorrer gargalos em cargas de trabalho limitadas pela CPU. Em alternativa, distribuo o IRQ pelo núcleo X e o «worker» pelo núcleo Y do mesmo nó NUMA, para obter um paralelismo verdadeiro. Testo ambas as variantes num teste A/B e escolho a que proporcionar uma latência mais estável.

Utilizar smp_affinity_list, effective_affinity e os valores predefinidos

Para além das máscaras hexadecimais, gosto de escrever em smp_affinity_list, uma vez que isso abrange áreas como 1-3,6,8-9 é fácil de ajustar. Para verificar, eu inspeciono effective_affinity respectivamente lista_de_afinidades_efetivas, porque o kernel ou os controladores podem excluir certas CPUs (por exemplo, núcleos desativados ou „interruptos geridos“).

# Atribuição legível por humanos
echo 1-3 > /proc/irq/181/smp_affinity_list

# Verificar a afiniência efetiva
cat /proc/irq/181/effective_affinity_list

Para evitar que as IRQs novas ou adicionadas após uma atualização do controlador sejam novamente distribuídas de forma dispersa, defino, se necessário, /proc/irq/default_smp_affinity para um valor base adequado (por exemplo, todos os núcleos do nó NUMA relevante, mas excluindo a CPU0). Posteriormente, substituo de forma seletiva as IRQs críticas individuais.

Garantir a persistência após reinicializações e recargas

As definições de Affinity são voláteis. Guardo-as através de uma unidade «oneshot» do systemd, que é executada após o objetivo de inicialização da rede, ou através de um pequeno script que determina e mapeia dinamicamente as listas de IRQ. Desta forma, as atribuições são mantidas mesmo após atualizações do kernel e reinicializações de ligações.

# /usr/local/sbin/net-irq-pin.sh (Exemplo)
#!/bin/bash
set -euo pipefail
IF=${1:-ens192}
CPUS="1-3"   CPUs de destino # (selecionar de forma consistente com 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

Unidade systemd # (esboçada)
# /etc/systemd/system/net-irq-pin.service
[Unit]
Description=Fixar IRQs da NIC
After=network-online.target

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

[Install]
WantedBy=multi-user.target

Importante: Volto a executar o script se o controlador for recarregado ou se o número de filas for alterado, uma vez que, nessas situações, os números de IRQ são alterados.

Virtualização e contentores: pensar no anfitrião e no convidado em conjunto

Em ambientes KVM, monto no Anfitrião as IRQs físicas das placas de rede aos núcleos do nó NUMA correspondente. Em paralelo, ligo vhost-net‑Threads e o processo QEMU (ou vCPUs individuais) também devem ser colocados aí, para que os percursos de dados do lado do anfitrião se mantenham curtos. No Convidado Configurei a afinidade de IRQ das vNICs para as vCPUs que associei, no lado do anfitrião, aos núcleos físicos. As cargas de trabalho dos contentores (cgroups/cpuset) beneficiam quando as CPUs permitidas aos contentores coincidem com os núcleos RX/TX do anfitrião – caso contrário, ocorrem acessos remotos evitáveis.

Aprofundar a análise: SoftIRQs, NAPI e deteção de congestionamento

Além de /proc/interrupções olho para /proc/softirqs, para ver se há muito trabalho no contexto de ksoftirqd em vez de ser executado diretamente no manipulador de IRQ – um indício de uma carga elevada e prolongada. Com napi_defer_hard_irqs (Depende do kernel) e, através de atribuições de filas bem definidas, controlo o grau de agressividade com que o NAPI agrupa os dados. ethtool -S fornece-me estatísticas por fila relativas a perdas, estados de ocupação e taxas de pacotes; assim, consigo identificar filas desequilibradas e ajustar a afinidade ou a indireção RSS em conformidade.

# Visão geral rápida da distribuição de SoftIRQ
cat /proc/softirqs | egrep 'NET_RX|NET_TX'

# Verificar as estatísticas do controlador e da fila
ethtool -S ens192 | egrep -i 'rx|tx|drop|busy'

Prática: Mapeamento para 4 filas num nó

Uma configuração típica que utilizo frequentemente: placa de rede (NIC) no nó NUMA 0 com 4 filas RSS. Evito a CPU0 e associo as filas às CPUs 1 a 4. Coloco também os workers Web ou proxy correspondentes nas CPUs 1 a 4, atribuo o XPS de forma idêntica e o RPS permanece desativado. Desta forma, obtenho percursos curtos e consistentes em ambas as direções.

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

Definir a afinidade de IRQ e o 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

# Fixar o processo de trabalho (systemd ou taskset)
# systemd: CPUAffinity=1 2 3 4
# Uma única vez: taskset -c 1-4

Se a carga continuar a aumentar, vou aumentar o número de filas (ethtool -L) até ao número adequado de núcleos do nó e distribua de forma rígida, seguindo um padrão reconhecível (por exemplo, ID da fila → ID do núcleo), para que os fluxos não „migrem“ ao longo do tempo.

Melhores práticas para hosts multi-core

Eu alivio CPU0, uma vez que aí são frequentemente executados temporizadores e serviços internos do kernel que causam perturbações sob carga. Por isso, prefiro atribuir as IRQs críticas a outros núcleos e deixo que a CPU0 suporte apenas algumas fontes não críticas. Em sistemas NUMA, sou coerente e mantenho adaptadores, IRQs, processos e acessos à memória no mesmo . Em ambientes sujeitos a grande carga, separo os núcleos de E/S dos núcleos de aplicações e isolo-os, se necessário. Acompanho todas as alterações com medições contínuas e ajusto as atribuições de forma iterativa.

Adotar uma abordagem mensurável: análise, scripts e plano de contingência

Antes de efetuar alterações, registo o estado atual com mpstat, htop, /proc/interrupções e medições de latência através do iperf3. Configurei scripts que aplicam automaticamente as definições de afinidade após um reinício ou uma atualização dos controladores. Para reverter alterações, mantenho máscaras neutras à disposição, para que, em caso de comportamento indesejado, possa reverter imediatamente. No ambiente de teste, testo perfis de carga que se aproximem o mais possível da minha produção e repito o Medição após cada ajuste. Só então é que ativo o perfil de forma permanente no anfitrião de destino.

Evitar os obstáculos comuns de forma limpa

As máscaras demasiado largas distribuem o trabalho por demasiadas pessoas CPUs e prejudicam o desempenho das caches, enquanto máscaras demasiado restritas entopem as filas. As particularidades do NUMA que são esquecidas geram acessos à memória remota, o que faz com que os tempos de resposta variem. Um «pinning» rígido entra ocasionalmente em conflito com as configurações de RPS/RFS; por isso, verifico explicitamente a distribuição de carga do SoftIRQ. Após atualizações do kernel ou alterações de controladores, volto a validar todos os números de IRQ, uma vez que as atribuições podem sofrer alterações. Com passos cautelosos e uma Documentário continuo apto a agir.

Brevemente resumido

A atribuição seletiva de IRQs vincula as interrupções de rede a um número reduzido de IRQs adequadas Núcleos, reduz a sobrecarga e estabiliza os tempos de resposta. Para tal, ajusto a afiniidade de IRQ e da CPU, tenho em conta a NUMA e verifico o efeito através de séries de medições. Nos casos em que o modo automático é suficiente, deixo o irqbalance atuar; as filas críticas, por sua vez, são direcionadas para núcleos fixos. Com RSS, RPS/RFS e parâmetros do kernel ajustados, o ajuste atinge todo o seu Efeito. Quem seguir estes passos de forma disciplinada conseguirá um desempenho de rede sensivelmente superior em servidores Linux multi-core.

Artigos actuais