中断亲和性概念:CPU亲和性、中断亲缘性与NUMA架构

好,咱们正式进入第二章。在动手配置之前,有些基础概念得先理清楚。说白了,中断亲和性就是解决一个问题——“谁该来处理这个中断?”

你想想看,一个网络包到了网卡,网卡发出中断信号。CPU A离得近,但正在忙;CPU B闲着,可它在另一个物理插槽上。选谁?选错了,性能就白白浪费了。

我刚开始接触这个领域时,也踩过坑。明明CPU占用率不高,但业务吞吐就是上不去。排查了半天,发现是中断被分到了一个跨NUMA的核上。从那以后,我养成了一个习惯:做性能调优,第一件事就是检查亲和性配置。

1. CPU亲和性:绑死你的进程

CPU亲和性,英文叫 CPU Affinity。它的核心思想很简单:把某个任务固定在某些CPU上执行。

为什么要这么干?因为CPU缓存是有温度的。一个进程在核0上跑,它的L1、L2缓存里全是它的数据。如果你放任它在核0到核3之间乱窜,每次切换都要重新加载缓存——这代价可不小。

关键数字:

  • L1缓存命中:约1-2纳秒
  • L1缓存未命中:约10-20纳秒
  • 跨CPU缓存迁移:数十到上百纳秒

在Linux里,CPU亲和性通过sched_setaffinity()系统调用来设置。用taskset工具可以快速绑定。

# 把进程PID 1234绑定到CPU 0和CPU 2上
taskset -p 0x5 1234

上面的0x5是什么意思?二进制是0101,从右往左看:第0位是1(核0),第2位是1(核2)。说白了,这就是一个位掩码。

我个人的习惯:

对性能敏感的应用程序,优先绑在连续的物理核心上,不要跨CCX(AMD)或cluster(ARM)。跨了,延迟就不可控了。

2. 中断亲缘性:中断也分家

中断亲缘性,Interrupt Affinity。它和CPU亲和性很像,但对象不同——这次绑的是硬件中断。

每个硬件设备都有自己的中断号(IRQ)。你可以告诉操作系统:这个IRQ的中断,只准在哪些CPU上触发。

在x86系统上,配置入口在/proc/irq/{IRQ_NUM}/smp_affinity这个文件里。写进去的,是一个十六进制的位掩码。

# 查看网卡(假设IRQ 77)的中断亲缘性
cat /proc/irq/77/smp_affinity
# 输出:00000000,00000000,00000000,0000000f
# 说明:默认所有CPU都能处理

# 把它绑到CPU 0-3上
echo "00000000,00000000,00000000,0000000f" > /proc/irq/77/smp_affinity

避坑指南:

我曾经见过有人直接把smp_affinity改成0x1,以为只绑一个核性能最好。结果呢?高吞吐场景下,那个核的中断处理满载,应用反而被饿死了。嗯,中断处理也得留点余量,别把鸡蛋放一个篮子里。

更现代一点的做法,是用irqbalance这个守护进程。它会自动帮你把中断分散到各个CPU上。但在NUMA场景下,它的策略往往不够精细——这时候就得手动调了。

3. NUMA架构:内存不是等距的

NUMA,Non-Uniform Memory Access,翻译过来叫“非统一内存访问”。说白了就是:CPU访问不同位置的内存,速度不一样。

在NUMA系统里,物理内存被划分成多个“节点”(Node)。每个节点挂在一组CPU上。

  • 本地访问:CPU访问自己所在节点的内存,延迟低,带宽高。
  • 远程访问:CPU访问另一个节点的内存,要通过QPI/UPI/Infinity Fabric总线,延迟高,带宽低。

典型延迟对比(高端服务器):

访问类型 延迟(ns) 带宽(GB/s)
本地DRAM ~80 ~50
远程DRAM(跨1跳) ~130 ~35
远程DRAM(跨2跳) ~180 ~25

瞧见没?跨节点访问,延迟几乎翻倍。如果你的中断处理程序在Node 0上跑,但它处理的数据缓冲区在Node 1的内存里——每次访问内存都得多等几十纳秒。积少成多,性能就崩了。

所以,NUMA架构下的中断亲和性配置,核心原则就一条:中断在哪,内存就分配在哪。

4. 三者关系:一张图说清楚

你可能会问:这三者到底是什么关系?我用一张图来总结。

中断亲和性概念架构 NUMA Node 0 CPU Core 0 CPU Core 1 本地内存(Local Memory) 访问延迟低 中断处理程序(ISR) CPU亲和性→绑在Core 0/1 NUMA Node 1 CPU Core 2 CPU Core 3 本地内存(Local Memory) 访问延迟低 中断处理程序(ISR) 中断亲缘性→绑在Core 2/3 远程访问 延迟高 中断请求(IRQ)

这张图表达了三个层次:

  1. NUMA节点划分:每个节点有自己的CPU核心和本地内存。
  2. CPU亲和性:中断处理程序被绑定到特定CPU核心上。
  3. 中断亲缘性:硬件中断请求被发送到特定NUMA节点内的CPU。

理想状态下,一个中断的处理流程应该是这样的:

  • 硬件设备在Node 0的PCIe总线上
  • 中断亲缘性配成Node 0的CPU
  • 中断处理程序的内存分配在Node 0的本地内存上
  • 应用程序也跑在Node 0上

这样一套组合拳下来,所有操作都在本地完成,没有跨节点开销。这是性能最优解。

我的经验法则:

在一台双路服务器上,如果你不确定怎么做,先把网卡中断全部绑在离网卡物理插槽最近的那个NUMA节点上。这通常就是最优方案。别问为什么,这是硬件设计决定的——最近的PCIe控制器就在那里。

本章小结

我们一口气讲了三个概念:

  • CPU亲和性:把进程/线程绑死到特定CPU,避免缓存抖动。
  • 中断亲缘性:控制硬件中断在哪些CPU上触发,避免中断处理跨NUMA。
  • NUMA架构:内存访问延迟不对称,核心原则是“本地访问优先”。