...

Аффинность IRQ в Linux на многопроцессорных системах: практическое руководство по оптимизации сетевых настроек

Я на практических примерах покажу, как с помощью IRQ-аффинности в многопроцессорных системах можно целенаправленно привязывать сетевые прерывания к ядрам процессора, снижать задержку и повышать пропускную способность. С помощью четких инструкций, примеров и таблицы я объясняю, как выбирать шестнадцатеричные маски, учитывать NUMA и правильно привязывать процессы.

Центральные пункты

  • Сродство IRQ целенаправленно направляет аппаратные прерывания на процессоры и снижает накладные расходы.
  • Аффинность процессора Фиксация обслуживания на тех же ядрах позволяет сохранять кэши локально.
  • NUMA Обратите внимание: адаптеры и ядра должны относиться к одному и тому же узлу.
  • irqbalance взвесить все «за» и «против»: автоматическое распределение или точная ручная настройка.
  • Мониторинг а итеративная настройка обеспечивает стабильные задержки.

Понимание аффинности IRQ: основы и последствия

На хостах под управлением Linux решают проблемы с входящими пакетами, дисковым вводом-выводом и таймерами IRQs которые ядро распределяет между ядрами процессора. Я определяю через файлы /proc/irq//smp_affinity и .../smp_affinity_list, какие процессоры могут обращаться к данному источнику. Широкая стандартная маска на первый взгляд кажется гибкой, однако при высокой нагрузке она приводит к промахам кэша, затратным сменам контекста и разбросанным программным прерываниям (SoftIRQ) по многим Процессоры. Я привязываю критические очереди отдельных сетевых карт к заданным ядрам, снижаю нагрузку на «горячие точки» и сокращаю пути передачи данных. Такое управление заметно повышает стабильность, когда одновременно активны много потоков.

Настройка IRQ и аффинности процессора: хранение данных локально

Мне удалось подключить IRQ-Привязка очередей NIC к процессорам, к которым относятся соответствующие рабочие процессы. Для этого я привязываю потоки веб-сервера или прокси с помощью набор задач или CPUAffinity= в systemd на те ядра, которые также обрабатывают прерывания RX/TX. Благодаря этому строки кэша остаются локальными, и я свожу межпроцессорную коммуникацию к минимуму, что Латентность выравнивает. Именно API-бэкэнды, сервисы реального времени и виртуализированные стеки извлекают выгоду из этой согласованности. Я тестирую связь под производственной нагрузкой до тех пор, пока потоки, SoftIRQ и пользовательское пространство не начнут четко согласовываться друг с другом.

Автоматика против точной настройки: как правильно использовать irqbalance

Служба irqbalance автоматически распределяет прерывания по доступным ядрам, что хорошо работает на универсальных серверах. Однако в сетевых конфигурациях с высокой нагрузкой такое распределение снижает локальность кэша и затрудняет целенаправленную привязку. Я ограничиваю работу irqbalance или выборочно отключаю его, если определённым очередям требуются фиксированные ядра. Для понимания основ и подбора подходящих профилей мне помогает это руководство по Настройка irqbalance. В результате система автоматически управляет некритическими IRQ, а я вручную назначаю чувствительные IRQ.

Шаг: Отображение соответствующих IRQ

Начну с обзора /proc/interrupts и отфильтровать по названию устройства, например ens192, eno1 или eth0. Современные адаптеры создают несколько очередей RX и TX, поэтому я выделяю группу номеров IRQ, привязанных к одному и тому же сетевому адаптеру. Я обращаю внимание на значения счетчиков, чтобы быстро выявлять «горячие точки» и в первую очередь связывать сильно загруженные очереди. Я регулярно проверяю этот показатель во время нагрузочных тестов, чтобы обеспечить стабильность распределения. Кроме того, я проверяю названия драйверов, поскольку они дают подсказки о поддержке RSS и отгрузке.

# Просмотр всех прерываний
cat /proc/interrupts

# Отображение только строк, относящихся к сетевым картам (пример: ens192)
grep -i ens192 /proc/interrupts

Эффективное использование топологии NUMA

