NUMA 架构设备的网卡中断绑核
为网卡中断绑核是一种常见的优化,在高并发场景下能够提升性能,并且更稳定。
-
缓存友好
默认的随机分配可能会导致不同 CPU 核心之间中断处理指令与数据的缓存被频繁覆盖。 如果为中断处理分配到独占的 CPU 核心,其指令与数据都将能长期存储在缓存中。
-
NUMA 友好
在一些场景下,我们可以将网卡中断与应用程序绑定在同一 NUMA 节点的各个核心上。 这样绑定就能够避免报文在中断与应用间传递时产生跨 NUMA 节点的访存。
irqbalance
为中断绑核前需要先关闭 irqbalance
服务,否则绑核配置可能会被覆盖:
systemctl stop irqbalance
多队列网卡通道配置
多队列网卡(比如 Intel 82575)通过哈希将报文分配到不同的硬队列上,并对 CPU 核心触发硬中断进行处理。
可以通过 ethtool
命令管理多队列网卡的通道配置。
通道由一个 IRQ(interrupt request)以及能触发该 IRQ 的若干队列组成。
这里的 IRQ 指的是硬中断。
ip=<ip>
# 通过 ip 查询网络接口
dev="$(ifconfig | grep -B 1 "${ip}" | head -n 1 | awk '{ print $1 }' | tr -d ':')"
dev="$(ip address | grep "${ip}" | awk '{ print $NF }')"
# 查询接口支持的最大通道数与当前配置
ethtool -l "${dev}"
irq_cnt=<irq-cnt>
# 配置网卡通道数
ethtool -L "${dev}" combined "${irq_cnt}"
中断号查询
可通过文件 /proc/interrupts
查询出每个 IRQ 的中断号、在各 CPU 上的触发次数、中断类型等。
我们可以通过网络接口名查询出各个通道的 IRQ 对应的中断号。
# 通过接口名查询网卡各通道的中断号
irq_list="$(grep "${dev}" /proc/interrupts | awk '{ print $1 }' | tr -d ':')"
# 统计一下中断数量是否与之前配置的通道数一致
echo "${irq_list}" | wc -w
中断绑核
IRQ 有一个属性 smp_affinity
,它指定了允许为该中断执行 ISR(interrupt service routine)的 CPU 核心。
smp_affinity
属性可用于将应用与中断绑定在同一核心或多个特定核心,从而提高应用的性能。
因为这能够允许中断线程与应用线程之间共享缓存。
smp_affinity
是一个十六进制掩码,标记为 1 的位表示其对应的 CPU 核心可用于执行 ISR。
smp_affinity_list
则是以十进制方式输出各个核心。
# 查询各中断的绑核配置
for irq in $irq_list; do
echo "${irq}"
cat /proc/irq/${irq}/smp_affinity
cat /proc/irq/${irq}/smp_affinity_list
echo
done
本文提供一些配置思路,实际场景中应当依据具体业务进行调整。 为每个 IRQ 绑定一个 CPU 核心,考虑两种绑核方式:
- 选择网卡同一 NUMA 节点上的核心,减少跨结点访存。
- 在每个节点上都选择相同数量的核心,使得负载均衡。
对于第一种绑核方式,需要先查出网卡同 NUMA 节点上的各个 CPU 核心:
# 查询网卡 PCI 地址
pci_addr="$(ethtool -i "${dev}" | grep bus-info | awk '{ print $2 }')"
# 查询网卡所属 NUMA 节点
lspci -vvvs "${pci_addr}" | grep NUMA
# 查询 NUMA 节点的 CPU 分布
numactl -H
明确需要绑定的 CPU 核心后,就可以通过写入 smp_affinity_list
来对 IRQ 进行绑核。
# 用空格分隔的 CPU 核心列表,需要与中断数量保持一致
cpu_list=<cpu-list>
for i in $(seq 1 "${irq_cnt}"); do
irq="$(echo ${irq_list} | awk "{ print \$$i }")"
cpu="$(echo ${cpu_list} | awk "{ print \$$i }")"
echo "IRQ: ${irq}, CPU: ${cpu}"
# 绑核
echo "${cpu}" | tee /proc/irq/${irq}/smp_affinity_list
done