...

Affinność IRQ w systemie Linux na systemach wieloprocesorowych: praktyczny przewodnik po optymalnej konfiguracji sieci

W oparciu o praktyczne przykłady pokażę, w jaki sposób funkcja IRQ Affinity w systemach wieloprocesorowych celowo przypisuje przerwania sieciowe do poszczególnych rdzeni procesora, zmniejsza opóźnienia i zwiększa przepustowość. Krok po kroku, na podstawie przykładów i tabeli, wyjaśniam, w jaki sposób dobieram maski szesnastkowe, uwzględniam architekturę NUMA i odpowiednio przypisuję procesy.

Punkty centralne

  • Przynależność IRQ ukierunkowuje przerwania sprzętowe do konkretnych procesorów i zmniejsza obciążenie systemowe.
  • Afinność procesora Przypisanie usług do tych samych rdzeni pozwala zachować lokalność pamięci podręcznej.
  • NUMA Należy pamiętać, że adaptery i rdzenie tego samego węzła korzystają z tej samej sieci.
  • irqbalance rozważyć: automatyczny podział czy ręczne dopracowanie.
  • Monitoring a iteracyjne dostosowywanie zapewnia stabilne opóźnienia.

Zrozumieć powiązanie IRQ: podstawy i działanie

Na serwerach z systemem Linux przyczyną są pakiety przychodzące, operacje wejścia/wyjścia na dysku oraz liczniki czasu IRQ które jądro rozdziela między rdzenie procesora. Za pomocą plików określam /proc/irq//smp_affinity oraz .../smp_affinity_list, które procesory mogą obsługiwać dane źródło. Szeroka maska standardowa wydaje się na pierwszy rzut oka elastyczna, jednak w przypadku obciążenia powoduje braki w pamięci podręcznej, kosztowne zmiany kontekstu oraz rozproszone miękkie przerwania (SoftIRQ) rozłożone na wiele Procesory. Przypisuję krytyczne kolejki poszczególnych kart sieciowych do określonych rdzeni, odciążam punkty newralgiczne i dbam o to, by ścieżki transmisji danych były jak najkrótsze. Takie kierowanie ruchem zauważalnie poprawia stabilność, gdy jednocześnie aktywnych jest wiele strumieni danych.

Dostosowanie IRQ i powinowactwa procesora: przechowywanie danych lokalnie

Udało mi się połączyć IRQ-Przypisanie kolejek NIC do procesorów, do których należą odpowiednie procesy robocze. W tym celu przypisuję wątki serwera WWW lub proxy za pomocą zestaw zadań lub CPUAffinity= w systemd na te rdzenie, które obsługują również przerwania RX/TX. Dzięki temu linie pamięci podręcznej pozostają lokalne, a komunikacja między procesorami jest ograniczona do minimum, co Opóźnienie wyrównuje. Szczególnie backendy API, usługi działające w czasie rzeczywistym i stosy wirtualizowane czerpią korzyści z tej spójności. Testuję połączenie pod obciążeniem produkcyjnym, dopóki przepływy, miękkie przerwania (SoftIRQ) i przestrzeń użytkownika nie będą ze sobą idealnie współgrać.

Tryb automatyczny a precyzyjne dostrajanie: właściwe ustawienie opcji irqbalance

Usługa irqbalance automatycznie rozdziela przerwania między dostępne rdzenie, co sprawdza się dobrze na serwerach ogólnego przeznaczenia. Jednak w konfiguracjach sieciowych o dużym obciążeniu takie rozdzielanie ogranicza lokalność pamięci podręcznej i utrudnia precyzyjne przypisywanie. Ograniczam działanie irqbalance lub wyłączam go selektywnie, gdy określone kolejki wymagają stałych rdzeni. W zrozumieniu podstaw i doborze odpowiednich profili pomaga mi ten przewodnik dotyczący Konfiguracja irqbalance. W rezultacie system automatyczny obsługuje sygnały o niskim priorytecie, podczas gdy wrażliwe sygnały IRQ przypisuję ręcznie.

