(一)研究目的
针对基于DRAM模拟非易失的索引进行评测,以指导以后基于PM的索引结构的制定。
(二)研究背景
(1)Optane DC PM 的属性
Performance
优点
- 每 DCPMM 的容量高达 512GB,比 DRAM 扩展得更好
- 单个 CPU 可配备 3TB 的 DCPMM
缺点
- 读/写延迟高于 DRAM
DCPMM | DDR4 DRAM | |
---|---|---|
读延迟 | 300ns | 75ns |
顺序读写带宽 | 40GB/s和10GB/s | 大约3倍和11倍 |
随机读写带宽 | 7.4GB/s和5.3GB/s | 大约8倍和14倍 |
由于 DRAM 和 PM之间的性能差异,应利用更多的内存通道(即装备更多 DCPMM )来达到峰值带宽。CPU 支持 6 个通道,每个通道有两个插槽,一个用于 DRAM,一个用于 PM。因此,DCPMM 的数量也表示内存通道的数量。与DRAM 类似,增加更多的 DCPMM 显著提高了 PM 的读性能,而对写带宽的影响相对较小。图1 比较了2个 DCPMM 和6个 DCPMM 下不同线程数量的带宽。
在大多数情况下,使用两个线程足以饱和PM写带宽。在本文的实验中,使用6个 DCPMM ,更高的带宽(更多的 DCPMM )是获得高索引性能的关键。
Operating modes
Optane DCPMM 可以配置为运行在两种模式:Memory 和 APP Direct,都允许通过正常的 load 和 store 指令直接访问 PM,并可以利用 CPU 缓存获得更高的性能。Memory 模式下,DCPMM 作为没有持久性的大内存;DRAM 被用作 cache 以减少延迟。APP Direct 模式下提供持久性,没有 DRAM cache,应用程序需要处理持久性、恢复、并发性和优化性能。
本文将 DCPMM 配置为在 App Direct 模式下运行,由软件来确定 DRAM 的角色。
(2)PM 编程模型
使用 PM programming libraries 提供的编程模型 cacheline flush 指令、PM-aware pointers、分配器和并发控制器来设计持久数据结构。
Persistence
使用 CLFLUSH、CLFLUSHOPT 或 CLWB 指令来将数据从 cache 正确地刷新到 PM 以达到安全的持久性。
Memory management
支持 PM 的文件系统有 Ext4 File system、 The SGI XFS File system、System Software for Persistent Memory 和 NOVA。
本文使用 Ext4 file system with Direct Access (DAX) 来管理 PM。
Concurrency and recovery
PM 应用程序需要在重新启动时将数据和程序状态恢复到一致的状态。
(三)研究概述
在真正的PM硬件“Intel Optane DC Persistent Memory Modules (DCPMM)”,开发了一个基于PM的索引的基准测试框架PiBench(a persistent index benchmarking framework),对四种具有代表性的索引结构wBTree、NV-Tree、BzTree和FPTree进行评价,确定了关键、有效的技术、见解和注意事项,以指导未来基于PM的索引结构的制定。
(四)关键技术
Write-Atomic B±Tree (wBTree)
wBTree 是一个持久的单线程 B+Tree,通过减少 cacheline 刷新和写入 PM 来实现高性能。
传统的 B+ 树节点排序是为了更快地进行二分查找。然而,如图 2(a) 所示,保持节点排序需要数据移位来为新键让路,这可能会在崩溃时使节点处于不一致的状态,并引起更多(昂贵的) PM 写操作。
使用未排序节点是 PM 树中常见而有效的设计。如图 2(b) 所示,bitmap 用于指示每个 slot 是否包含有效的记录(绿色有效,红色无效)。新记录被插入到空闲的 slot,并且使用 8 字节的写操作自动修改 bitmap,以设置新记录有效。使用未排序的节点减少了(昂贵的)PM写入并简化了实现,但是需要线性查找,可能比二分查找更昂贵。
为了使用二分查找,wBTree 在每个节点上使用一个间接 slot array,记录相应键的索引位置,并按顺序排序。如图 3© 所示,bitmap 保留一位(最左边突出位)来表示数组的有效性,插入 key 5 后,slot array 被标记为无效并被更新。wBTree 依赖 bitmap 的原子更新来实现一致性,并依赖日志记录更复杂的操作。在发生故障时,slot array 将被检测为无效,并在恢复时重构。
NV-Tree
NV-Tree 提出了选择性一致性的概念,加强了叶节点的一致性,松弛了内部节点的一致性。这种设计通过避免多次 cacheline 刷新简化了实现,并降低了一致性成本。但是,内部节点必须在恢复时重新构建,因为 PM 中的副本可能不一致,并且无法正确查找。与 wBTree 类似,NVTree 也使用未排序的叶节点,并使用 append-only 策略来实现 fail-atomocity。图 3 (底部) 显示了一个在 NV-Tree 叶子节点中插入的示例,无论键是否存在,记录都被直接附加一个正标志(或者删除时附加一个负标志)。内部节点是连续存储的,避免了指针,提高了缓存效率。但这使得重构代价高昂,所以为了避免频繁重构,内部节点以稀疏的方式进行重构,这可能会导致较高的内存占用。
BzTree
BzTree 是用于 PM 的 lock-free 的 B+Tree,使用 persistent multi-word compare-and-swap (PMwCAS)来处理并发性并简化实现。 如图 4 所示,BzTree 在 PM 中存储内部节点和叶子节点。内部节点是不变的(copy-on-write),除了对现有子指针的更新,叶节点可以容纳插入和更新。插入到父节点时,将使用包含新键的新节点替换该节点。然后,执行祖父节点中的更新以指向新的父节点。
Fingerprinting Persistent Tree (FPTree)
FPTree 同时使用 DRAM 和 PM 来实现接近 DRAM 的性能。如图 5 所示,它将内部节点存储在 DRAM 中,将叶节点存储在 PM 中。通过这种方式,FPTree 在保持主要数据(叶节点)持久性的同时加快了查找性能,因为在树遍历期间,与完全瞬态的对应对象相比,只有叶访问开销更大。背后的基本原理是,虽然失去叶节点会导致不可逆转的数据损失,但内部节点总是可以从叶节点重建。由于内部节点必须在恢复时重建,因此 FPTree 用恢复时间换取更高的运行时性能。
FPTree 使用指纹来加速搜索。它们是叶内键的一个字节哈希值,连续地放置在叶节点的第一个 cachelin 大小的块中。FPTree 还使用带有叶内位图的未排序叶节点,这样搜索就会线性遍历叶子中的所有有效键。搜索将首先扫描指纹,将页内键探针的数量平均限制为一个,这显著提高了性能。FPTree 为树的瞬态和持久部分应用了不同的并发控制方法。它分别为内部节点和叶节点使用硬件事务内存(HTM)和细粒度锁。这种选择性并发设计解决了HTM和PM所需的持久性原语(如cacheline flushing指令)的明显不兼容性,这些原语总是导致 HTM 事务直接中止。
PiBench
PiBench 的设计是为了对不同索引进行统一公平的比较。如图 6 所示,所测试的索引必须编译成一个共享库,并按照一个定义的 API 链接到 PiBench,或者通过包装器转换来自 PiBench API 的请求。该 API 由一个纯粹的抽象类和一个用于实例化基准数据结构的 create_index 函数组成,该类封装了常见操作(插入、查找、删除、扫描、更新)。要使用 PiBench,用户只需要派生一个实现 API 的类。然后PiBench 针对实例化的索引对象发出请求。
(五)实验
环境和设置
Hardware
- a Linux (5.3) server
- an Intel Xeon Platinum 8260L CPU
- 1.5 TB of Optane DC PM (6 × 256 GB DCPMMs) configured in the App Direct mode
- 96 GB of DRAM (6 × 16 GB DIMMs)
- The CPU has 24 cores (48 hyperthreads), 36 MB of L3 cache, and is clocked at 2.40 GHz.
Software
使用 PM 开发工具包 Persistent Memory Development Kit (PMDK) 实现了所有索引。PMDK 提供了管理 PM 的原语,包括 PM 分配器。wBTree、FPTree 和 NV-Tree 直接与 PMDK 交互;BzTree 只与 PMwCAS 交互,PMwCAS 被扩展到使用 PMDK。对于DRAM分配,使用 jemalloc。
设置
测试了不同的节点大小,并确定了每个树的最佳性能大小。对于 FPTree 和 NV-Tree,使用 128 个记录的内部节点和 64 个记录的叶子节点。对于 wBTree,每个内部节点有 32 条记录,每个叶子节点有 64 条记录。BzTree的节点大小设置为1 KB。
索引实现
本文对四种索引结构进行了重要细节的更改,兼容 PMDK 编程模型从而在真正的 PM 上执行。
FPTree:原文提出了单线程版本和并发版本,本文关注并发版本。但单线程版本中的优化,例如分组分配叶子节点,可以应用于所有树。
wBTree:原文使用 undo-redo logs 来处理失败原子性。本文使用 FPTree 更高效的 micro-logs 进行改进。使用 PMDK persistent pointers。
NV-Tree:原文没有设计并发性,本文实现了 lock coupling。使用 PMDK persistent pointers,将叶节点中的记录对齐到 8 字节边界,对于 8 字节的键和值,带有有效性标志记录的大小为 24 字节。这比需要的多了 7 个字节,但性能更好。由于内部节点不强制一致性,本文将它们放在 DRAM 上以提高性能。
BzTree:将 PMwCAS 描述符的大小从 4 增加到 12,以适应足够的内存单词更改和新的分配。
(六)总结
本文总结观察结果,从评估中提炼了 8 条见解如下:
(1)The evaluated indexes run factors slower on real PM than originally reported.
原因1:缺乏健全的 PM 编程模型,一些设计(如copy-on-write)并不理想。
原因2:基于 DRAM 的仿真不精确,主要方面是 PM 带宽的稀缺。
(2)PM Bandwidth is a scarce resource.
PM 用于顺序读、顺序写和随机写的带宽分别比DRAM低 ~ 3×、~11× 和 ~14×。
(3)PM allocations are very expensive.
PM 分配比 DRAM 分配慢了一个数量级。为了减少分配开销,应使用 in-place updates 和 bulk allocation。
(4)Leveraging DRAM is desirable for high performance but may trade off recovery time and cost of ownership.
由于 PM 的带宽有限,如果在恢复时需要在 DRAM 上重建很多数据,恢复时间可能会变长。DRAM 价格成本较高。
(5)Fingerprinting is effective in speeding up point queries.
Fingerprinting 显著减少了point operations (lookup, insert, update, and delete) 对 PM 访问,从而实现更高的性能和并发级别。
(6)Indirection slot arrays significantly speed up range queries.
Indirection slot arrays 是范围扫描的首选技术,既不需要扫描整个叶子,也不需要重建记录的顺序。
(7)Copy-on-Write is a bad fit for PM.
原因 1:CoW 增加了 PM allocations。
原因 2:CoW 消耗额外的 PM 带宽。
(8)Applicability to NVDIMM.
减少写和刷新的技术(如 unsorted nodes 和 fingerprinting)也适用于 NVDIMM。NVDIMM 上的 PM allocations 代价较大,因为需要 flashes 和 fences,使得 CoW 也不适合。