...

Linux IRQ-affiniteit op multiprocessorsystemen: praktische gids voor optimale netwerkoptimalisatie

Ik laat aan de hand van praktijkvoorbeelden zien hoe IRQ-affinity op multiprocessorsystemen netwerkonderbrekingen gericht aan CPU-kernen koppelt, de latentie verlaagt en de doorvoer verhoogt. Aan de hand van duidelijke stappen, voorbeelden en een tabel leg ik uit hoe ik hex-maskers kies, rekening houd met NUMA en processen op de juiste manier toewijs.

Centrale punten

  • IRQ Affiniteit leidt hardware-interrupts gericht naar CPU's en vermindert de overhead.
  • CPU-affiniteit Door diensten aan dezelfde cores te koppelen, blijven caches lokaal.
  • NUMA Let op: gebruik adapters en cores van hetzelfde knooppunt.
  • irqbalans afwegen: automatisch verdelen of handmatig bijsturen.
  • Controle en iteratieve aanpassing zorgen voor stabiele latenties.

IRQ-affiniteit begrijpen: basisprincipes en effecten

Op Linux-hosts worden inkomende pakketten, schijf-I/O en timers verwerkt IRQ's die de kernel over de CPU-kernen verdeelt. Ik stel via de bestanden /proc/irq//smp_affinity en .../smp_affinity_list, welke CPU’s een bron mogen bedienen. Een breed standaardmasker lijkt op het eerste gezicht flexibel, maar leidt bij belasting tot cache-misses, kostbare contextwisselingen en verspreide soft-IRQ’s over vele CPU's. Ik koppel kritieke wachtrijen van afzonderlijke NIC’s aan gedefinieerde cores, ontlast hotspots en houd de datapaden kort. Deze sturing verbetert de stabiliteit merkbaar zodra er veel datastromen tegelijk actief zijn.

IRQ- en CPU-affiniteit afstemmen: gegevens lokaal houden

Ik heb de IRQ-De bevoegdheid van de NIC-wachtrijen in relatie tot de CPU-toewijzing van de betreffende workers. Hiervoor pin ik webserver- of proxy-threads met behulp van takenverzameling of CPUAffiniteit= in systemd naar die cores die ook de RX/TX-interrupts verwerken. Hierdoor blijven cache-lijnen lokaal en beperk ik de communicatie tussen CPU’s tot een minimum, wat de Latency gladstrijkt. Vooral API-backends, realtime-diensten en gevirtualiseerde stacks profiteren van deze samenhang. Ik test de koppeling onder productielast totdat flows, SoftIRQ’s en de gebruikersruimte naadloos op elkaar aansluiten.

Automatisch versus fijnafstemming: irqbalance op de juiste manier gebruiken

De service irqbalans verdeelt interrupts automatisch over beschikbare cores, wat goed werkt op algemene servers. In zwaar belaste netwerkomgevingen remt deze spreiding echter de cache-lokaliteit af en bemoeilijkt het gerichte pinning. Ik beperk irqbalance of schakel het selectief uit wanneer bepaalde wachtrijen vaste cores nodig hebben. Voor een basiskennis en geschikte profielen helpt deze handleiding mij bij irqbalance configureren. Het resultaat is dat het automatische systeem de niet-kritieke taken regelt, terwijl ik de gevoelige IRQ’s handmatig toewijs.

Stap: Relevante IRQ's zichtbaar maken

Ik begin met een blik op /proc/onderbrekingen en filter op de apparaatnaam, bijvoorbeeld ens192, eno1 of eth0. Moderne adapters maken meerdere RX- en TX-wachtrijen aan, daarom zoek ik een groep IRQ-nummers die aan dezelfde NIC zijn toegewezen. Ik let op de tellerstanden om hotspots snel te herkennen en wachtrijen die zwaar worden belast als eerste toe te wijzen. Dit overzicht controleer ik regelmatig tijdens belastingstests, zodat de toewijzing op lange termijn standhoudt. Daarnaast controleer ik de benamingen van de stuurprogramma’s, omdat deze aanwijzingen geven over RSS-mogelijkheden en offloading.