Krok: Wyświetlenie odpowiednich sygnałów IRQ

Zacznę od spojrzenia na /proc/interrupts i filtruję według nazwy urządzenia, np. ens192, eno1 lub eth0. Nowoczesne adaptery tworzą kilka kolejek RX i TX, dlatego szukam grupy numerów IRQ przypisanych do tej samej karty sieciowej. Zwracam uwagę na wartości liczników, aby szybko wykrywać punkty newralgiczne i w pierwszej kolejności przydzielać kolejki o dużym obciążeniu. Regularnie sprawdzam ten widok podczas testów obciążeniowych, aby zapewnić trwałość przypisania. Dodatkowo sprawdzam nazwy sterowników, ponieważ dostarczają one wskazówek dotyczących możliwości RSS i odciążania.

# Wyświetl wszystkie przerwania
cat /proc/interrupts

# Wyświetl tylko wiersze dotyczące kart sieciowych (przykład: ens192)
grep -i ens192 /proc/interrupts

Mądre wykorzystanie topologii NUMA

Na serwerach z wieloma węzłami przenoszę sygnały IRQ przede wszystkim na rdzenie tych NUMA-Węzeł, do którego fizycznie podłączona jest karta sieciowa. Sprawdzam to za pomocą lscpu oraz numactl --hardware i zaznaczam odpowiednie zestawy procesorów do późniejszego tworzenia masek. Procesy korzystające z tych ścieżek sieciowych również przypisuję do tego samego węzła i za pomocą zasad pamięci zapewniam lokalne Pamięć-Przypisania. W ten sposób unikam kosztownych połączeń zdalnych realizowanych poprzez łącza QPI/UPI. Takie podejście szybko przynosi wymierne korzyści w testach opóźnień.

Jak bezpiecznie wybierać maski bitowe: logika szesnastkowa w skrócie

Plik smp_affinity akceptuje maski bitowe w formacie szesnastkowym, które bezpośrednio odpowiadają identyfikatorom rdzeni i obejmują również duże systemy. Często zaczynam od prostych wzorów: CPU0 to 0x1, CPU1 to 0x2, CPU2 to 0x4, CPU3 to 0x8 itd., podczas gdy 0xF obejmuje rdzenie 0–3. Na maszynach z dużą liczbą rdzeni zapisuję kilka 32-bitowych bloków, oddzielonych przecinkami, aby Maska dokładnie odwzorowuje wszystkie identyfikatory. Ten przegląd pomaga mi dokonywać bezbłędnych przyporządkowań i unikać niezamierzonych przesunięć. Poniższą tabelę często wykorzystuję jako pomoc w zapamiętaniu.

Numer procesora Bit (binarny) Maska Hex Wskazówka
0 …0001 0x1 CPU0 często odciążać i używać oszczędnie.
1 …0010 0x2 IRQ na CPU1 przypiąć.
2 …0100 0x4 Podłączyć sygnał IRQ do procesora CPU2.
3 …1000 0x8 Podłączyć sygnał IRQ do procesora CPU3.
0–3 …1111 0xF Włączenie wszystkich czterech rdzeni często powoduje niewielki wzrost opóźnienia.
0–7 11111111 0xFF Szerokie rozproszenie danych, co negatywnie wpływa na lokalność pamięci podręcznej.

Ustawianie powiązania IRQ: jak przypisać kolejki do rdzeni

