专用服务器的 NUMA 感知与 CPU 绑定
16 分钟阅读 - 2026年6月16日

如何检查 NUMA 拓扑结构,并将 Linux 工作负载分配到正确的核心和内存上。内容涵盖 numactl、taskset、systemd、BIOS 设置以及针对特定工作负载的策略。
专用服务器的 NUMA 感知与 CPU 绑定
在任何多插槽服务器上,进程的运行位置与其内存驻留位置是两个不同的问题,而让二者失调是导致性能浪费的最常见原因之一。 NUMA 感知与 CPU 固定正是解决这一问题的两大关键手段。本文将介绍 NUMA 的工作原理、如何在 Linux 上进行检测,以及如何为数据库、AI 训练和对延迟敏感的服务正确地固定工作负载。
NUMA 在多插槽服务器上的工作原理
NUMA(非统一内存访问)节点是一组通过专用内存控制器绑定到本地 RAM 块的 CPU 核心。在双插槽服务器上,通常有两个节点。 任何核心均可读取任意地址,但本地访问延迟约为 80 纳秒,而通过英特尔 UPI 或 AMD Infinity Fabric 进行跨插槽传输的延迟则在 130–150 纳秒左右。在插槽数量更多的大型系统中,最差情况下的节点延迟可能超过 250 纳秒。
带宽表现遵循相同规律。双插槽 Sapphire Rapids 系统在核心访问本地内存时可维持约 600 GB/s 的带宽,但插槽间链路的带宽仅为其一小部分,因此跨插槽传输很快就会成为瓶颈。 高核心数处理器使这一问题更加细化:英特尔的 Sub-NUMA 集群 (SNC) 和 AMD 的每插槽节点数 (NPS) 将每个插槽划分为多个 NUMA 域,因此一台“双插槽”机器可以轻松向 Linux 呈现四个或八个节点。
如果缺乏对 NUMA 的感知,Linux 调度器会毫不犹豫地在插槽之间迁移线程,而其工作集却仍保留在原始节点上。此后每次访问都变成了远程访问。其明显症状是 CPU 利用率高而实际吞吐量低,因为核心一直在等待内存响应。I/O 设备会使情况更加恶化。 GPU 或 NIC 连接到特定的 PCIe 根总线,该总线属于某个 NUMA 节点。如果为其提供数据的进程运行在另一个插槽上,每次 DMA 传输都必须穿越互连总线。
在 Linux 上检查 NUMA 拓扑
以下四种工具几乎涵盖了您所需的一切:
lscpu用于快速查看套接字和节点摘要。numactl --hardware用于查看节点内存总量及节点间距离矩阵。numastat用于查看每个进程的命中/未命中计数器。lstopo(来自 hwloc)用于缓存层次结构和 PCIe 设备局部性分析。
从 numactl --hardware。该文件列出了每个节点、其所属的核心和内存,以及距离矩阵。数值为10表示本地,20+表示远程。若在多插槽服务器上仅看到单个节点,说明您的BIOS启用了节点交错(Node Interleaving)并隐藏了拓扑结构,请先解决此问题(见下文)。
对于特定进程, numastat -p <PID> 会详细显示其内存的实际分配位置。有四个计数器值得关注:
numa_hit:在目标节点上分配的内存。该值应尽可能高。numa_miss:目标节点已满,分配溢出至其他节点。numa_foreign:其他节点尝试本地分配但未成功,表明存在内存压力。other_node: 在进程运行节点以外的节点上分配的页面。此处的数值过高是固定策略不佳的典型迹象。
对于 GPU 或 NIC 工作负载,请运行 lstopo-no-graphics 并查看每个 PCIe 设备连接到了哪个 NUMA 节点。如果驱动该设备的内核位于另一个节点上,这便是首要解决的问题。
CPU 绑定与内存策略
CPU 固定(或称 CPU 亲和性)将进程绑定到特定核心,从而防止调度器将其迁移。仅靠这一点还不够,因为 Linux 默认采用首次写入内存策略:内存页会被分配到第一个对其进行写入操作的节点上。 如果一个线程在被固定之前在错误的节点上启动,其内存就会保留在那里。您需要同时控制放置和分配。
有三种工具可处理常见情况:
| 工具 | 控制功能 | 适用场景 |
|---|---|---|
taskset | 仅限 CPU 核心 | 快速对现有进程进行一次性绑定 |
numactl | CPU 核心和内存 | 启动具有严格局部性的工作负载 |
| systemd | CPU 核心和内存,持久化 | 需要在重启后保持固定的服务 |
numactl 支持四种内存策略:
--membind=N:仅在节点 N 上分配,若已满则失败。--preferred=N: 优先使用节点 N,必要时回退到其他节点。--interleave=all:在节点间轮询分配,以实现带宽均衡。--localalloc:在运行该工作负载的 CPU 所在的节点上分配。
将工作负载固定到某个节点
首先,确定哪些核心属于目标节点:
numactl --hardware然后启动应用程序,使其在核心和内存上都绑定到该节点:
numactl --cpunodebind=0 --membind=0 ./your_application对于已运行的进程,使用 taskset:
taskset -cp 0-7 <PID>若要使其在重启后生效,请在 systemd 单元中进行设置:
[Service]
CPUAffinity=0-7
NUMAPolicy=bind
NUMAMask=0重新加载并重启:
sudo systemctl daemon-reload && sudo systemctl restart <service>当您手动绑定时,请关闭内核的自动负载均衡功能,以免其干扰您的资源分配:
sysctl -w kernel.numa_balancing=0将其添加到 /etc/sysctl.conf 中以实现持久化。随后通过 numastat -p <PID> 在几分钟的实际工作负载下进行验证。如果 other_node 始终保持在零附近,则说明固定配置已生效。
根据工作负载选择策略
正确的策略取决于您的工作负载是更受益于低延迟,还是更受益于所有节点间的总带宽。
| 工作负载 | 策略 | 原因 |
|---|---|---|
| 数据库(PostgreSQL、MySQL、SQL Server) | --cpunodebind + --membind | 大型共享缓冲区、对延迟敏感的查询路径 |
| 内存缓存(Redis、Memcached) | 单节点绑定 | 所有操作均访问内存,远程延迟会立即显现 |
| AI/ML 训练与推理 | 绑定至 GPU 的 NUMA 节点 | 避免张量传输跨越 PCIe 根节点 |
| 分析(Spark、Elasticsearch) | --interleave=all | 大型工作集需要跨所有节点的带宽 |
| 对延迟敏感的API,权衡取舍 | 严格的固定 + IRQ 亲和性 | 可预测性比峰值吞吐量更重要 |
| 网络密集型(RoCEv2、InfiniBand) | 将引脚绑定至网卡的 NUMA 节点,为中断分配专用核心 | 将中断处理保持在本地,避免干扰应用线程 |
针对 GPU 工作负载,请运行 lstopo 以确定 GPU 所在的 NUMA 节点,随后使用 numactl --cpunodebind=N --membind=N 针对该 N 启动训练或推理进程。这是在多插槽 GPU 服务器上最容易获得收益的方法之一,因为默认调度程序的放置通常是错误的。
对于横跨两个插槽的 HPC 和 MPI 工作负载,请使用 localalloc 将每个 rank 固定在单个节点上,而不是将所有任务交错分配。每个 rank 都将获得本地内存,并行处理将在 rank 级别进行。
一个实用提示:若将任务固定在单个节点上,请在该节点上预留 2–4 GB 的内存空间。若节点运行接近满载,将触发内存回收机制,这会消耗掉您原本试图节省的延迟。
需检查的 BIOS 和内核设置
工具输出的准确性取决于固件所呈现的拓扑结构。需确认以下几项设置:
- 节点交错(Node Interleaving):请将其禁用。启用该功能时,BIOS 会将所有内存呈现为单一的扁平化内存池,并完全向操作系统隐藏 NUMA 信息。
numactl --hardware若启用此功能,多插槽服务器上将仅显示一个节点。 - 子 NUMA 集群(Intel)或每插槽节点数(AMD):若需更精细的局部性,请在高核心数处理器上启用。重启后
lscpu重启后进行确认。 vm.zone_reclaim_mode:对于大多数生产服务器,请设置为 0。非零值会激进地回收本地内存而非远程分配,这可能导致有用的页面缓存被驱逐。kernel.numa_balancing:通用工作负载下保持启用,手动固定时关闭。自动平衡器迁移页面和线程的方式可能与您的策略冲突。
若您在裸机环境中进行 NUMA 调优(可控制 BIOS、内核参数及 IRQ 亲和性),则无需绕过虚拟机管理程序的抽象层即可应用上述所有设置。这正是此类工作在专用硬件上比在云虚拟机中更易实现的主要原因。
对于具有完全 root 访问权限的多插槽专用服务器,请参阅 FDC 的专用服务器。

优化 Linux 服务器工作负载的调整配置文件
如何为 GPU、数据库和高带宽 Linux 服务器选择、应用和定制经过调整的配置文件,并提供示例和 Ansible 部署技巧。
16 分钟阅读 - 2026年6月9日
VPS 的 Linux OOM 杀手调整:实用指南
12 分钟阅读 - 2026年6月8日