Show Table of Contents
8.4. libvirt NUMA 调试
通常,NUMA 系统通过将客机大小限制在单一 NUMA 节点的资源数量实现最佳性能。应当避免 NUMA 节点中不必要的分散资源。
使用
numastat
工具对进程和操作系统的每个 NUMA 节点内存统计进行查看。
在以下示范中,
numastat
工具显示了 NUMA 节点中的四种非优化内存排列的虚拟机:
# numastat -c qemu-kvm
Per-node process memory usage (in MBs)
PID Node 0 Node 1 Node 2 Node 3 Node 4 Node 5 Node 6 Node 7 Total
--------------- ------ ------ ------ ------ ------ ------ ------ ------ -----
51722 (qemu-kvm) 68 16 357 6936 2 3 147 598 8128
51747 (qemu-kvm) 245 11 5 18 5172 2532 1 92 8076
53736 (qemu-kvm) 62 432 1661 506 4851 136 22 445 8116
53773 (qemu-kvm) 1393 3 1 2 12 0 0 6702 8114
--------------- ------ ------ ------ ------ ------ ------ ------ ------ -----
Total 1769 463 2024 7462 10037 2672 169 7837 32434
运行
numad
,使客机 CPU 和内存资源自动对齐。
接下来再次运行
numastat -c qemu-kvm
,以查看 numad
的运行结果。以下输出显示了资源已对齐:
# numastat -c qemu-kvm
Per-node process memory usage (in MBs)
PID Node 0 Node 1 Node 2 Node 3 Node 4 Node 5 Node 6 Node 7 Total
--------------- ------ ------ ------ ------ ------ ------ ------ ------ -----
51747 (qemu-kvm) 0 0 7 0 8072 0 1 0 8080
53736 (qemu-kvm) 0 0 7 0 0 0 8113 0 8120
53773 (qemu-kvm) 0 0 7 0 0 0 1 8110 8118
59065 (qemu-kvm) 0 0 8050 0 0 0 0 0 8051
--------------- ------ ------ ------ ------ ------ ------ ------ ------ -----
Total 0 0 8072 0 8072 0 8114 8110 32368
注意
同时运行
numastat
和 -c
可提供简洁的输出;添加 -m
可将每个节点上系统范围内的内存信息添加到输出。更多信息请参见 numastat
手册页。
8.4.1. NUMA 虚拟 CPU 钉选
虚拟 CPU 钉选为裸机系统中的任务钉选提供了相似的优点。由于虚拟 CPU 在主机操作系统中作为用户空间任务运行,钉选将提高缓存效率。例如,当所有虚拟 CPU 线程都在同一个物理 socket 中运行时,将会共享 L3 缓存域。
合并虚拟 CPU 钉选和
numatune
可以避免 NUMA 缺失。NUMA 缺失会对性能带来显著影响,通常会有至少 10% 或更多的性能损失。虚拟 CPU 钉选和 numatune
应该同时配置。
当虚拟机在执行存储或网络 I/O 任务时,最好将所有的虚拟 CPU 和内存固定至同一个连接到 I/O 适配器的物理 socket。
注意
lstopo 工具可以被用作使 NUMA 拓扑可视化。同时还可以用作验证虚拟 CPU 在同一个物理插槽中的内核绑定。更多信息请参照知识库文章,链接如下:lstopo: https://access.redhat.com/site/solutions/62879。
重要
在虚拟 CPU 的数量远多于物理内核时,钉选将导致复杂性增加。
以下示例 XML 配置具有固定在物理 CPU 0-7 上的域过程。虚拟线程固定在其自身的 cpuset 上。例如,虚拟 CPU0 被固定在 物理 CPU 0 上,虚拟 CPU1 被固定在物理 CPU 1 上等等:
<vcpu cpuset='0-7'>8</vcpu> <cputune> <vcpupin vcpu='0' cpuset='0'/> <vcpupin vcpu='1' cpuset='1'/> <vcpupin vcpu='2' cpuset='2'/> <vcpupin vcpu='3' cpuset='3'/> <vcpupin vcpu='4' cpuset='4'/> <vcpupin vcpu='5' cpuset='5'/> <vcpupin vcpu='6' cpuset='6'/> <vcpupin vcpu='7' cpuset='7'/> </cputune>
虚拟 CPU 与 vcpupin 标签之间有直接的联系。如果 vcpupin 选项不是确定的,那么属性值会被自动指定并会从上一级虚拟 CPU 标签选项中继承。以下配置显示了因为 vcpu 5 丢失的
<vcpupin>
。进而,vCPU5 将会被固定在物理 CPU 0-7 上,正如上一级标签 <vcpu>
中指定的那样:
<vcpu cpuset='0-7'>8</vcpu> <cputune> <vcpupin vcpu='0' cpuset='0'/> <vcpupin vcpu='1' cpuset='1'/> <vcpupin vcpu='2' cpuset='2'/> <vcpupin vcpu='3' cpuset='3'/> <vcpupin vcpu='4' cpuset='4'/> <vcpupin vcpu='6' cpuset='6'/> <vcpupin vcpu='7' cpuset='7'/> </cputune>
重要
<vcpupin>
、<numatune>
以及 <emulatorpin>
应该被一同配置,从而实现优化、确定的性能。更多关于 <numatune>
标签的信息,请查看第 8.4.2 节 “域进程”。更多关于 <emulatorpin>
标签的信息,请参照第 8.4.4 节 “使用 emulatorpin”。
8.4.2. 域进程
如 Red Hat Enterprise Linux 所提供的那样,libvirt 使用 libnuma 来为域进程设定内存绑定政策。这些政策的节点集可以作为 static(在域 XML 中指定),或 auto (通过询问 numad 进行配置)进行配置。关于如何配置这些
<numatune>
标签内部的示例,请参考以下 XML 配置:
<numatune> <memory mode='strict' placement='auto'/> </numatune>
<numatune> <memory mode='strict' nodeset='0,2-3'/> </numatune>
libvirt 使用 sched_setaffinity(2) 来为域进程设定 CPU 绑定政策。cpuset 选项既可以是 static (静态;在域 XML 中指定),也可以是 auto (自动;通过询问 numad 进行配置)。关于如何配置这些
<vcpu>
标签内部的示例,请参考以下 XML 配置:
<vcpu placement='auto'>8</vcpu>
<vcpu placement='static' cpuset='0-10,ˆ5'>8</vcpu>
在
<vcpu>
所使用的放置模式和 <numatune>
之间有隐式的继承规则:
<numatune>
的放置模式默认为与<vcpu>
相同的放置模式,当<nodeset>
被指定时,则被默认为 static。- 同样,
<vcpu>
的放置模式默认为与<numatune>
相同的放置模式,当<cpuset>
被指定时,则被默认为 static。
这意味着为了域进程而进行的 CPU 调试和内存调试可以被单独指定和定义,但是它们也可以在依赖其他放置模式的情况下进行配置。
也有一种可能,即通过 numad 来配置您的系统,可以启动选定数量的虚拟 CPU,并且在启动时不需要固定到所有的虚拟 CPU。
例如,通过 32 个虚拟 CPU 使仅有的 8 个虚拟 CPU 在一个系统中启动,配置 XML 方式与如下方式类似:
<vcpu placement='auto' current='8'>32</vcpu>
注意
更多关于虚拟 CPU 和 numatune 的信息请参照以下网址: http://libvirt.org/formatdomain.html#elementsCPUAllocation 和 http://libvirt.org/formatdomain.html#elementsNUMATuning
8.4.3. 域虚拟 CPU 线程
除了调试域进程,libvirt 还允许 XML 配置中每个虚拟 CPU 线程的钉选策略设置。设置
<cputune>
标签内部每个虚拟 CPU 线程的钉选策略:
<cputune> <vcpupin vcpu="0" cpuset="1-4,ˆ2"/> <vcpupin vcpu="1" cpuset="0,1"/> <vcpupin vcpu="2" cpuset="2,3"/> <vcpupin vcpu="3" cpuset="0,4"/> </cputune>
在这个标签中,libvirt 可以使用 cgroup 或 sched_setaffinity(2),使虚拟 CPU 线程固定在指定的 cpuset 中。
注意
更多关于
<cputune>
的信息,请参照以下网址: http://libvirt.org/formatdomain.html#elementsCPUTuning
8.4.4. 使用 emulatorpin
调试域进程钉选策略的另一个方法是使用
<cputune>
中的 <emulatorpin>
标签。
<emulatorpin>
标签指定了 emulator(一个域的子集,不包括虚拟 CPU)将被固定的主机物理 CPU。<emulatorpin>
标签提供了一个将精确关联设定成仿真线程进程的方法。因此,vhost 线程在同一个物理 CPU 和内存子集中运行,从而可以从缓存位置获益。例如:
<cputune> <emulatorpin cpuset="1-3"/> </cputune>
注意
在 Red Hat Enterprise Linux 7 中,默认启用自动化 NUMA 平衡。随着 vhost-net 仿真线程能够更加可靠地跟随虚拟 CPU 任务,自动化 NUMA 平衡减少了手动调试
<emulatorpin>
的需要。关于自动化 NUMA 平衡的更多信息,请参照第 8.3 节 “自动化 NUMA 平衡”。
8.4.5. 用 virsh 调试 vcpu CPU 钉选
重要
这些只是示例命令。您需要依据环境替换属性值。
以下示例
virsh
命令会将 ID 为 1 的虚拟 CPU 线程(rhel7)固定到物理 CPU 2。
% virsh vcpupin rhel7 1 2
您也可以通过
virsh
命令获得当前虚拟 CPU 的钉选配置。例如:
% virsh vcpupin rhel7
8.4.6. 用 virsh 调试域进程 CPU 钉选
重要
这些只是示例命令。您需要依据环境替换属性值。
emulatorpin
选项将 CPU 关联设置应用到与每个域进程关联的线程。为了完全固定,您必须给每个客机同时使用 virsh vcpupin
(如之前所展示的)和 virsh emulatorpin
。例如:
% virsh emulatorpin rhel7 3-4
8.4.7. 用 virsh 调试域进程内存策略
域进程内存可以动态调试。请参考以下示例指令:
% virsh numatune rhel7 --nodeset 0-10
关于这些指令的更多示例,请参见
virsh
手册页。
8.4.8. 客机 NUMA 拓扑
客机 NUMA 拓扑可以通过使用
<cpu>
标签中的 <numa>
标签在客机虚拟机的 XML 中进行指定。请参照以下示例,并替换相应的属性值:
<cpu> ... <numa> <cell cpus='0-3' memory='512000'/> <cell cpus='4-7' memory='512000'/> </numa> ... </cpu>
每个
<cell>
元素指定一个 NUMA cell 或者 NUMA 节点。cpus
指定了 CPU 或者部分节点的系列 CPU,memory
以千位字节(1,024字节一块)指定了节点内存。从 0 开始,cellid
或 nodeid
以递增的次序被指定到每个 cell 或节点。
8.4.9. 向多个客机 NUMA 节点指定主机大页面
在 Red Hat Enterprise Linux 7.1 中,主机中的大页面可以被分配到多个客机的 NUMA 节点。这一过程可以优化内存性能,因为客机 NUMA 节点可以被移动到主机 NUMA 节点需要的位置上,同时客机可以继续使用主机指定的大页面。
在配置客机 NUMA 节点拓扑后(详情参考第 8.4.8 节 “客机 NUMA 拓扑”),在客机 XML 的
<memoryBacking>
元素中指定大页面的大小和客机 NUMA 节点集。page size
和 unit
代表主机大页面的大小。nodeset
指定了大页面被分配的客机 NUMA 节点(或若干节点)。
在以下示例中,客机 NUMA 节点 0-5(不包括 NUMA 节点 4)将会使用 1 GB 的大页面,客机 NUMA 节点 4 将使用 2 MB 的大页面,无论客机 NUMA 节点在主机的任何位置。为了在客机中使用 1 GB 的大页面,主机必须先启动 1 GB 大页面;启动 1 GB 大页面的方法请参考第 7.3.3 节 “大页面和透明大页面(THP)”
<memoryBacking> <hugepages/> <page size="1" unit="G" nodeset="0-3,5"/> <page size="2" unit="M" nodeset="4"/> </hugepages> </memoryBacking>
当一些客机 NUMA 节点和单独主机 NUMA 节点进行合并起作用时,将允许对大页面控制的增强,但继续使用不同的大页面尺寸。例如,即使客机 NUMA 节点 4 和 5 被移动到了主机的 NUMA 节点 1 上,它们也将继续使用不同大小的大页面。
注意
当使用
strict
内存模式时,在 NUMA 节点上不具有足够可用的大页面的情况下,客机将无法启用。关于 <numatune>
标签中 strict
内存模式选项的配置示例,请参照第 8.4.2 节 “域进程”。
8.4.10. PCI 设备的 NUMA 节点位置
当启动一个新的虚拟机时,了解主机 NUMA 拓扑和 NUMA 节点中的 PCI 设备归属是重要的一点,以便在请求 PCI 传递时,客机可以被固定在正确的 NUMA 节点以优化其内存性能。
例如,如果客机被固定在 NUMA 节点 0-1 上,但是其 PCI 设备中的一个隶属于节点 2,那么节点之间的数据传输将花费一段时间。
在 Red Hat Enterprise Linux 7.1 中,libvirt 在客机 XML 中为 PCI 设备报道了 NUMA 节点位置,使管理应用程序完成更好的性能决策。
该信息在
/sys/devices/pci*/*/numa_node
的 sysfs
文件中可见。使用 lstopo 工具来回报 sysfs
数据,可以作为验证这些设置的一种方法。
# lstopo-no-graphics Machine (126GB) NUMANode L#0 (P#0 63GB) Socket L#0 + L3 L#0 (20MB) L2 L#0 (256KB) + L1d L#0 (32KB) + L1i L#0 (32KB) + Core L#0 + PU L#0 (P#0) L2 L#1 (256KB) + L1d L#1 (32KB) + L1i L#1 (32KB) + Core L#1 + PU L#1 (P#2) L2 L#2 (256KB) + L1d L#2 (32KB) + L1i L#2 (32KB) + Core L#2 + PU L#2 (P#4) L2 L#3 (256KB) + L1d L#3 (32KB) + L1i L#3 (32KB) + Core L#3 + PU L#3 (P#6) L2 L#4 (256KB) + L1d L#4 (32KB) + L1i L#4 (32KB) + Core L#4 + PU L#4 (P#8) L2 L#5 (256KB) + L1d L#5 (32KB) + L1i L#5 (32KB) + Core L#5 + PU L#5 (P#10) L2 L#6 (256KB) + L1d L#6 (32KB) + L1i L#6 (32KB) + Core L#6 + PU L#6 (P#12) L2 L#7 (256KB) + L1d L#7 (32KB) + L1i L#7 (32KB) + Core L#7 + PU L#7 (P#14) HostBridge L#0 PCIBridge PCI 8086:1521 Net L#0 "em1" PCI 8086:1521 Net L#1 "em2" PCI 8086:1521 Net L#2 "em3" PCI 8086:1521 Net L#3 "em4" PCIBridge PCI 1000:005b Block L#4 "sda" Block L#5 "sdb" Block L#6 "sdc" Block L#7 "sdd" PCIBridge PCI 8086:154d Net L#8 "p3p1" PCI 8086:154d Net L#9 "p3p2" PCIBridge PCIBridge PCIBridge PCIBridge PCI 102b:0534 GPU L#10 "card0" GPU L#11 "controlD64" PCI 8086:1d02 NUMANode L#1 (P#1 63GB) Socket L#1 + L3 L#1 (20MB) L2 L#8 (256KB) + L1d L#8 (32KB) + L1i L#8 (32KB) + Core L#8 + PU L#8 (P#1) L2 L#9 (256KB) + L1d L#9 (32KB) + L1i L#9 (32KB) + Core L#9 + PU L#9 (P#3) L2 L#10 (256KB) + L1d L#10 (32KB) + L1i L#10 (32KB) + Core L#10 + PU L#10 (P#5) L2 L#11 (256KB) + L1d L#11 (32KB) + L1i L#11 (32KB) + Core L#11 + PU L#11 (P#7) L2 L#12 (256KB) + L1d L#12 (32KB) + L1i L#12 (32KB) + Core L#12 + PU L#12 (P#9) L2 L#13 (256KB) + L1d L#13 (32KB) + L1i L#13 (32KB) + Core L#13 + PU L#13 (P#11) L2 L#14 (256KB) + L1d L#14 (32KB) + L1i L#14 (32KB) + Core L#14 + PU L#14 (P#13) L2 L#15 (256KB) + L1d L#15 (32KB) + L1i L#15 (32KB) + Core L#15 + PU L#15 (P#15) HostBridge L#8 PCIBridge PCI 1924:0903 Net L#12 "p1p1" PCI 1924:0903 Net L#13 "p1p2" PCIBridge PCI 15b3:1003 Net L#14 "ib0" Net L#15 "ib1" OpenFabrics L#16 "mlx4_0"
此结果表明:
- NIC
em*
与磁盘sd*
是与 NUMA 节点 0 和 cores 0、2、4、6、8、10、12、14 连接的。 - NIC
p1*
与ib*
是与 NUMA 节点 1 和 cores 1、3、5、7、9、11、13、15 连接的。