Po zidentyfikowaniu IRQ z kolejką karty sieciowej rozdzielam je na dedykowane Rdzenie tak jak 1, 2 i 3, aby płynnie skalować przetwarzanie równoległe. Dzięki temu każda kolejka RX/TX pozostaje przy swoim rdzeniu, co pozwala uniknąć zakłóceń krzyżowych i zapewnia spójność działania SoftIRQ. Podczas dopracowywania porównuję przepustowość i opóźnienia, aż rozkład zacznie działać niezawodnie. Aby uzyskać głębszy wgląd w temat, pomocny jest krótki Praktyczny przewodnik z różnymi wariantami w zależności od adaptera. Polecenia te celowo uruchamiam w oknach serwisowych i zabezpieczam je za pomocą skryptu startowego.

Przykład #: przypisanie trzech IRQ kolejki do procesorów 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)

Przypisywanie procesów: umieszczanie usług na tych samych rdzeniach

Przypisuję procesy robocze aplikacji do tych Procesory, które obsługują odpowiednie sygnały IRQ, dzięki czemu ścieżki danych pozostają krótkie. W systemd korzystam z CPUAffinity=1 2 3 lub uruchom jednorazowo za pomocą taskset -c 1-3. W przypadku serwerów z wieloma procesami przypisuję stałe zestawy rdzeni dla każdej grupy procesów, aby uniknąć konkurencji. Takie powiązanie zapewnia stale niższe Opóźnienia, ponieważ pamięci podręczne procesora pozostają odpowiednio zapełnione. Po wprowadzeniu zmian sprawdzam wątki, gniazda i miękkie przerwania za pomocą narzędzi htop, ss i perf.

Optymalizacja sieci: połączenie RSS, RPS/RFS i parametrów jądra

Wiele kart sieciowych rozsyła pakiety za pomocą RSS o kolejkach, które następnie przypisuję do rdzeni za pomocą IRQ-Affinity. Szczegóły i zasady działania podsumowuję w sekcji Skalowanie po stronie odbiorczej w kompaktowy sposób. Dodatkowo steruję RPS/RFS tak, aby miękkie przerwania (SoftIRQ) nie kolidowały z twardą logiką przypisania pinów. Równolegle dostosowuję bufory poprzez net.core.rmem_max oraz net.core.wmem_max i sprawdź opcje TCP, takie jak tcp_timestamps. Te elementy składowe przyczyniają się do osiągnięcia tego samego celu: niskiego opóźnienia przy wysokiej Szybkość transmisji.

Prawidłowa interpretacja MSI-X i układu kolejki

Nowoczesne karty sieciowe wykorzystują technologię MSI‑X i tworzą osobne sygnały IRQ dla każdej kolejki RX/TX. Najpierw sprawdzam, ile kolejek i kanałów ma aktualnie włączonych sterownik, a następnie dostosowuję to do budżetu rdzeni odpowiedniego węzła NUMA. W ten sposób zapobiegam sytuacji, w której zbyt wiele kolejek jest przypisanych do zbyt małej liczby rdzeni lub, odwrotnie, gdy część mocy obliczeniowej pozostaje niewykorzystana.

Sprawdź i dostosuj liczbę kolejek oraz kanałów w trybie #
ethtool -l ens192 # aktualne limity (RX/TX/combined)
ethtool -L ens192 combined 4  # np. aktywacja 4 kolejek

# Sprawdź ustawienia RSS-Indirection i Hash
ethtool -x ens192 # Wyświetl tabelę Indirection i klucz Hash

Wiele sterowników nadaje IRQ-om opisowe nazwy (np. ens192-TxRx-0). Utrzymuję spójność tablicy Indirection z przypisaniem rdzeni, aby przepływy stabilnie trafiały do „swoich“ kolejek. Jeśli rozkład sprzętowy ulegnie zmianie, dochodzi do niepotrzebnych przemieszczeń SoftIRQ.

Właściwe stosowanie RPS/RFS i XPS