На хостах с несколькими узлами я предпочитаю переносить IRQ на ядра тех, NUMA-узел, к которому физически подключена сетевая карта. Я проверяю это с помощью lscpu и numactl --hardware и отмечаю подходящие наборы процессоров для последующего формирования масок. Процессы, использующие эти сетевые пути, я также привязываю к тому же узлу и с помощью политик использования памяти обеспечиваю локальное Память-присвоения. Таким образом я избегаю дорогостоящих удаленных обращений через каналы QPI/UPI. Такая дисциплина быстро приносит ощутимые преимущества в тестах на задержку.

Как правильно выбирать битовые маски: шестнадцатеричная логика вкратце

Файл smp_affinity поддерживает шестнадцатеричные битовые маски, которые напрямую сопоставляются с идентификаторами ядер и охватывают даже крупные системы. Я часто начинаю с простых шаблонов: CPU0 — 0x1, CPU1 — 0x2, CPU2 — 0x4, CPU3 — 0x8 и т. д., тогда как 0xF охватывает ядра 0–3. На машинах с большим количеством ядер я записываю несколько 32-битных блоков, разделенных запятыми, чтобы Маска точно отображает все идентификаторы. Эта сводка помогает мне производить правильное сопоставление и избегать непреднамеренных переносов. Следующую таблицу я часто использую в качестве памятки.

Номер процессора Бит (двоичный) Маска Hex Подсказка
0 …0001 0x1 CPU0 часто снимать нагрузку и использовать с умеренной интенсивностью.
1 …0010 0x2 IRQ на CPU1 управлять.
2 …0100 0x4 Подключить IRQ к CPU2.
3 …1000 0x8 Подключить IRQ к CPU3.
0–3 …1111 0xF Если включить все четыре ядра, задержка часто немного увеличивается.
0–7 11111111 0xFF Широкое распределение, страдает локальность кэша.

Настройка IRQ-аффинности: как привязать очереди к ядрам

После того как я определил IRQ-очереди сетевой карты, я распределяю их по выделенным Ядра такие как 1, 2 и 3, чтобы обеспечить плавное масштабирование параллельной обработки. Таким образом, каждая очередь RX/TX остается привязанной к своему ядру, что позволяет избежать перекрестных помех и обеспечить согласованность работы SoftIRQ. В процессе доработки я сравниваю пропускную способность и задержку, пока распределение не станет надежным. Для более глубокого понимания мне помогает краткий Практическое руководство с различными вариантами в зависимости от адаптера. Я намеренно запускаю эти команды в окнах технического обслуживания и фиксирую их с помощью скрипта загрузки.

Пример #: привязка трёх IRQ-очередей к процессорам 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)

Процессорное фиксирование: размещение сервисов на одних и тех же ядрах

Я привязываю рабочие процессы приложения к тем Процессоры, которые обрабатывают соответствующие IRQ, чтобы пути передачи данных оставались короткими. В systemd я использую CPUAffinity=1 2 3 или запустите один раз с помощью taskset -c 1-3. Для серверов с несколькими рабочими процессами я назначаю фиксированные наборы ядер для каждой группы рабочих процессов, чтобы избежать конкуренции. Такая привязка обеспечивает стабильно более низкие Задержки, поскольку кэш-память процессора остается заполненной. После внесения изменений я проверяю потоки, сокеты и SoftIRQ с помощью htop, ss и perf.

Настройка сети: связь между RSS, RPS/RFS и параметрами ядра

Многие сетевые карты передают пакеты посредством RSS о очередях, которые я затем привязываю к ядрам с помощью IRQ-аффинности. Подробности и принципы действия я изложу в разделе Масштабирование на стороне приема в компактном виде. Кроме того, я управляю RPS/RFS таким образом, чтобы SoftIRQ не противоречили жесткой логике фиксации выводов. Параллельно с этим я настраиваю буферы через net.core.rmem_max и net.core.wmem_max и проверьте такие опции TCP, как tcp_timestamps. Эти компоненты способствуют достижению одной и той же цели: низкой задержки при высокой Скорость передачи данных.

Правильное толкование MSI‑X и структуры очереди

