GPU虚拟机创建时间深度优化

从公有云服务商那里购买过虚拟主机的资深用户,一般会发现这么个规律:创建一台CPU虚拟主机是比较快的,但是要创建一台包含GPU卡的虚拟主机通常需要等比较长的时间,整个创建过程短则数十秒钟,长则数分钟。对于绝大多少的用户来说,虚拟主机的创建时间长一点对他们影响并不大,因为创建虚拟机属于相对低频操作。但是也会有一些特定的用户由于其业务场景交互性比较强,会对虚拟主机的创建时间有相对苛刻的要求,因为过长的创建时间会导致其业务用户体验很差。本文将从虚拟化的角度来介绍GPU虚拟主机创建时间长背后的原因,以及相关的优化方法。

通过分析Libvirt, QEMU以及Guest 内的相关日志及对应的时间戳,可以获取GPU虚拟主机在创建过程中的耗时情况,这里我们主要关心几个关键的时间点:  a) Libvirt 开始创建QEMU 进程;b) Libvirt 执行 Resume启动VCPU ;  c) Guest kernel 打印第一条日志.  在本文中,我们把a和 b 之间的时间间隔称为QEMU初始化时间, 把b 和c 之间的时间间隔称为 BIOS执行时间。以下数据是在滴滴云的线上环境中采集到的创建一台包含8个CPU核虚拟机实例的相关数据:

从上面的数据可以看到,对于规格相同的虚拟机实例,带1块P40卡的GPU实例相比同规格的CPU实例在QEMU初始化及BIOS执行部分的时间都明显要长, 在带4块P40卡以及更大内存规格的场景下,需要的时间会进一步拉长。通过实验我们发现在主机配置和GPU卡型号确定的前提下,GPU实例的创建时间长短主要取决于两个因素:虚拟机的内存大小和GPU卡的数量。

为什么GPU实例的创建过程要比CPU实例的创建过程耗时长?多消耗的时间到底花在哪里?要搞清楚原因需要深入的分析,比较直观的办法就是通过perf采样来生成火焰图,以此来分析虚拟机在创建过程中的热点函数。下图是在滴滴云环境里抓取到的GPU虚拟机启动过程中QEMU进程的火焰图。

通过对代码调用关系的分析,可以得知热点发生在系统分配内存和对内存页面清零的过程中,是由QEMU中的vfio_dma_map函数在执行VFIO_IOMMU_MAP_DMA  ioctl 系统调用所触发,该调用会Pin住所有分配给VM当做RAM使用的内存。在Pin 内存的过程中,如果虚拟内存对应的物理页面尚未分配,会先进行物理内存分配并对内存页面内容进行清零。在Linux kernel 中,对分配给应用程序的内存进行清零主要是基于安全方面的考虑,避免Host 内存中的内容泄漏给用户空间的应用程序。这里之所以要将内存Pin 住,目的是为了保证IOMMU IO页表和 host HVA->HPA 映射的一致性,否则Guest 内设备的DMA操作可能会访问到错误的内存页面。

VFIO DMA 映射处理慢可以在一定程度上解释为什么内存的大小和GPU卡的数量会影响到GPU实例的创建时间。虚拟机实例内存规格越大,需要映射和Pin住的内存量也就越大,相关处理的耗时和内存量成正比。另外GPU卡上通常会包含一块比较大的MMIO区域,对MMIO的映射也会耗费较多的时间,卡的数量越多,耗时就会越长。相比之下,CPU实例的创建过程没有VFIO DMA 映射的相关处理流程,因此会比较快。

针对以上的热点,有什么办法可以消除或者缓解呢?已经有业内的同行们提到过这个问题并给出了对应的解决方案,其思路是对分配给VM 用作RAM使用的内存区域做一个标记,在内核中跳过对标记的内存页面进行清零,而将清零的动作留给QEMU来做,在QEMU 中可以利用多线程以及更高效的指令进行清零动作,从而加速Pin内存的过程。该方案的缺陷主要有两点:  一是存在安全性风险,其他应用程序可以利用设定的标记来窥探host 内存中的信息;二是在VM实例的VCPU个数比较少的情况下,优化效果不是很好。

我们采用了另外一种方案,通过修改Host kernel的内存管理部分, 我们实现了一种对Host 上空闲物理内存提前进行清零的机制,清零动作可以在系统空闲的时候进行,当某个内存页面被清零后,将其对应的 struct page 进行标记,这样在需要对内存进行清零的时候,可以通过检查该标记来判断是否要执行清零动作,如果清零的标记已经被设置,就可以跳过清零的步骤。该方案避免了上述方案中的两个主要问题,同时还有其它方面的好处,主要包括以下几点:a.可以提高缺页异常处理效率,尤其是透明大页的缺页异常处理效率;b. 可以加速需要Pin内存及需要通过mlock 来锁住内存的应用场景,例如使用RDMA, QAT 硬件加速等场合;c.  可以加速内核中其他需要对内存进行清零的场景。相关补丁的RFC版本,我们已经提交到了Linux kernel 社区。

另一个加速Pin内存的有效方法是采用大页,通过开启透明大页可以显著减少缺页处理的调用次数并加速Pin内存的过程。下图展示了开启透明大页以及启用空闲内存预清零机制对GPU实例创创建时间的影响。

以上的数据表明,在开启透明大页以及空闲内存预清零功能后,可以显著的的优化QEMU的初始化时间,但是BIOS部分的耗时依然偏长。通过进一步的分析我们发现主要的时间消耗还是在VFIO 映射DMA的处理过程当中,主要有几个方面的原因:a. 映射DMA Pin内存需要逐页查询页表,开销较大;b. QEMU 存在对部分IOVA区域的反复映射及解除映射的操作。于是我们尝试在这两个方向上进行优化,通过采用批量处理的方法减少查询页表的开销,另外在QEMU中加入VFIO DMA映射区域的管理,有效的规避了效率低下的反复映射及解除映射操作,最终大幅度降低了VFIO DMA映射的时间消耗。

在解决完上述问题后我们并没有止步,对虚拟机实例创建过程中的可优化的其它地方,我们也做了相关的处理,例如关闭BIOS boot menu ,优化VFIO PCI 设备reset 的流程,去掉对GPU实例来说不必要的操作,最终将GPU实例创建过程中虚拟化部分的时间开销减少了90%以上,下面这张图展示了单卡小内存规格实例优化前后的耗时对比:

大内存规格和多GPU卡的效果更加显著,时间减少了95%以上,相关数据如下图:

经过上述的优化,目前在滴滴云上创建一个GPU实例的速度比优化前显著加快,甚至比优化前创建一个CPU实例的速度还要快,如果用户对GPU实例的创建速度有比较强的需求,欢迎到滴滴云上进行体验

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页