跳转到内容

CPU 绑核、NUMA 与大页内存 ​

这三项是网上「PVE 性能优化」清单里最常出现的参数,也是最容易被滥用的一批——它们不是默认关闭的坏东西,而是只在特定硬件和负载下才有收益,用错地方反而更慢。这篇讲清楚适用条件、怎么验证有没有用,以及改动的代价。

大部分家用/单路机器用不上这篇的大部分内容

消费级主板通常只有一颗 CPU、一个 NUMA 节点。如果下面「开始之前」里的检测命令显示你只有 1 个 NUMA 节点,那 NUMA 绑定和大页内存对你没有意义,直接跳到 CPU Type 选 host 就够了。

为什么需要它 ​

Linux 内核的默认调度器和 KVM 的内存管理,对绝大多数负载已经调得足够好。这三项工具解决的是更窄的问题:

  • CPU 亲和 (affinity):把虚拟机的 vCPU 线程固定在指定的物理核心上,减少调度器把线程换到别的核心带来的缓存失效。收益通常只在低延迟、可重复测量的负载(数据库基准、实时音视频处理、网络功能虚拟化)上才能感知。
  • NUMA:多路服务器上,内存是分区挂在每颗 CPU 上的,访问本地内存快,跨节点访问慢。让虚拟机的内存和 vCPU 落在同一个 NUMA 节点,能避免这部分延迟。单路机器没有这个问题。
  • 大页内存 (hugepages):用 1 GiB 或 2 MiB 的大页代替默认 4 KiB 页,减少 TLB miss,对大内存、访存密集的虚拟机(大内存数据库、DPDK 类负载)有帮助。代价是这部分内存会被锁定,其他用途用不了。

先测量,再决定要不要碰这一篇

用 top、iostat -x 2 5、vmstat 1、客户机内部的应用自带指标,确认瓶颈确实是 CPU 调度或内存访问延迟,而不是磁盘 IO、网络或者单纯核心数不够。查不出具体瓶颈就上绑核和大页,大概率只是把复杂度堆上去,却测不出差异。

开始之前 ​

宿主机 shell 执行,看你的硬件是否有多个 NUMA 节点:

sh
numactl --hardware
lscpu | grep -i numa
  • 输出里 available: 1 nodes 表示单 NUMA 节点,本文的 NUMA 绑定部分对你没用。
  • 输出多个节点,才需要继续往下看。

改动会中断正在运行的虚拟机

CPU 拓扑(sockets/cores)、NUMA 设置和大页内存都是虚拟机启动时由 QEMU 决定的硬件呈现,改完配置必须完整关机再开机(Reboot 不够,因为 QEMU 进程本身要重启)才能生效。大页内存还需要在宿主机启动参数里预留内存,要重启整台宿主机,期间该机器上所有虚拟机都会下线。安排到维护窗口,并提前确认要保留多少内存给宿主机和其他虚拟机——大页预留的内存在预留期间谁都用不了,包括没有配置大页的虚拟机。

回退方法:CPU/NUMA 设置改回原值后重启对应虚拟机即可;大页内存需要去掉内核启动参数里的 hugepagesz=/hugepages=,刷新引导配置后再重启宿主机一次。

操作步骤 ​

1. NUMA 感知(多路机器) ​

在虚拟机的 Hardware → CPU 里勾选 NUMA,并把 Sockets 设成和宿主机 NUMA 节点数一致(例如两路机器设 2)。PVE 会自动把虚拟机的内存和 vCPU 均分到对应的 NUMA 节点。

NUMA 还有一个隐藏用途

numa: 1 也是虚拟机支持内存/CPU 热插拔的前提条件,即使你只有一个 NUMA 节点,这类需要热插拔的场景也需要开启它。

需要精细控制时,可以在 /etc/pve/qemu-server/<vmid>.conf 里手写 numa0 定义单个 NUMA 节点的 CPU 范围、绑定的宿主机节点、内存大小和分配策略(preferred/bind/interleave),例如:

text
numa0: cpus=0-3,hostnodes=0,memory=8192,policy=bind

policy=bind 最严格(只能用指定节点的内存,内存不够会失败),preferred 更宽松(优先本地,不够就跨节点)。没有实测数据支撑时不要手写这一项,写错比不写更容易导致内存分配失败或者跨节点访问反而更多。