Современные сетевые адаптеры используют MSI‑X и создают отдельные IRQ для каждой очереди RX/TX. Сначала я проверяю, сколько очередей и каналов в данный момент активировано драйвером, и подстраиваю это под бюджет ядер соответствующего узла NUMA. Таким образом я предотвращаю ситуацию, когда слишком много очередей сосредотачивается на слишком малом количестве ядер или, наоборот, пропускная способность остается неиспользованной.

#: проверка и настройка количества очередей и каналов
ethtool -l ens192 # текущие ограничения (RX/TX/combined)
ethtool -L ens192 combined 4  #, например, активировать 4 очереди

# Проверка настроек RSS-Indirection и хэша
ethtool -x ens192 # Отобразить таблицу Indirection и хеш-ключ

Многие драйверы присваивают IRQ-кодам понятные названия (например,. ens192-TxRx-0). Я поддерживаю согласованность таблицы Indirection с распределением ядра, чтобы потоки стабильно попадали в „свои“ очереди. Если распределение аппаратных ресурсов отличается от этого, возникают ненужные перемещения SoftIRQ.

Грамотное использование RPS/RFS и XPS

RPS/RFS может распределять пакеты программным способом между ядрами — это хорошо для сетевых карт без большого количества очередей, но контрпродуктивно, если я уже использую точную привязку с помощью RSS и IRQ-Affinity. Поэтому я сознательно принимаю решение: либо жесткая привязка с помощью RSS+IRQ-Affinity, либо RPS выключено, либо небольшое количество аппаратных очередей и Целенаправленное применение RPS. Кроме того, я настраиваю XPS для направления TX, чтобы исходящие пакеты отправлялись с „нужных“ ядер.

IF=ens192

# Полная отключение RPS (при корректной привязке IRQ через 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

# Альтернативный вариант: выборочная активация RPS (пример: процессоры 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
# Лучше: использовать синтаксис, аналогичный 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

Установить значение # XPS в соответствии с выбранными рабочими процессорами (TX-пути)
for q in /sys/class/net/$IF/queues/tx-*; do echo 1-3 > "$q"/xps_cpus; done

Важна согласованность: ядро RX‑IRQ, нагрузка ksoftirqd и рабочий поток должны располагаться на одном ядре (или паре ядер). Это позволяет устранить многие эффекты «кросс-кор-баунс».

Учёт SMT/Hyper-Threading и пар ядер

В системах с SMT часто целесообразно зарезервировать одно физическое ядро для RX‑IRK и разместить соответствующий рабочий процесс на Дочерний поток объединить — или сознательно разделить, если рабочая нагрузка требует интенсивных вычислений. Я определяю пары потоков на основе топологии, а затем принимаю четкое решение вместо случайного распределения.

Определение пар "# Sibling"
for c in /sys/devices/system/cpu/cpu*/topology/thread_siblings_list; do
  echo "$(basename "$(dirname "$c")") : $(cat "$c")"
done

Если распределить RX-IRQ и рабочие процессы пользовательского пространства на одном физическом ядре (разные SMT-потоки), то обеспечивается хорошая локальность L1/L2, но при нагрузках, ограниченных производительностью ЦП, могут возникать узкие места. В качестве альтернативы я распределяю IRQ на ядро X, а рабочий процесс — на ядро Y в рамках одного и того же узла NUMA, чтобы обеспечить настоящий параллелизм. Оба варианта я тестирую методом A/B-тестирования и выбираю тот, который обеспечивает более стабильную задержку.

Использование smp_affinity_list, effective_affinity и Defaults

Помимо шестнадцатеричных масок, я люблю писать в smp_affinity_list, поскольку это позволяет охватить такие области, как 1-3,6,8-9 удобно установить. Для проверки я проверяю effective_affinity соответственно список_эффективных_аффинностей, поскольку ядро или драйверы могут исключать определенные процессорные ядра (например, ядра, отключенные от работы, или „управляемые прерывания“).

# Присвоение в удобочитаемом виде
echo 1-3 > /proc/irq/181/smp_affinity_list

# Проверка эффективной привязки
cat /proc/irq/181/effective_affinity_list

