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#elementsCPUAllocationhttp://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 开始,cellidnodeid 以递增的次序被指定到每个 cell 或节点。

8.4.9. 向多个客机 NUMA 节点指定主机大页面

在 Red Hat Enterprise Linux 7.1 中,主机中的大页面可以被分配到多个客机的 NUMA 节点。这一过程可以优化内存性能,因为客机 NUMA 节点可以被移动到主机 NUMA 节点需要的位置上,同时客机可以继续使用主机指定的大页面。
在配置客机 NUMA 节点拓扑后(详情参考第 8.4.8 节 “客机 NUMA 拓扑”),在客机 XML 的 <memoryBacking> 元素中指定大页面的大小和客机 NUMA 节点集。page sizeunit 代表主机大页面的大小。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_nodesysfs 文件中可见。使用 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 连接的。