2. CPU 亲和(affinity) ​

sh
# 宿主机 shell,把 <vmid> 换成实际的虚拟机编号
qm set <vmid> --affinity 0-3

把该虚拟机的 vCPU 线程限制在宿主机的 0-3 号核心上。

affinity 不是安全边界,也不是免费的

官方文档明确说明:CPU 亲和不是安全特性,不能用它隔离互不信任的租户。它只是把线程钉在指定核心上,代价是:

  • 调度器失去了在这些核心和其他核心之间做负载均衡的自由,用不好反而更慢。
  • 需要你自己长期维护——加虚拟机、换硬件都要重新规划,核心数对不上时容易忘记更新。
  • I/O 相关的线程不受这个设置影响,只影响 vCPU 执行线程。

只在需要可重复的基准测试或者验证延迟敏感负载时使用,不要作为常规调优手段全局套用。

3. 大页内存(hugepages) ​

先在宿主机保留大页:

sh
# 查看当前引导方式(GRUB 还是 systemd-boot)
proxmox-boot-tool status
  • 用 systemd-boot(默认无 GRUB 的新装机器):编辑 /etc/kernel/cmdline,追加 hugepagesz=1G hugepages=<N> default_hugepagesz=1G(<N> 是要预留的 1 GiB 大页数量),然后执行 proxmox-boot-tool refresh。
  • 用 GRUB(传统 BIOS 或开了 Secure Boot):编辑 /etc/default/grub 的 GRUB_CMDLINE_LINUX_DEFAULT,加同样的参数,然后执行 update-grub。

改完重启宿主机才会生效。然后在虚拟机配置里指定大小:

text
hugepages: 1024

值是 1024(1 GiB 大页)、2(2 MiB 大页)或 any(优先 1 GiB,不行就退回 2 MiB)。

大页内存预留之后,不用也占着

宿主机启动时预留的大页内存,在没有虚拟机使用它的时候也不能被其他程序或普通虚拟机使用。预留 32 GiB 大页,宿主机能自由支配的内存就少 32 GiB,哪怕这个时间点用大页的虚拟机是关着的。

预留数量算错(比如预留后剩余内存不够跑其他服务),轻则其他虚拟机起不来,重则宿主机本身在启动阶段就资源紧张。先在一台不重要的测试机或维护窗口验证参数,再上生产。回退方式是去掉 hugepagesz=/hugepages= 参数、刷新引导配置、重启宿主机。

检查结果 ​

sh
# 宿主机:确认虚拟机的拓扑写进了配置
qm config <vmid> | grep -E 'numa|affinity|hugepages|sockets|cores'

# 宿主机:确认大页预留生效
cat /proc/meminfo | grep -i huge

# 宿主机:确认 vCPU 线程确实跑在指定核心上(PID 从 qm status --verbose 或 ps 里找)
ps -eLo pid,psr,comm | grep kvm

客户机内部:

sh
# 虚拟机内部,确认拓扑符合预期(仅当开了 NUMA 时才有意义)
numactl --hardware

真正的验收标准是重复跑一次此前用来发现瓶颈的基准测试或应用指标,对比改动前后的数据。数字没有可感知的改善,就把改动撤回,复杂度不该白白留着。

常见问题 ​

虚拟机改完配置起不来,报内存/大页相关错误。 大页内存要在系统启动早期、内存还没碎片化时一次性预留,运行中的系统再申请容易失败。确认 /proc/meminfo 里 HugePages_Free 足够,不够就调大预留量并重启宿主机。

开了 NUMA 之后应用反而变慢。 检查 Sockets 数是否真的等于宿主机 NUMA 节点数,以及虚拟机总内存/vCPU 是否能被节点数整除——分配不均会导致客户机操作系统看到一个奇怪的拓扑,某些应用(尤其是数据库)对此很敏感。

设置了 affinity 后其他虚拟机变卡。 检查有没有把太多虚拟机的 affinity 挤到同一小组核心上,导致这些核心过载而其他核心空闲。affinity 需要全局规划,不能只看单台虚拟机。

参考资料 ​

Proxmox VE 非官方中文使用指南,与 Proxmox Server Solutions GmbH 无隶属关系。