Чтобы новые IRQ или те, которые появились после перезагрузки драйверов, не распределялись слишком широко, я при необходимости устанавливаю /proc/irq/default_smp_affinity до разумного базового значения (например, все ядра соответствующего NUMA-узла, но без CPU0). Затем я целенаправленно переопределяю отдельные критические IRQ.

Обеспечение сохранности данных после перезагрузки и обновления страницы

Настройки Affinity являются эфемерными. Я сохраняю их с помощью модуля systemd-oneshot, который запускается после достижения цели инициализации сети, или с помощью небольшого скрипта, который динамически определяет и сопоставляет списки IRQ. Таким образом, сопоставления сохраняются даже после обновлений ядра и сброса соединений.

# /usr/local/sbin/net-irq-pin.sh (пример)
#!/bin/bash
set -euo pipefail
IF=${1:-ens192}
CPUS="1-3"   Целевые процессоры # (выбирать NUMA-согласованные)
for irq in $(grep -i "$IF" /proc/interrupts | awk '{print $1}' | tr -d ':'); do
  echo "$ CPU" > /proc/irq/$irq/smp_affinity_list || true
done

# Модуль systemd (в общих чертах)
# /etc/systemd/system/net-irq-pin.service
[Unit]
Description=Привязка IRQ сетевых карт
After=network-online.target

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

[Install]
WantedBy=multi-user.target

Важно: я запускаю скрипт заново, если драйвер был перезагружен или количество очередей изменилось, так как в этом случае номера IRQ сдвигаются.

Виртуализация и контейнеры: комплексное подход к хосту и гостю

В средах KVM я монтирую на Хозяин физические IRQ-сигналы сетевых карт на ядра соответствующего NUMA-узла. Параллельно я подключаю vhost-net‑потоки и процесс QEMU (или отдельные виртуальные процессоры) также размещаются там, чтобы пути передачи данных со стороны хоста оставались короткими. В Гость Я настраиваю IRQ-аффинность виртуальных сетевых карт (vNIC) на те виртуальные процессоры (vCPU), которые я сопоставил на стороне хоста с физическими ядрами. Контейнерные рабочие нагрузки (cgroups/cpuset) получают преимущество, если разрешённые процессоры контейнеров пересекаются с ядрами RX/TX хоста — в противном случае возникают излишние удалённые обращения.

Углубленный анализ: SoftIRQ, NAPI и обнаружение заторов

Кроме того, /proc/interrupts я заглядываю в /proc/softirqs, чтобы понять, много ли работы в контексте ksoftirqd вместо того, чтобы выполняться непосредственно в обработчике IRQ — это свидетельствует о постоянно высокой нагрузке. С помощью napi_defer_hard_irqs (В зависимости от ядра) и с помощью правильного распределения очередей я регулирую, насколько активно NAPI выполняет пакетную обработку. ethtool -S предоставляет мне статистику по каждому очереди относительно потерь, состояний «занято» и скорости передачи пакетов; благодаря этому я выявляю несбалансированные очереди и соответствующим образом корректирую аффинность или RSS-индирекцию.

# Краткий обзор распределения SoftIRQ
cat /proc/softirqs | egrep 'NET_RX|NET_TX'

# Просмотр статистики драйверов и очередей
ethtool -S ens192 | egrep -i 'rx|tx|drop|busy'

Практика: распределение 4 очередей по одному узлу

Типичная конфигурация, которую я часто использую: сетевая карта на NUMA-узле 0 с 4 очередями RSS. Я избегаю использования CPU0 и привязываю очереди к CPU1–4. Соответствующие веб- или прокси-рабочие процессы я также размещаю на 1–4, XPS назначаю аналогичным образом, а RPS оставляю отключенным. Таким образом я получаю короткие и стабильные пути в обоих направлениях.

IF=ens192
QUEUES=(181 182 183 184)   # Примеры IRQ (определить заранее)
CPUS="1-4"

Установить аффинность IRQ и XPS для #
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

# Привязка рабочих процессов (systemd или taskset)
# systemd: CPUAffinity=1 2 3 4
# Однократно: taskset -c 1-4

