问题
持久内存分配是开发高性能内存应用程序的基本构建块。许多分配器通过各种类型的元数据(例如,对象位图、slab结构、数据块标头、预写日志)来管理持久堆,以便有效地为内存分配和释放服务。更新堆元数据会触发对持久内存的频繁小写入,大小从1bit到64B。所有这些持久分配器都使用与大小分离算法来服务小分配请求,以减少内存碎片。
现有方法局限性
现有方法的局限性:
-
对堆元数据的小写入可能会导致缓存行刷新。CPU缓存行的典型大小为64 B[31]。nvm_malloc的位图大小为8B,当位图重复更新时,缓存行也需要刷新以实现持久性。但缓存行刷新的延迟是写入延迟的7.5倍[7]。在四个基准测试中,缓存行刷新的数量占分配器引发的刷新操作总数的40.4%~99.7%,导致持久内存分配器的性能下降。
-
分配器的堆元数据倾向于在持久内存中随机访问。许多分配器将堆分为固定大小(例如,4MB)的块,以便于管理。它们在每个块的头中记录记账元数据,以避免元数据被错误修改,这种布局使标头分布在整个堆空间中。在一系列分配和释放请求后,分配器必须原地更新随机分布在持久内存中的头。最近的工作表明,对于小写入,持久存储器表现出比顺序访问性能差得多的随机访问性能[39,40]。因此,堆元数据发出的小随机写入会阻止分配器实现最佳性能。
-
静态slab分离导致持久内存碎片。由于持久堆以文件的形式存储在DAX文件系统上,碎片无法通过重新启动来消除。所有用于持久内存的分配器都使用大小隔离的slab来进行小块分配。每个slab都是多个空闲块的容器,并处理特定大小类的内存分配。指定给一个尺寸类的slab不能再用于其他尺寸类。对于分配大小不断变化和频繁删除操作的工作负载,这种分离导致的碎片将内存使用量增加了2.8倍。
本文方法
本文设计了一个新的分配器——NVAlloc,有效地消除了缓存行刷新和小的随机写入,并减轻了由slab引起的内存碎片。
-
使用从数据块到其相应堆元数据的交错内存映射,以及线程本地缓存中链表的交错布局,以避免重复访问同一CPU缓存行。
-
添加了一个持久记账日志,以顺序模式存储小的元数据更新,从malloc()和free()的关键路径中完全删除了随机元数据访问。
-
支持slab变形,在slab变形过程中,两个尺寸类中的块可以共同位于一个板中。可以很好地利用低内存使用率的slab中的可用空间,用于slab元数据管理的运行时开销为4.5%。当slab大部分空闲但无法用于满足其他尺寸类中的请求时,会自动启用slab变形。
NVAlloc是对现有一致性模型的补充。6个基准测试的结果表明,对于小型和大型分配,NVAlloc将最先进的持久内存分配器的性能分别提高了6.4倍和57倍。使用NVAlloc可将内存使用量减少57.8%。此外,本文将NVAlloc集成到一个持久的FPTree中,与最先进的分配器相比,NVAlloc将此应用程序的性能提高了3.1倍。
实验
实验环境:两个Intel Xeon Gold 5218R CPU的Linux服务器(内核5.3.0-050300-generic)、每个CPU有20个物理内核(40个超线程)、64 GB DRAM、两个Intel Optane DIMM(每个DIMM 128 GB)。连接到CPU的每对DIMM都安装Ext4 DAX文件系统,并在App Direct模式下进行配置。为避免NUMA效应,我们使用numactl程序将每个线程绑定到第一个插槽中的一个核心。
数据集:Threadtest [2]、Prod-con [1, 36]、Shbench [29]、larson [22, 31]、DBMStest [16]
实验对比:小分配的时间开销、吞吐量、内存消耗、故障恢复
实验参数:线程数、是否支持一致性、数据集、是否有GC
总结
针对持久内存上的内存分配/回收,现有方法有三个问题:对堆元数据的小写入导致缓存行刷新;分配器的堆元数据倾向于在持久内存中随机访问;静态slab分离导致持久内存碎片。本文提出NVAlloc,提出三个创新点:(1)使用从数据块到其相应堆元数据的交错内存映射,以及线程本地缓存中链表的交错布局,以避免重复访问同一CPU缓存行。(2)添加了一个持久记账日志,以顺序模式存储小的元数据更新,从malloc()和free()的关键路径中完全删除了随机元数据访问。(3)支持slab变形,两个尺寸类中的块可以共同位于一个slab中,利用低内存使用率的slab中的可用空间。