# Alle interrupts bekijken
cat /proc/interrupts

# Alleen regels weergeven die relevant zijn voor de NIC (bijvoorbeeld ens192)
grep -i ens192 /proc/interrupts

Slim gebruikmaken van NUMA-topologie

Op hosts met meerdere nodes verplaats ik IRQ’s bij voorkeur naar de cores van die NUMA-Knooppunt waaraan de NIC fysiek is aangesloten. Ik controleer dit met lscpu en numactl --hardware en markeer geschikte CPU-sets voor de latere maskervorming. Processen die deze netwerkpaden gebruiken, koppel ik eveneens aan hetzelfde knooppunt en zorg via geheugenbeleidsregels voor lokale Geheugen-toewijzingen. Zo vermijd ik dure externe toegangen via de QPI/UPI-verbindingen. Deze discipline levert al snel meetbare voordelen op in latentietests.

Veilig bitmaskers kiezen: hexadecimale logica in één oogopslag

Het bestand smp_affinity ondersteunt hex-bitmaskers die rechtstreeks naar core-ID’s verwijzen en ook grote systemen omvatten. Ik begin vaak met eenvoudige patronen: CPU0 is 0x1, CPU1 is 0x2, CPU2 is 0x4, CPU3 is 0x8 enzovoort, terwijl 0xF de cores 0–3 omvat. Op machines met veel cores schrijf ik meerdere 32-bitsblokken, gescheiden door komma’s, zodat de Masker alle ID’s correct weergeeft. Dit overzicht helpt me om foutloze toewijzingen te doen en onbedoelde verschuivingen te voorkomen. De volgende tabel gebruik ik vaak als geheugensteuntje.

CPU-nummer Bit (binair) Hex-masker Tip
0 …0001 0x1 CPU0 vaak ontlasten en spaarzaam gebruiken.
1 …0010 0x2 IRQ op CPU1 vastzetten.
2 …0100 0x4 IRQ op CPU2 aansluiten.
3 …1000 0x8 IRQ op CPU3 aansluiten.
0–3 …1111 0xF Als je alle vier de cores inschakelt, neemt de latentie vaak licht toe.
0–7 11111111 0xFF Brede spreiding, cache-localiteit lijdt eronder.

IRQ-affiniteit instellen: zo koppel ik wachtrijen aan cores

Nadat ik de wachtrij-IRQ's van de NIC heb geïdentificeerd, verdeel ik ze over speciale Kernen zoals 1, 2 en 3, om parallelle verwerking netjes te schalen. Zo blijft elke RX/TX-wachtrij bij zijn eigen core, wat overspraak voorkomt en de SoftIRQ-verwerking consistent houdt. Tijdens de fijnafstemming vergelijk ik de doorvoer en de latentie totdat de verdeling betrouwbaar functioneert. Voor een diepgaandere inleiding helpt een korte Praktische gids met varianten, afhankelijk van de adapter. Ik voer de commando’s bewust uit tijdens onderhoudsvensters en beveilig ze via een opstartscript.

# Voorbeeld: drie wachtrij-IRQ’s koppelen aan CPU1..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)

Proces-pinning: diensten op dezelfde cores plaatsen

Ik koppel applicatieworkers aan die CPU's, die de bijbehorende IRQ’s verwerken, zodat de gegevenspaden kort blijven. Onder systemd gebruik ik CPUAffinity=1 2 3 of begin eenmalig met taskset -c 1-3. Voor multi-worker-servers wijs ik vaste aantallen cores toe per worker-groep, zodat er geen concurrentie ontstaat. Deze koppeling zorgt voor constant lagere Latencies, omdat de CPU-caches goed gevuld blijven. Na wijzigingen controleer ik threads, sockets en soft-IRQ's met htop, ss en perf.

Netwerkoptimalisatie: RSS, RPS/RFS en kernelparameters koppelen

