virtiofs per-inode DAX 介绍

背景信息

  1. 什么是 virtiofs?

virtiofs 是一种用于在 host/guest 之间共享文件的文件系统,由 Redhat 开源,它使得不同 guest 之间能够以快速、一致、安全的方式共享同一个 host 目录树结构,目前广泛应用于 Kata Container 作为容器的 rootfs。

  1. 什么是 DAX?

DAX (Direct Access) 最初是针对于 NVDIMM (Non-Volatile DIMM, 非易失性内存设备) 的一种文件访问模式,由于 NVDIMM 设备的访问时延已经很接近内存,同时 CPU 可以像访问内存一样直接访问 NVDIMM 设备,因而在访问 NVDIMM 设备上存储的一个文件的时候,不会再使用 page cache 进行缓存,而是直接访问 NVDIMM 设备,从而大幅缩减 IO 路径上的软件栈。

virtiofs 也具有 DAX 模式,此时 guest 内部访问 virtiofs 文件的时候,guest 内部并不会维护 page cache,而是直接访问 host 上该文件对应的 page cache,也就是说此时 host/guest 会共用 host page cache,从而避免 double page cache 带来的内存开销。

virtiofs DAX 的限制

DAX window 资源存在争抢

在具体阐述之前,有必要先简单介绍 virtiofs 是怎样实现 DAX 的。

virtiofs per-inode DAX 介绍

dax 模式下,VMM (e.g., qemu) 需要给 virtiofs 设备分配一段 GPA (Guest Physical Address) 地址空间,有了这一段 GPA 地址空间,VMM 才能让 guest 共享 host page cache,这一段 GPA 地址空间在 virtio 协议中称为 shared memory region,qemu 中可以通过 "-device vhost-user-fs-pci,...,cache-size=2G" 中的 "cache-size" 参数指定这段地址空间的大小。

virtiofs 会将这段 GPA 地址空间划分为一个个小的切片,称为 dax window,guest 在访问 virtiofs 中的文件的时候,就需要为当前访问的文件区间分配对应的 dax window,dax window 就用于 GPA->HPA 之间的地址翻译,从而使得guest 内部访问这个文件区间的时候,实际访问的是对应的 host page cache。

dax window 的大小对于性能具有重要影响。减小 dax window 的粒度,那么 shared memory region 大小一定的情况下,那么可用的 dax window 的数量更多,资源更加宽裕,但是由于单个 dax window 覆盖的范围变小了,guest 会更加频繁地触发 FUSE_SETUPMAPPING fuse 请求,从而带来性能开销;而如果增大 dax window 的粒度,那么 shared memory region 大小一定的情况下,可用的 dax window 的数量就变少了,更容易引发对 dax window 资源的竞争。

目前 dax window 的粒度是默认的 2M,在编译时确定,运行时无法动态修改。

由于 dax window 的数量是有限的,当 dax window 资源耗尽时,virtiofs 会触发 recalim 流程,即同步/异步地回收一些之前分配的、但当前已经不再使用的 dax window。

同步触发的 recalim 流程会严重影响性能,此时进程在访问文件时就需要同步等待可用的 dax window 资源,从而影响性能。例如需要访问大量小文件的场景,会快速耗尽 dax window 资源,从而进入 dax window 的 relcaim 流程,从而严重影响性能。

以下通过编译内核来模拟上述场景,将 kernel tree 通过 virtiofs 挂载到 guest 中,在 guest 中测试执行 make vmlinux -j128 的时间

type cache cache-size time
non-dax always -- real 2m48.119s
dax always 64M real 4m49.563s
dax always 1G real 3m14.200s
dax always 4G real 2m41.141s

从上述数据可以看出,在访问大量小文件的场景下,dax window 的 reclaim 操作会带来性能损耗,且 dax window 资源越紧张,损耗越大,相较于 non-dax,dax window 为 64M 大小时性能损耗为 -70%,dax window 为 1G 大小时性能损耗为 -15%。

有时候并不真的节省内存

上一节描述了 recalim 流程会严重影响性能,那么理论上只要扩大 shared memory region,从而增大 dax window 的数量,就能避免 reclaim 流程的触发。从上面的测试也可以看到,"cache-size=4G" 时,dax 与 non-dax 的性能已经持平。

但是需要指出的是,增大 cache-size 并非毫无代价,虽然 cache-size 消耗的是 host 上的 VMM (例如 qemu) 的虚拟地址空间,而非真正消耗物理内存,但是在 guest 中同样需要为 cache-size 分配对应的 struct page 描述符。那么 struct page 描述符就会存在 1.5% (64/4096 = ~1.5%,struct page 的大小为 64 字节,假设 page 是 4k 的) 的内存开销,所以 1G cache-size 就存在 16M 的内存开销。也就是说 cache-size 过大,虽然消除了潜在的性能损耗,但是 page 描述符带来的额外内存开销,反而可能对冲掉 dax 特性节省的内存。

per-inode DAX 方案

针对上述问题,我们提出了 per-inode DAX 的方案。在此之前,DAX 模式的开启或关闭是 mount 级别的,即每个 virtiofs 文件系统,要么全部开启 DAX 模式,要么全部关闭 DAX 模式。而有了 per-inode DAX 之后,每个文件可以控制开启或关闭 DAX 模式,相当于是为 DAX 模式的开启或关闭提供更细粒度的控制。用户可以根据场景和需求,自行控制每个文件是否开启 DAX 模式。

例如在上述介绍的场景中,文件大小小于 32KB 的文件就关闭 DAX 模式,这是考虑到在DAX 模式下,这个文件会占用一个 2M 大小的 dax window,这个 dax window 对应的 page 描述符会占用 32 KB 内存;而在关闭 DAX 的模式下,这个文件的 guest page cache 占用的内存不会超过 32KB。显然这种情况下,关闭 DAX 的模式更加节省内存。

virtiofs per-inode DAX 介绍

我们实现的方案非常简单,virtiofs 是基于 FUSE 协议实现的,在 lookup 阶段 guest 会向 host daemon 发送 FUSE_LOOKUP message,在返回的 reply 中包含了对应 inode 的元数据。而 per-inode DAX 方案下,FUSE_LOOKUP message 返回的 reply 中包含的 FUSE_ATTR_DAX 标记就描述了该文件是否开启 DAX 模式。也就是说由 host daemon 控制某个文件是否开启 DAX 模式,即由用户来实现各样的策略。

per-inode DAX 特性在 v5.17 合入主线。

总结

本文介绍了现有的 virtiofs DAX 实现存在的限制,并进而引出了对应的 per-inode DAX 解决方案。per-inode DAX 方案非常的简单、直观,但是这需要用户在一定先验知识的基础上实现相应的策略,因而我们相信这一定还不是终态的方案。接下来我们将继续探索更进一步的解决方案,让 virtiofs DAX 更加的高效、易用。

上一篇:深入Protobuf源码-概述、使用以及代码生成实现


下一篇:Git分支管理-如何查看分支