Если нагрузка будет продолжать расти, я увеличу количество очередей (ethtool -L) до количества целесообразных ядер узла и распределяйте их строго по узнаваемому шаблону (например, ID очереди → ID ядра), чтобы потоки не „перемещались“ с течением времени.

Передовые методы для многоядерных хостов

Я снимаю нагрузку CPU0, поскольку там часто запущены таймеры и внутренние службы ядра, которые при высокой нагрузке создают помехи. Поэтому я предпочитаю назначать критические IRQ на другие ядра, а на CPU0 оставляю лишь несколько некритических источников. На системах NUMA я следую этому принципу последовательно и размещаю адаптеры, IRQ, процессы и обращения к памяти на одном и том же Узел. В условиях высокой нагрузки я разделяю ядра ввода-вывода и ядра приложений и, при необходимости, изолирую их. Все изменения я сопровождаю постоянным мониторингом и итеративно корректирую распределение ресурсов.

Подход, основанный на измеримых показателях: анализ, сценарии и план действий на случай непредвиденных обстоятельств

Перед внесением изменений я фиксирую текущее состояние с помощью mpstat, htop, /proc/interrupts и измерения задержки с помощью iperf3. Я настраиваю скрипты, которые автоматически применяют настройки аффинности после перезагрузки или перезагрузки драйверов. На случай необходимости отката я готовлю нейтральные шаблоны, чтобы при возникновении сбоев сразу же вернуться к исходному состоянию. В тестовой среде я тестирую профили нагрузки, максимально приближенные к моей производственной среде, и повторяю Измерение после каждой настройки. Только после этого я окончательно активирую профиль на целевом хосте.

Обходите часто встречающиеся камни преткновения

Слишком широкие маски распределяют нагрузку между слишком большим количеством Процессоры и замедляют работу кэшей, в то время как слишком узкие маски перегружают очереди. Неучтенные особенности NUMA приводят к обращениям к удаленной памяти, что вызывает колебания времени отклика. Жёсткая привязка иногда вступает в конфликт с настройками RPS/RFS, поэтому я явно проверяю распределение нагрузки SoftIRQ. После обновлений ядра или смены драйверов я заново проверяю все номера IRQ, поскольку сопоставления могут измениться. Осторожные действия и чёткое Документальный фильм я сохраняю способность действовать.

Краткое резюме

Целенаправленная фиксация IRQ позволяет привязать сетевые прерывания к нескольким подходящим Ядра, снижает накладные расходы и стабилизирует время отклика. Для этого я настраиваю IRQ и аффинность процессора, учитываю NUMA и проверяю эффективность с помощью серий измерений. Там, где достаточно автоматики, я позволяю работать irqbalance, а критические очереди направляю на фиксированные ядра. С помощью RSS, RPS/RFS и настроенных параметров ядра настройка раскрывает весь свой потенциал. Эффект. Те, кто дисциплинированно выполняет эти шаги, смогут значительно повысить сетевую производительность на многоядерных серверах под управлением Linux.

Текущие статьи

Многопроцессорная система с серверным оборудованием под управлением Linux и сетевой картой для настройки аффинности IRQ
Серверы и виртуальные машины

Аффинность IRQ в Linux на многопроцессорных системах: практическое руководство по оптимизации сетевых настроек

Узнайте, как целенаправленно использовать афинность IRQ в Linux на многопроцессорных системах, чтобы с помощью ключевого слова irq affinity оптимизировать настройку сети и нагрузку на ЦП.

Серверная стойка с визуализированными подключениями к Redis для PHP-приложений
Базы данных

Использование пула соединений Redis в PHP для обеспечения максимальной производительности

Узнайте, как использовать пул соединений Redis в приложениях на PHP, чтобы с помощью phpredis сократить задержки, оптимизировать кэширование хостинга и устойчиво повысить производительность.

Фотореалистичное изображение центра обработки данных с символом «безтактового ядра» и стабильной загрузкой процессора
Серверы и виртуальные машины

Объяснение режима «Tickless» в планировщике ядра: преимущества, риски и настройка

«Tickless»-ядро: простое объяснение. Преимущества, риски и настройка ядра для серверов, систем высокопроизводительных вычислений (HPC) и систем с низкой задержкой.