Veel NIC's verspreiden pakketten via RSS over wachtrijen, die ik vervolgens met IRQ-affiniteit aan cores koppel. Details en werkingsprincipes vat ik samen onder Receive Side Scaling compact samengevat. Daarnaast stel ik RPS/RFS zo in dat SoftIRQ’s niet in strijd zijn met de harde pinning-logica. Tegelijkertijd pas ik buffers aan via net.core.rmem_max en net.core.wmem_max en controleer TCP-opties zoals tcp_tijdstempels. Deze bouwstenen dragen bij aan dezelfde doelstelling: een lage latentie bij hoge Bitsnelheid.

MSI-X en de wachtrijindeling correct interpreteren

Moderne NIC’s maken gebruik van MSI-X en maken voor elke RX/TX-wachtrij aparte IRQ’s aan. Ik controleer eerst hoeveel wachtrijen en kanalen het stuurprogramma momenteel heeft geactiveerd en pas dit aan het core-budget van de betreffende NUMA-node aan. Zo voorkom ik dat te veel wachtrijen op te weinig cores terechtkomen of dat er juist capaciteit onbenut blijft.

#: het aantal wachtrijen en kanalen controleren en aanpassen
ethtool -l ens192 # huidige limieten (RX/TX/combined)
ethtool -L ens192 combined 4  # bijv. 4 wachtrijen activeren

# RSS-indirection en hash-instellingen controleren
ethtool -x ens192 # indirection-table en hash-key weergeven

Veel stuurprogramma’s geven IRQ’s een beschrijvende naam (bijvoorbeeld. ens192-TxRx-0). Ik zorg ervoor dat de Indirection-tabel consistent blijft met de Core-toewijzing, zodat flows stabiel in „hun“ wachtrij terechtkomen. Als de hardwareverdeling afwijkt, leidt dit tot onnodige verschuivingen van de SoftIRQ’s.

RPS/RFS en XPS op de juiste manier inzetten

RPS/RFS kan pakketten via software over cores verdelen – handig voor NIC’s zonder veel wachtrijen, maar contraproductief als ik al een exacte toewijzing heb via RSS en IRQ-affiniteit. Ik kies daarom bewust voor: ofwel een harde toewijzing via RSS+IRQ-affiniteit en RPS uit, of een klein aantal hardware-wachtrijen en RPS gericht op. Daarnaast configureer ik XPS voor de TX-richting, zodat uitgaande pakketten door de „juiste“ cores worden verzonden.

IF=ens192

# RPS volledig uitschakelen (bij correcte IRQ-pinning 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

# Alternatief: RPS selectief inschakelen (voorbeeld: CPU's 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
# Beter: gebruik een syntaxis vergelijkbaar met 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

Stel # XPS in overeenkomstig de geselecteerde worker-CPU's (TX-paden)
for q in /sys/class/net/$IF/queues/tx-*; do echo 1-3 > "$q"/xps_cpus; done

Het is belangrijk dat er samenhang is: de RX-IRQ-core, de ksoftirqd-load en de worker-thread moeten op dezelfde core (of hetzelfde core-paar) draaien. Hierdoor verdwijnen veel cross-core-bounce-effecten.

Rekening houden met SMT/Hyper-Threading en core-paren

Op systemen met SMT is het vaak zinvol om een fysieke core te reserveren voor een RX-IRQ en de bijbehorende worker op de Broer- of zusterthread in te stellen – of bewust te scheiden als de werklast rekenintensief is. Ik bepaal de threadparen op basis van de topologie en neem vervolgens een duidelijke beslissing in plaats van een willekeurige verdeling.

#-broer-zusparen bepalen
for c in /sys/devices/system/cpu/cpu*/topology/thread_siblings_list; do
  echo "$(basename "$(dirname "$c")") : $(cat "$c")"
done

Als ik RX-IRQ en user-space-workers over dezelfde fysieke core verdeel (verschillende SMT-threads), ontstaat er een goede L1/L2-localiteit, maar bij CPU-gebonden workloads kunnen er knelpunten ontstaan. Als alternatief verdeel ik IRQ over core X en de worker over core Y van dezelfde NUMA-node, om echte parallelliteit te verkrijgen. Ik test beide varianten met A/B-tests en kies degene die de stabielere latentie oplevert.

Gebruik smp_affinity_list, effective_affinity en Defaults