RPS/RFS może rozdzielać pakiety programowo między rdzenie – to dobre rozwiązanie w przypadku kart sieciowych bez wielu kolejek, ale przynosi efekt przeciwny do zamierzonego, jeśli już dokładnie przypisuję je za pomocą RSS i IRQ-Affinity. Dlatego świadomie podejmuję decyzję: albo twarde przypisanie za pomocą RSS+IRQ-Affinity, albo RPS wyłączone, albo kilka kolejek sprzętowych i RPS ukierunkowane na. Dodatkowo konfiguruję XPS dla kierunku TX, aby pakiety wychodzące były wysyłane przez „właściwe“ rdzenie.

IF=ens192

# Całkowite wyłączenie RPS (przy poprawnym przypisaniu IRQ za pomocą 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

# Alternatywnie: selektywne włączenie RPS (przykład: procesory 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
# Lepiej: użyć składni podobnej do 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

Ustawienie # XPS odpowiednio do wybranych procesorów roboczych (ścieżki TX)
for q in /sys/class/net/$IF/queues/tx-*; do echo 1-3 > "$q"/xps_cpus; done

Ważna jest spójność: rdzeń RX-IRQ, obciążenie ksoftirqd i wątek roboczy powinny znajdować się na tym samym rdzeniu (lub parze rdzeni). Dzięki temu znika wiele efektów „cross-core bounce”.

Uwzględnienie technologii SMT/Hyper-Threading oraz par rdzeni

W systemach z technologią SMT często warto zarezerwować fizyczny rdzeń dla przerwania RX-IRQ i przyporządkować odpowiedni proces roboczy do Wątek pokrewny – lub świadomie je rozdzielić, jeśli obciążenie wymaga intensywnych obliczeń. Na podstawie topologii określam pary wątków, a następnie podejmuję jasną decyzję zamiast stosować losowy podział.

Wykrywanie par równorzędnych #
for c in /sys/devices/system/cpu/cpu*/topology/thread_siblings_list; do
  echo "$(basename "$(dirname "$c")") : $(cat "$c")"
done

Jeśli rozdzielę RX-IRQ i procesy użytkownika na tym samym rdzeniu fizycznym (różne wątki SMT), uzyskuję dobrą lokalność L1/L2, ale w przypadku obciążeń ograniczonych przez procesor mogą wystąpić wąskie gardła. Alternatywnie rozdzielam IRQ na rdzeń X, a procesy User‑Space‑Worker na rdzeń Y tego samego węzła NUMA, aby uzyskać prawdziwą równoległość. Oba warianty testuję metodą A/B i wybieram ten, który zapewnia bardziej stabilne opóźnienia.

Korzystanie z smp_affinity_list, effective_affinity i Defaults

Oprócz masek Hex lubię pisać w smp_affinity_list, ponieważ dzięki temu można zająć się takimi obszarami jak 1-3,6,8-9 można je wygodnie ustawić. Aby to sprawdzić, sprawdzam effective_affinity Odpowiednio lista_efektywnych_pokrewieństw, ponieważ jądro lub sterowniki mogą pomijać niektóre procesory (np. rdzenie wyłączone z pracy lub „zarządzane przerwania“).

# Przypisanie w formacie czytelnym dla człowieka
echo 1-3 > /proc/irq/181/smp_affinity_list

# Sprawdź efektywne przypisanie
cat /proc/irq/181/effective_affinity_list

Aby nowe IRQ lub te dodane po ponownym załadowaniu sterowników nie były ponownie rozrzucane po całym zakresie, w razie potrzeby ustawiam /proc/irq/default_smp_affinity do sensownej wartości bazowej (np. wszystkie rdzenie odpowiedniego węzła NUMA, z wyjątkiem CPU0). Następnie celowo nadpisuję poszczególne krytyczne sygnały IRQ.

Zapewnienie trwałości po ponownym uruchomieniu i przeładowaniu strony

Ustawienia Affinity są ulotne. Zapisuję je za pomocą jednostki systemd typu oneshot, która uruchamia się po osiągnięciu celu inicjalizacji sieci, lub za pomocą małego skryptu, który dynamicznie wykrywa i mapuje listy IRQ. Dzięki temu przypisania pozostają zachowane nawet po aktualizacjach jądra i zresetowaniu połączeń.

