1 Qemu 使用 VFIO 透传设备时启动慢

在使用 VFIO 透传 PCI 设备时,如果虚机使用普通的 4K 物理页,Qemu 启动速度非常慢。启动 100G 的虚机可能就要几十秒甚至更长时间。

2 通过 Perf 进行性能分析

下面我们通过火焰图来看一下普通内存的 Qemu 启动时间都消耗在哪。

2.1 通过 perf 生成火焰图

$ git clone https://github.com/cobblau/FlameGraph

// 其中 25104 是 qemu 进程号
$ perf record -F 99 -p 25104 -g -- sleep 10
$ perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > xx.svg

2.2 4K 内存火焰图结果与分析

QEMU VFIO 内存优化探索与实践_Linux

通过火焰图可以看出,时间按消耗主要在两个方面:

  • 在缺页中断中调用 do_huge_pmd_anonymous_page 申请内存。

  • 使用 clear_huge_page 对内存清零。

3 使用 1G 的巨页内存进行优化

给虚机使用巨页内存可以成功对上面第一条进行优化。

由于云虚机内存都是以 G 为颗粒度的,所以我们可以给虚机提供 1G 的巨页内存。

我们以 450G 内存的虚机为例,使用 1G 巨页内存后,启动时间可以优化到 32s。

再次通过火焰图打印一下使用巨页后的时间消耗。

3.1 巨页内存火焰图结果与分析

可以看到使用巨页后的时间消耗的大头是在内存清零上。

4 通过加速内存清零进行优化

4.1 Qemu 内存申请过程简介

如果没有使用 VFIO 透传设备,Qemu 开机时只是 mmap 出来内存并没有真正占用物理内存。真正占用物理内存是虚机在使用时产生的缺页中断中。

如果使用 VFIO 透传虚机,那么虚机的物理内存要事先分配好并且 pin 住不会让内存产生迁移。

当使用巨页内存时同样会在申请后进行预分配操作,并且在预分配内存的内核代码中进行清零操作。

4.2 Qemu 巨页内存申请调用流程

从上图看到 Qemu 在预分配内存是直接写内存,然后通过内核的缺页中断实现的。

前面提到即便给虚机使用了巨页内存,虚机启动时间仍旧有 30 几秒。那么时间消耗到了哪里了呢?

原因是内核在分配内存的时候由于怕原内存中的数据遭到泄露,所以在分配时对内存进行了清零操作。

通过上面的 “巨页内存火焰图”,我们已经可以看到在缺页中断中 clear_page_erms 占据了相当大的时间,并且代码隐藏的相当隐蔽。

4.3 内核内存清零调用流程

QEMU VFIO 内存优化探索与实践_Linux_02

4.4 内存清零过程优化

  • 参考 2019OS2AT 大会上《腾讯云虚拟化性能优化实践》一文中蒋彪老师的思路,可以在各个 VCPU 进入 none-root 前分别对自己负责的内存地址段使用 memset() 清零。

  • 目前 Qemu 也提供了预分配内存的函数,只要预分配内存的全局变量 mem_prealloc 打开,Qemu 就会创建与 VCPU 数量相当的线程用来对内存清零。可以把 memset() 的位置挪到预分配内存的函数中。

  • 可以把内核的 clear_page_erms 函数实现换成 SSE 指令集实现。

  • 还可以让虚机在关机后,主动对内存进行清零,或者在 host 创建巨页时就对内存进行清零。

小伙伴们看看哪种方法适合自己哦。