Naast Hex-maskers schrijf ik graag in smp_affinity_list, aangezien daarmee gebieden zoals 1-3,6,8-9 gemakkelijk laten plaatsen. Ter controle kijk ik of effectieve_affiniteit respectievelijk effectieve_affiniteitslijst, omdat de kernel of stuurprogramma's bepaalde CPU's kunnen uitsluiten (bijvoorbeeld offline cores of „managed interrupts“).

# Door mensen leesbare toewijzing
echo 1-3 > /proc/irq/181/smp_affinity_list

# Effectieve binding controleren
cat /proc/irq/181/effective_affinity_list

Om te voorkomen dat nieuwe IRQ’s of IRQ’s die na het opnieuw laden van een stuurprogramma zijn toegevoegd, weer willekeurig worden verdeeld, stel ik indien nodig /proc/irq/default_smp_affinity tot een zinvolle basiswaarde (bijvoorbeeld alle cores van het betreffende NUMA-knooppunt, maar zonder CPU0). Vervolgens overschrijf ik bepaalde kritieke IRQ's doelgericht.

Persistentie instellen bij het opnieuw opstarten en herladen

Affinity-instellingen zijn vluchtig. Ik maak er een back-up van via een systemd-oneshot-unit die wordt uitgevoerd na de doelstelling voor netwerkinitialisatie, of via een klein script dat IRQ-lijsten dynamisch detecteert en in kaart brengt. Zo blijven toewijzingen behouden, zelfs na kernel-updates en link-resets.

# /usr/local/sbin/net-irq-pin.sh (voorbeeld)
#!/bin/bash
set -euo pipefail
IF=${1:-ens192}
CPUS="1-3"   # Doel-CPU's (kies NUMA-consistent)
for irq in $(grep -i "$IF" /proc/interrupts | awk '{print $1}' | tr -d ':'); do
  echo "$ CPU's" > /proc/irq/$irq/smp_affinity_list || true
done

# systemd-unit (schets)
# /etc/systemd/system/net-irq-pin.service
[Unit]
Description=Pin NIC IRQ's
After=network-online.target

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

[Install]
WantedBy=multi-user.target

Belangrijk: ik roep het script opnieuw op als het stuurprogramma opnieuw is geladen of als het aantal wachtrijen is gewijzigd, omdat de IRQ-nummers dan verschuiven.

Virtualisatie en containers: host en gast als één geheel beschouwen

In KVM-omgevingen koppel ik op de Gastheer de fysieke NIC-IRQ’s aan de cores van het betreffende NUMA-knooppunt. Tegelijkertijd sluit ik vhost-net‑Threads en het QEMU‑proces (of afzonderlijke vCPU’s) ook daar, zodat de gegevenspaden aan de hostzijde kort blijven. In het Gast stel ik de IRQ-affiniteit van de vNIC’s in op die vCPU’s die ik aan de hostzijde aan fysieke cores heb gekoppeld. Container-workloads (cgroups/cpuset) profiteren ervan als de toegestane CPU's van de containers overlappen met de RX/TX-cores van de host – anders ontstaan er vermijdbare externe toegangen.

De analyse verdiepen: SoftIRQ’s, NAPI en files herkennen

Naast /proc/onderbrekingen ik kijk in /proc/softirqs, om te zien of er veel werk is in de context van ksoftirqd in plaats van direct in de IRQ-handler te worden afgehandeld – een aanwijzing voor een aanhoudend hoge belasting. Met napi_defer_hard_irqs (Afhankelijk van de kernel) en door zorgvuldige toewijzing van wachtrijen regel ik hoe agressief NAPI batches verwerkt. ethtool -S geeft me per wachtrij statistieken over drops, ‘busy’-toestanden en pakketsnelheden; hier kan ik onevenwichtige wachtrijen herkennen en de affiniteit of RSS-indirection dienovereenkomstig aanpassen.

# Snel overzicht van de SoftIRQ-verdeling
cat /proc/softirqs | egrep 'NET_RX|NET_TX'

# Stuurprogramma-/wachtrijstatistieken bekijken
ethtool -S ens192 | egrep -i 'rx|tx|drop|busy'

