Je montre de manière concrète comment l'« IRQ affinity » permet, sur les systèmes multiprocesseurs, d'affecter de manière ciblée les interruptions réseau à des cœurs de processeur, de réduire la latence et d'augmenter le débit. À l'aide d'étapes claires, d'exemples et d'un tableau, je précise comment choisir les masques hexadécimaux, tenir compte de la structure NUMA et affecter les processus de manière appropriée.
Points centraux
- IRQ Affinity redirige de manière ciblée les interruptions matérielles vers les processeurs et réduit la surcharge.
- Affinité CPU Le fait de fixer les services aux mêmes cœurs permet de conserver les caches localement.
- NUMA Notez que les adaptateurs et les cœurs d'un même nœud sont utilisés.
- irqbalance peser le pour et le contre : répartition automatique ou ajustement manuel.
- Suivi et l'ajustement itératif garantissent des latences stables.
Comprendre l'affinité IRQ : principes de base et effets
Sur les hôtes Linux, les paquets entrants, les E/S disque et les temporisateurs IRQs que le noyau répartit entre les cœurs de processeur. Je définis, via les fichiers /proc/irq//smp_affinity et .../smp_affinity_list, quels processeurs sont autorisés à accéder à une source. Un masque standard large semble à première vue offrir une grande souplesse, mais il génère, en cas de « Last Cache Misses », des changements de contexte coûteux et des SoftIRQ dispersées sur de nombreux CPUs. J'affecte les files d'attente critiques de certaines cartes réseau à des cœurs définis, je désengorge les points de congestion et je réduis les chemins de données. Ce routage améliore sensiblement la stabilité dès que de nombreux flux sont actifs simultanément.
Optimiser l'IRQ et l'affinité CPU : conserver les données en local
J'ai réussi à connecter la IRQ-Attribution des files d'attente NIC en fonction du processeur auquel sont affectés les workers concernés. Pour cela, j'épingle les threads du serveur web ou du proxy à l'aide de taskset ou CPUAffinity= dans systemd, sur les cœurs qui traitent également les interruptions RX/TX. Ainsi, les lignes de cache restent locales et je réduis au minimum les communications entre processeurs, ce qui Latence lisse. Les backends API, les services en temps réel et les piles virtualisées, en particulier, bénéficient de cette cohérence. Je teste l'interface sous charge de production jusqu'à ce que les flux, les SoftIRQ et l'espace utilisateur s'imbriquent parfaitement les uns dans les autres.
Automatique ou réglage fin : bien comprendre le rôle d'irqbalance
Le service irqbalance répartit automatiquement les interruptions entre les cœurs disponibles, ce qui fonctionne bien sur les serveurs polyvalents. Dans les configurations réseau soumises à une charge élevée, cette répartition nuit toutefois à la localité du cache et complique le « pinning » ciblé. Je limite l'utilisation d'irqbalance ou je le désactive de manière sélective lorsque certaines files d'attente nécessitent des cœurs fixes. Pour acquérir les bases et trouver les profils adaptés, je me réfère à ce guide sur Configurer irqbalance. En résumé, le système automatique gère les IRQ non critiques, tandis que j'attribue manuellement les IRQ sensibles.
Étape : afficher les IRQ concernées
Je commence par jeter un œil à /proc/interrupts et je filtre par nom d'appareil, par exemple ens192, eno1 ou eth0. Les adaptateurs modernes créent plusieurs files d’attente RX et TX ; je recherche donc un groupe de numéros d’IRQ attribués à la même carte réseau. Je surveille les valeurs des compteurs afin d’identifier rapidement les points sensibles et de prioriser les files d’attente les plus sollicitées. Je vérifie régulièrement cette vue lors des tests de charge afin de m’assurer que l’affectation reste valable à long terme. De plus, je contrôle les noms des pilotes, car ils fournissent des indications sur les capacités RSS et de déchargement.
# Afficher toutes les interruptions
cat /proc/interrupts
# Afficher uniquement les lignes relatives à la carte réseau (exemple : ens192)
grep -i ens192 /proc/interrupts
Utiliser intelligemment la topologie NUMA
Sur les hôtes comportant plusieurs nœuds, je préfère réaffecter les IRQ aux cœurs de ceux-ci NUMA- Nœud auquel la carte réseau est physiquement connectée. Je vérifie cela avec lscpu et numactl --hardware et je sélectionne les ensembles de CPU appropriés pour la création ultérieure des masques. Je rattache également au même nœud les processus qui utilisent ces chemins de réseau et je m'assure, via des politiques de mémoire, d'une Mémoire-Affectations. Cela me permet d'éviter les accès à distance coûteux via les liaisons QPI/UPI. Cette rigueur apporte rapidement des avantages mesurables lors des tests de latence.
Choisir des masques de bits en toute sécurité : la logique hexadécimale en un coup d'œil
Le fichier smp_affinity accepte les masques de bits hexadécimaux qui correspondent directement aux identifiants de cœurs et couvrent également les systèmes de grande envergure. Je commence souvent par des schémas simples : CPU0 est 0x1, CPU1 est 0x2, CPU2 est 0x4, CPU3 est 0x8, etc., tandis que 0xF englobe les cœurs 0 à 3. Sur les machines comportant de nombreux cœurs, j'écris plusieurs blocs de 32 bits, séparés par des virgules, afin que la Masque reflète correctement tous les identifiants. Cet aperçu m'aide à effectuer des affectations sans erreur et à éviter les transferts involontaires. J'utilise souvent le tableau suivant comme aide-mémoire.
| Numéro de processeur | Bit (binaire) | Masque hexagonal | Remarque |
|---|---|---|---|
| 0 | …0001 | 0x1 | CPU0 souvent les décharger et ne les utiliser qu'avec parcimonie. |
| 1 | …0010 | 0x2 | IRQ sur CPU1 pinnen. |
| 2 | …0100 | 0x4 | Connecter l'IRQ au CPU2. |
| 3 | …1 000 | 0x8 | Connecter l'IRQ au CPU3. |
| 0–3 | …1111 | 0xF | Lorsque les quatre cœurs sont activés, la latence augmente souvent légèrement. |
| 0–7 | 11111111 | 0xFF | Répartition étendue, la localité du cache en pâtit. |
Configurer l'affinité IRQ : comment associer des files d'attente à des cœurs
Après avoir identifié les IRQ de file d'attente de la carte réseau, je les répartis sur des Cores comme 1, 2 et 3, afin de faire évoluer correctement le traitement parallèle. Ainsi, chaque file d’attente RX/TX reste sur son cœur, ce qui évite la diaphonie et garantit la cohérence du traitement des SoftIRQ. Pendant la phase de mise au point, je compare le débit et la latence jusqu’à ce que la répartition soit fiable. Pour une introduction plus approfondie, un bref Guide pratique avec des variantes selon l'adaptateur. J'exécute ces commandes délibérément pendant les fenêtres de maintenance et je les sécurise à l'aide d'un script de démarrage.
Exemple # : associer trois IRQ de file d'attente aux CPU 1 à 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)
Processus de « pinning » : affecter les services aux mêmes cœurs
J'affecte les workers d'application à ceux-là CPUs, qui gèrent les IRQ correspondantes, afin que les chemins de données restent courts. Sous systemd, j'utilise CPUAffinity=1 2 3 ou lance-le une seule fois avec taskset -c 1-3. Pour les serveurs multi-workers, j'attribue un nombre fixe de cœurs à chaque groupe de workers afin d'éviter toute concurrence. Ce couplage permet d'obtenir des valeurs constamment plus faibles Latence, car les caches du processeur restent correctement remplis. Après chaque modification, je vérifie les threads, les sockets et les SoftIRQ à l'aide de htop, ss et perf.
Optimisation du réseau : associer RSS, RPS/RFS et les paramètres du noyau
De nombreuses cartes réseau transmettent les paquets via RSS sur les files d'attente, que j'associe ensuite à des cœurs à l'aide de l'affinité IRQ. Je résume les détails et les principes de fonctionnement sous Mise à l'échelle côté réception de manière compacte. De plus, je gère les RPS/RFS de sorte que les SoftIRQ n'interfèrent pas avec la logique de pinning fixe. En parallèle, j'ajuste les tampons via net.core.rmem_max et net.core.wmem_max et vérifie les options TCP telles que tcp_timestamps. Ces éléments contribuent tous au même objectif : une faible latence associée à un haut niveau de Débit binaire.
Bien interpréter le MSI-X et la structure des files d'attente
Les cartes réseau modernes utilisent le protocole MSI-X et créent des IRQ distinctes pour chaque file d'attente RX/TX. Je vérifie d’abord le nombre de files d’attente et de canaux actuellement activés par le pilote, puis j’adapte ce nombre au budget en cœurs du nœud NUMA correspondant. Cela me permet d’éviter qu’un trop grand nombre de files d’attente ne se retrouvent sur un nombre insuffisant de cœurs ou, à l’inverse, que des capacités restent inutilisées.
Vérifier et ajuster le nombre de files d'attente et de canaux #
ethtool -l ens192 # limites actuelles (RX/TX/combinées)
ethtool -L ens192 combined 4 # : activer 4 files d'attente, par exemple
# : vérifier les paramètres RSS Indirection et Hash
ethtool -x ens192 # : afficher la table d'indirection et la clé de hachage
De nombreux pilotes attribuent des noms évocateurs aux IRQ (par exemple,. ens192-TxRx-0). Je veille à ce que la table d'indirection soit cohérente avec l'affectation du cœur, afin que les flux soient acheminés de manière stable vers „ leur “ file d'attente. Si la répartition matérielle s'en écarte, cela entraîne des migrations inutiles des SoftIRQ.
Utiliser les formats RPS/RFS et XPS de manière cohérente
RPS/RFS peut répartir les paquets au niveau logiciel entre les cœurs – ce qui est utile pour les cartes réseau ne disposant pas de nombreuses files d’attente, mais contre-productif si j’effectue déjà un mappage précis via RSS et l’affinité IRQ. Je fais donc un choix délibéré : soit un mappage rigide via RSS + affinité IRQ, soit RPS désactivé, ou un petit nombre de files d’attente matérielles et Cibler le RPS. De plus, je configure XPS pour la direction TX afin que les paquets sortants soient envoyés par les cœurs „ appropriés “.
IF=ens192
# Désactiver complètement le RPS (en cas d'affectation correcte des 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
# Alternative : activer RPS de manière ciblée (exemple : processeurs 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
# Mieux : utiliser une syntaxe similaire à celle 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
Définir # XPS en fonction des processeurs de travail sélectionnés (chemins TX)
for q in /sys/class/net/$IF/queues/tx-*; do echo 1-3 > "$q"/xps_cpus; done
La cohérence est essentielle : le cœur RX-IRQ, la charge ksoftirqd et le thread de travail doivent se trouver sur le même cœur (ou la même paire de cœurs). Cela permet d'éliminer de nombreux effets de rebond entre cœurs.
Prendre en compte la technologie SMT/Hyper-Threading et les paires de cœurs
Sur les systèmes équipés de SMT, il est souvent judicieux de réserver un cœur physique à une interruption RX-IRQ et d'affecter le worker correspondant au thread frère de les associer – ou de les séparer délibérément lorsque la charge de travail est intensive en calcul. Je détermine les paires de threads à partir de la topologie, puis je prends une décision claire plutôt que de procéder à une répartition aléatoire.
Identifier les paires de frères et sœurs #
for c in /sys/devices/system/cpu/cpu*/topology/thread_siblings_list; do
echo "$(basename "$(dirname "$c")") : $(cat "$c")"
done
Si je répartis les RX-IRQ et les workers de l'espace utilisateur sur le même cœur physique (différents threads SMT), j'obtiens une bonne localité L1/L2, mais des goulots d'étranglement peuvent apparaître avec des charges de travail liées au processeur. Sinon, je répartis les IRQ sur le cœur X et les workers sur le cœur Y d’un même nœud NUMA, afin d’obtenir un véritable parallélisme. Je teste ces deux variantes en A/B et je choisis celle qui offre la latence la plus stable.
Utiliser smp_affinity_list, effective_affinity et les valeurs par défaut
Outre les masques hexadécimaux, j'aime bien écrire en smp_affinity_list, car cela permet d'aborder des domaines tels que 1-3,6,8-9 facile à installer. Pour m'en assurer, je vérifie affinité_effective respectivement liste_d'affinités_effectives, car le noyau ou les pilotes peuvent exclure certains processeurs (par exemple, les cœurs mis hors ligne ou les „ interruptions gérées “).
# Affectation lisible par l'utilisateur
echo 1-3 > /proc/irq/181/smp_affinity_list
# Vérifier l'affectation effective
cat /proc/irq/181/effective_affinity_list
Pour éviter que les IRQ nouvelles ou ajoutées après un rechargement des pilotes ne soient à nouveau réparties de manière trop dispersée, je définis, si nécessaire, /proc/irq/default_smp_affinity à une valeur de base pertinente (par exemple, tous les cœurs du nœud NUMA concerné, à l'exception du CPU0). Je passe ensuite outre certaines IRQ critiques de manière ciblée.
Assurer la persistance après les redémarrages et les rechargements
Les paramètres d'Affinity sont éphémères. Je les sauvegarde via une unité systemd « oneshot » qui s'exécute après l'objectif d'initialisation du réseau, ou à l'aide d'un petit script qui détecte et mappe dynamiquement les listes d'IRQ. Ainsi, les affectations sont conservées même après les mises à jour du noyau et les réinitialisations de liens.
# /usr/local/sbin/net-irq-pin.sh (exemple)
#!/bin/bash
set -euo pipefail
IF=${1:-ens192}
CPUS="1-3" # Processeurs cibles (choisir des processeurs cohérents 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
Unité systemd # (schéma)
# /etc/systemd/system/net-irq-pin.service
[Unit]
Description=Fixer les IRQ des cartes réseau
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/net-irq-pin.sh ens192
[Install]
WantedBy=multi-user.target
Important : je relance le script lorsque le pilote a été rechargé ou que le nombre de files d'attente a été modifié, car les numéros d'IRQ changent alors.
Virtualisation et conteneurs : envisager ensemble l'hôte et l'invité
Dans les environnements KVM, je monte sur le Hôte les IRQ physiques des cartes réseau vers les cœurs du nœud NUMA correspondant. En parallèle, je connecte vhost-net‑Les threads et le processus QEMU (ou les vCPU individuels) y sont également placés, afin que les chemins de données restent courts côté hôte. Dans le Invité J'oriente l'affinité IRQ des vNIC vers les vCPU que j'ai associées à des cœurs physiques côté hôte. Les charges de travail des conteneurs (cgroups/cpuset) en tirent profit lorsque les processeurs autorisés des conteneurs recoupent les cœurs RX/TX de l’hôte ; dans le cas contraire, cela entraîne des accès à distance évitables.
Approfondir l'analyse : SoftIRQ, NAPI et détection des embouteillages
En plus de /proc/interrupts je jette un œil à /proc/softirqs, pour voir s'il y a beaucoup de travail dans le contexte de ksoftirqd au lieu d'être traitée directement dans le gestionnaire d'IRQ – ce qui indique une charge élevée persistante. Avec napi_defer_hard_irqs (En fonction du noyau) et grâce à des attributions de files d'attente propres, je contrôle le niveau d'agressivité du traitement par lots de NAPI. ethtool -S Il me fournit, pour chaque file d'attente, des statistiques sur les paquets perdus, les états « occupé » et les débits de paquets ; cela me permet d'identifier les files d'attente déséquilibrées et d'ajuster l'affinité ou l'indirection RSS en conséquence.
# Aperçu rapide de la répartition des SoftIRQ
cat /proc/softirqs | egrep 'NET_RX|NET_TX'
# Consulter les statistiques du pilote et de la file d'attente
ethtool -S ens192 | egrep -i 'rx|tx|drop|busy'
Exemple pratique : mappage de 4 files d'attente sur un nœud
Une configuration typique que j'utilise souvent : une carte réseau sur le nœud NUMA 0 avec 4 files d'attente RSS. J'évite le CPU0 et j'associe les files d'attente aux CPU1 à 4. Je place également les workers Web ou proxy correspondants sur les CPU 1 à 4, j'attribue les XPS de manière identique et je désactive les RPS. Cela me permet d'obtenir des chemins courts et cohérents dans les deux sens.
IF=ens192
QUEUES=(181 182 183 184) # Exemples d'IRQ (à déterminer au préalable)
CPUS="1-4"
Définir l'affinité IRQ et XPS pour #
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
# Épingler le worker (systemd ou taskset)
# systemd : CPUAffinity=1 2 3 4
# Une seule fois : taskset -c 1-4
Si la charge continue d'augmenter, j'augmenterai le nombre de files d'attente (ethtool -L) jusqu'au nombre de cœurs pertinents du nœud et répartissez-les de manière fixe selon un schéma identifiable (par exemple, ID de file d'attente → ID de cœur), afin que les flux ne „ migrent “ pas au fil du temps.
Bonnes pratiques pour les hôtes multicœurs
Je soulage CPU0, car c'est là que s'exécutent souvent des temporisateurs et des services internes du noyau qui peuvent causer des perturbations en cas de charge importante. Je préfère donc affecter les IRQ critiques à d’autres cœurs et ne confier à CPU0 que quelques sources non critiques. Sur les systèmes NUMA, je reste cohérent et maintiens les adaptateurs, les IRQ, les processus et les accès mémoire sur le même Nœud. Dans les environnements fortement sollicités, je sépare les cœurs d'E/S des cœurs d'application et je les isole si nécessaire. J'accompagne chaque modification par des mesures en continu et j'ajuste les affectations de manière itérative.
Adopter une approche mesurable : analyse, scripts et plan de secours
Avant d'effectuer des modifications, je consigne l'état actuel à l'aide de mpstat, htop, /proc/interrupts et des mesures de latence via iperf3. Je configure des scripts qui appliquent automatiquement les paramètres d'affinité après un redémarrage ou un rechargement des pilotes. Pour les retours en arrière, je dispose de masques neutres afin de pouvoir revenir immédiatement en arrière en cas de dysfonctionnement. En environnement de préproduction, je teste des profils de charge aussi proches que possible de ceux de ma production et je répète les Mesure après chaque modification. Ce n'est qu'alors que j'active définitivement le profil sur l'hôte cible.
Éviter proprement les écueils fréquents
Les masques trop larges répartissent le travail entre un trop grand nombre de personnes CPUs et ralentissent les caches, tandis que des masques trop restrictifs encombrent les files d’attente. Les particularités NUMA négligées génèrent des accès à la mémoire distante, ce qui entraîne des variations dans les temps de réponse. Un « pinning » strict entre parfois en conflit avec les paramètres RPS/RFS ; c’est pourquoi je vérifie explicitement la répartition de la charge des SoftIRQ. Après des mises à jour du noyau ou des changements de pilotes, je revalide tous les numéros d’IRQ, car les affectations peuvent changer. En procédant avec prudence et en suivant une approche claire documentaire je reste capable d'agir.
En bref
Le « pinning » ciblé des IRQ permet de mapper les interruptions réseau à un petit nombre d'IRQ appropriées Cores, réduit la charge système et stabilise les temps de réponse. Pour cela, j’ajuste l’affinité des IRQ et des cœurs de processeur, je tiens compte de la structure NUMA et je vérifie l’efficacité de ces réglages à l’aide de séries de mesures. Lorsque le mode automatique suffit, je laisse « irqbalance » agir ; j’affecte les files d’attente critiques à des cœurs fixes. Grâce à RSS, RPS/RFS et à des paramètres du noyau adaptés, l’optimisation déploie tout son Effet. En suivant ces étapes avec rigueur, vous obtiendrez des performances réseau nettement supérieures sur les serveurs Linux multicœurs.