# /usr/local/sbin/net-irq-pin.sh (przykład)
#!/bin/bash
set -euo pipefail
IF=${1:-ens192}
CPUS="1-3"   # docelowe procesory (wybrać zgodne z 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

Jednostka systemd # (w zarysie)
# /etc/systemd/system/net-irq-pin.service
[Unit]
Description=Przypisanie przerwań IRQ kart sieciowych
After=network-online.target

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

[Install]
WantedBy=multi-user.target

Ważne: Uruchamiam skrypt ponownie, gdy sterownik zostanie ponownie załadowany lub zmieni się liczba kolejek, ponieważ wówczas zmieniają się numery IRQ.

Wirtualizacja i kontenery: wspólne podejście do hosta i gościa

W środowiskach KVM montuję na Gospodarz fizyczne sygnały przerwań NIC na rdzeniach odpowiedniego węzła NUMA. Równolegle podłączam vhost-net‑Wątek i proces QEMU (lub poszczególne procesory vCPU) również tam, aby ścieżki danych po stronie hosta pozostały krótkie. W Gość ustawiam przynależność IRQ kart vNIC do tych vCPU, które po stronie hosta przypisaliśmy do fizycznych rdzeni. Obciążenia kontenerowe (cgroups/cpuset) zyskują na tym, gdy dozwolone procesory kontenerów pokrywają się z rdzeniami RX/TX hosta – w przeciwnym razie powstają niepotrzebne dostępy zdalne.

Pogłębienie analizy: rozpoznawanie SoftIRQ, NAPI i zatorów

Oprócz /proc/interrupts zaglądam do /proc/softirqs, aby sprawdzić, czy w kontekście ksoftirqd zamiast być realizowane bezpośrednio w procedurze obsługi IRQ – co wskazuje na utrzymujące się wysokie obciążenie. Z napi_defer_hard_irqs (W zależności od jądra) oraz poprzez odpowiednie przypisanie kolejek reguluję, jak intensywnie NAPI grupuje operacje. ethtool -S dostarcza mi statystyki dotyczące utraty pakietów, stanów zajętości i szybkości przesyłania pakietów dla poszczególnych kolejek; dzięki temu mogę wykryć niezrównoważone kolejki i odpowiednio dostosować afynicję lub indykację RSS.

# Szybki przegląd rozkładu SoftIRQ
cat /proc/softirqs | egrep 'NET_RX|NET_TX'

# Wyświetlanie statystyk sterownika/kolejki
ethtool -S ens192 | egrep -i 'rx|tx|drop|busy'

Praktyka: mapowanie dla 4 kolejek na jednym węźle

Typowa konfiguracja, z której często korzystam: karta sieciowa na węźle NUMA 0 z 4 kolejkami RSS. Unikam procesora CPU0 i przypisuję kolejki do procesorów CPU1–4. Odpowiednie procesy robocze serwera WWW lub proxy również umieszczam na procesorach 1–4, identycznie przypisuję XPS, a RPS pozostaje wyłączone. W ten sposób uzyskuję krótkie, spójne ścieżki w obu kierunkach.

IF=ens192
QUEUES=(181 182 183 184)   Przykładowe IRQ dla # (należy je wcześniej ustalić)
CPUS="1-4"

Ustawienie powinowactwa IRQ i XPS dla #
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

# Przypisanie procesów roboczych (systemd lub taskset)
# systemd: CPUAffinity=1 2 3 4
# Jednorazowo: taskset -c 1-4

Jeśli obciążenie będzie nadal rosło, zwiększę liczbę kolejek (ethtool -L) aż do liczby sensownych rdzeni węzła i rozdzielaj je sztywno według rozpoznawalnego wzorca (np. ID kolejki → ID rdzenia), aby przepływy nie „przemieszczały się“ w miarę upływu czasu.