Praktijk: toewijzing van 4 wachtrijen aan één node

Een typische configuratie die ik vaak gebruik: NIC op NUMA-knooppunt 0 met 4 RSS-wachtrijen. Ik vermijd CPU0 en koppel de wachtrijen aan CPU1-4. De bijbehorende web- of proxy-workers plaats ik eveneens op 1-4, XPS wijs ik op dezelfde manier toe en RPS blijft uitgeschakeld. Zo krijg ik korte, consistente paden in beide richtingen.

IF=ens192
QUEUES=(181 182 183 184)   # Voorbeeld-IRQ's (vooraf vaststellen)
CPUS="1-4"

# IRQ-affiniteit en XPS instellen
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

# Worker vastzetten (systemd of taskset)
# systemd: CPUAffinity=1 2 3 4
# Eenmalig: taskset -c 1-4

Als de belasting verder toeneemt, verhoog ik het aantal wachtrijen (ethtool -L) tot het aantal zinvolle cores van de node en verdeel deze strikt volgens een herkenbaar patroon (bijv. wachtrij-ID → core-ID), zodat flows in de loop van de tijd niet „verschuiven“.

Best practices voor multi-core-hosts

Ik ontlast CPU0, aangezien daar vaak timers en interne kernelprocessen draaien die bij hoge belasting storingen veroorzaken. Ik wijs kritieke IRQ’s daarom bij voorkeur toe aan andere cores en laat CPU0 slechts enkele, niet-kritieke bronnen verwerken. Op NUMA-systemen ben ik consequent en houd ik adapters, IRQ’s, processen en geheugentoegangen op dezelfde Knooppunt. In veeleisende omgevingen scheid ik I/O-kernen van applicatiekernen en isoleer ik ze indien nodig. Bij alle wijzigingen voer ik doorlopende metingen uit en pas ik de toewijzingen stapsgewijs aan.

Een meetbare aanpak: analyse, scripts en een noodplan

Voordat ik wijzigingen aanbreng, leg ik de huidige situatie vast met mpstat, htop, /proc/onderbrekingen en latentie-metingen met iperf3. Ik stel scripts in die de affinity-instellingen automatisch toepassen na een reboot of het opnieuw laden van stuurprogramma’s. Voor rollbacks houd ik neutrale maskers achter de hand, zodat ik bij storingen onmiddellijk kan terugschakelen. In de staging-omgeving test ik belastingprofielen die zo dicht mogelijk bij mijn productieomgeving liggen en herhaal ik de Meting na elke aanpassing. Pas dan activeer ik het profiel definitief op de doelhost.

Veelvoorkomende struikelblokken netjes vermijden

Te brede maskers verdelen het werk over te veel mensen CPU's en vertragen caches, terwijl te krappe maskers de wachtrijen verstoppen. Vergeten NUMA-bijzonderheden leiden tot toegang tot extern geheugen, waardoor de responstijden fluctueren. Harde pinning botst soms met RPS/RFS-instellingen, daarom controleer ik expliciet de SoftIRQ-belastingverdeling. Na kernel-updates of het wisselen van stuurprogramma's valideer ik alle IRQ-nummers opnieuw, omdat toewijzingen kunnen veranderen. Met voorzichtige stappen en een duidelijke Documentaire blijf ik in staat om te handelen.

Kort samengevat

Door gerichte IRQ-pinning worden netwerkinterrupts aan een klein aantal geschikte Kernen, vermindert overhead en stabiliseert responstijden. Hiervoor stem ik de IRQ- en CPU-affiniteit af, houd ik rekening met NUMA en controleer ik het effect met reeksen metingen. Waar automatische instellingen volstaan, laat ik irqbalance zijn werk doen; kritieke wachtrijen wijs ik toe aan vaste cores. Met RSS, RPS/RFS en aangepaste kernelparameters komt de tuning volledig tot zijn recht. Effect. Wie deze stappen nauwgezet uitvoert, haalt op multi-core Linux-servers merkbaar meer netwerkprestaties uit het systeem.

Huidige artikelen