Najlepsze praktyki dotyczące hostów wielordzeniowych

Odciążam CPU0, ponieważ często działają tam timery i wewnętrzne usługi jądra, które pod obciążeniem powodują zakłócenia. Dlatego też krytyczne sygnały IRQ przypisuję przede wszystkim do innych rdzeni, a na procesorze CPU0 pozostawiam tylko kilka, niekrytycznych źródeł. W systemach NUMA postępuję konsekwentnie i utrzymuję adaptery, sygnały IRQ, procesy oraz operacje dostępu do pamięci na tym samym Węzeł. W środowiskach o dużym obciążeniu oddzielam rdzenie wejścia/wyjścia od rdzeni aplikacji i w razie potrzeby je izoluję. Wszystkie zmiany monitoruję na bieżąco, a przydziały dostosowuję iteracyjnie.

Podejście oparte na mierzalnych wynikach: analiza, skrypty i plan awaryjny

Przed wprowadzeniem zmian dokumentuję aktualny stan za pomocą mpstat, htop, /proc/interrupts oraz pomiary opóźnień za pomocą iperf3. Konfiguruję skrypty, które automatycznie stosują ustawienia powinowactwa po ponownym uruchomieniu systemu lub przeładowaniu sterowników. Na wypadek konieczności cofnięcia zmian mam przygotowane neutralne maski, dzięki czemu w razie nieprawidłowego działania mogę natychmiast przywrócić poprzedni stan. W środowisku stagingowym testuję profile obciążenia, które są jak najbardziej zbliżone do mojego środowiska produkcyjnego, i powtarzam Pomiar po każdej zmianie. Dopiero wtedy trwale aktywuję profil na hoście docelowym.

Skuteczne omijanie typowych przeszkód

Zbyt szerokie maski rozdzielają pracę między zbyt wiele osób Procesory i spowalniają pamięć podręczną, podczas gdy zbyt wąskie maski powodują zatory w kolejkach. Pominięte specyficzne cechy NUMA powodują dostęp do pamięci zdalnej, co prowadzi do wahań czasów odpowiedzi. Twarde przypisanie (hard pinning) czasami koliduje z ustawieniami RPS/RFS, dlatego wyraźnie sprawdzam rozkład obciążenia SoftIRQ. Po aktualizacjach jądra lub zmianach sterowników ponownie weryfikuję wszystkie numery IRQ, ponieważ przypisania mogą ulec zmianie. Dzięki ostrożnym działaniom i jasnej Film dokumentalny zachowuję zdolność do działania.

Krótkie podsumowanie

Ukierunkowane przypisywanie sygnałów IRQ pozwala powiązać przerwania sieciowe z kilkoma odpowiednimi Rdzenie, zmniejsza obciążenie systemowe i stabilizuje czasy odpowiedzi. W tym celu dostosowuję IRQ i powinowactwo procesora, uwzględniam architekturę NUMA oraz sprawdzam efekty za pomocą serii pomiarów. Tam, gdzie wystarcza tryb automatyczny, pozwalam działać irqbalance, a krytyczne kolejki kieruję do stałych rdzeni. Dzięki RSS, RPS/RFS i dostosowanym parametrom jądra tuning osiąga swój pełny Efekt. Kto konsekwentnie zastosuje te kroki, uzyska zauważalnie wyższą wydajność sieciową na wielordzeniowych serwerach z systemem Linux.

Artykuły bieżące

Szafa serwerowa z wizualizacją połączeń Redis dla aplikacji PHP
Bazy danych

Pula połączeń Redis w PHP dla maksymalnej wydajności

Dowiedz się, jak wykorzystać pulę połączeń Redis w aplikacjach PHP, aby za pomocą phpredis zmniejszyć opóźnienia, zoptymalizować pamięć podręczną hostingu i trwale zwiększyć wydajność.