文章Shimin C ,Qin J .Persistent B+-trees in non-volatile main memory[J].Proceedings of the VLDB Endowment,2015,8(7):786-797.
本文为记录学习使用,如有错误,烦请指正。
本次文章内容主要围绕原文前二章内容
引言
未来的计算机系统预计将配备非易失性主存(Non-Volatile Main Memory, NVMM),这得益于新一代非易失性存储器(Non-Volatile Memory, NVM)技术的发展,如相变存储器(Phase Change Memory, PCM)、自旋转移矩磁阻存储器(Spin-Transfer Torque Magnetic Random Access Memory, STT-MRAM)和忆阻器(Memristor)。非易失性特性使得内存中的数据结构可以在故障后立即恢复。然而,要实现这一目标,需要仔细设计以确保在故障后内存数据结构处于已知的一致状态。
由于B+-树是一类重要的树结构,广泛用于数据库和数据密集型系统。因此文章重点研究了持久化内存中的 B+-树(Persistent In-Memory B+-Trees)这种数据结构
目标
非易失性特性使得内存中的数据结构可以在故障后立即恢复,
即NVMM 的非易失性特性使得在电源故障和系统崩溃的情况下实现持久化数据结构成为可能。
问题
1.数据结构不一致问题

上图描述了如果在插入到排序树节点J的中间发生故障可能会发生什么。
正常情况:通常,插入算法会将9、7和4向右移动一个插槽,插入3,并增加数字字段,导致图3顶部所示的正确结束状态。
三种故障导致的情况:
①在第一种情况下,故障发生在算法移动4之前。
②在第二种情况下,故障发生在算法增加数字字段之前。
③在第三种情况下,算法实际上已经完成了插入,但并非所有数据都已写回NVMM。注意,观察图像左侧树节点结构,可以发现节点J由两条高速缓冲行组成。如果高速缓冲行1已写回NVMM,但发生故障时高速缓冲行2仍在CPU高速缓存中,就会出现③所示问题
上述三种情况导致三种不同的不一致状态。同样,很容易表明从具有不同故障情况的多个启动状态可以达到相同的不一致状态。因此,仅使用不一致树节点的信息是不可能的。
举例:
5124479(故障发生在写入3之前)
5124(未写回NVMM) 479(已写回NVMM)
说明如果没有仔细的设计,数据结构在故障后可能处于不一致的不可恢复状态
2.算法性能问题
为解决上面的数据结构不一致问题
乍一看,使用传统技术(如撤销-重做日志(Undo-Redo Logging)和影子拷贝(Shadowing))来支持持久化 B+-树似乎是一个直接的方法。
然而,文章发现在NVMM中支持持久化 B+-树的问题与基于磁盘的 B+-树有很大不同:
-
CPU缓存控制的限制:软件对CPU缓存的控制能力有限。通常无法保证修改后的CPU缓存行何时以及以何种顺序写回主存。虽然可以通过特殊的 CPU缓存刷新(clflush)和内存栅栏指令(mfence)来强制写回顺序,但这些指令会带来显著的性能开销。
-
不同NVM技术的特性:例如,PCM的写入速度较慢,且存在能耗和耐久性问题;相比之下,STT-MRAM和忆阻器的性能接近DRAM。因此,减少写操作是PCM的一个重要目标,但对于快速的类DRAM NVM来说可能并不那么重要。
思路
1.首先解决数据结构一致性问题
为了防止 NVMM 写操作在故障中丢失,文章利用了两条x86指令:
一般情况下修改后的缓存行不会立刻写回到NVMM中,CPU在将存储指令发送到缓存时即认为存储操作已完成,然而如果此时缓存中的脏缓存行还没有写回NVMM,此时系统故障,则可能就会产生数据结构不一致的问题。
*clflush(cache line flush):解决长时间不写回。
给定一个内存地址,clflush 指令会使包含该地址的缓存行在所有缓存层中失效,并将该失效操作广播到系统的所有 CPU 核心。如果指定的缓存行是脏的(即被修改过),则在失效之前会将其写回到主存。
在多核处理器系统中,CPU 可能会为了优化性能,对内存访问操作进行重排序。这意味着,即使在代码中 A 操作在 B 操作之前,CPU 可能会先执行 B 再执行 A(如果它认为这样可以更快完成)。mfence 用于防止这种重排序,确保在 mfence 之前的所有内存读写操作在全局范围内可见,并在其之后的任何操作开始之前完全完成。
*mfence(memory fence):解决写回顺序不确定。
mfence 指令确保在其之前的所有内存读取和写入操作在全局范围内可见,然后再进行其后的任何内存操作。然而,mfence 本身并不会将脏缓存行写回内存。
意味着在 mfence 之前发出的 clflush 指令会被强制立即生效,确保数据在系统崩溃前被安全地写入 NVMM。
何时使用这两条指令?
数据修改操作完成后立即执行。
同样也是为了确保修改的数据被持久化到 NVMM 中,而不是仅仅停留在 CPU 缓存中。
防止系统崩溃或断电时,数据仍然滞留在缓存中而未写回 NVMM,导致数据丢失。
如何确定顺序(确定顺序的依据):
不越界
读取数组越界是一种未定义行为(Undefined Behavior),这意味着程序可能会有多种不可预测的行为。
以下为越界的部分可能
1.如果遍历操作访问的内存地址超出了程序的可访问范围,操作系统会检测到非法内存访问,从而触发段错误(Segmentation Fault)或访问冲突(Access Violation)。
2.如果越界读取的内存区域恰好落在有效的内存地址范围内(例如属于其他数据结构的区域),则可能不会立即引发崩溃,但可能会读取到意外的内存数据,导致最终程序的结果错误。
举例:
插入insert:键增加,为了保证不越界,先更新键值、结点指针
删除delete:键减少,为了保证不越界,先更新键数量字段
为了确认clflush 和 mfence 对性能的影响进行了如下实验:

图4 展示了在一台 Intel Xeon X86-64 机器上执行 clflush 和 mfence 指令对性能的影响。
文章进行了顺序写入和随机写入的实验。
图4 中:
- Y 轴表示访问每个记录的平均耗时。
- X 轴表示在两个
mfence之间被clflush刷新的缓存行数。 - 顺序写入的三条曲线(下方)和随机写入的三条曲线(上方)分别展示了以下情况:
- 仅进行写入操作。
- 每次写入记录后执行
clflush。 - 对于情况 (2),每写入 i 个记录后插入一次
mfence(i 从 1 变到 16)
实验结果分析:
- 从图中可以看到,
clflush会显著减慢顺序写入的速度,因为clflush强制将脏缓存行写回内存。这会打断 CPU 和内存控制器对顺序内存访问的优化(例如,合并多个访问到同一个已打开的内存行)。 - 相比之下,由于随机写入不利于这些优化,
clflush对其影响较小。 - 插入
mfence后会进一步增加开销,因为mfence需要等待之前的clflush完成。在两个mfence之间增加clflush的记录数可以减少这一开销。因此,降低mfence的相对频率是很重要的。
2.提出分析实现持久数据结构的算法的指标
结合上文我们可以知道写入次数、clflush指令和mfence指令数量会显著影响算法的开销。
由此为了验证后文提出的wB+-Tree的性能比传统的实现持久数据结构的算法(undo-redo log & shadowing)更好,依据上文的分析,提出了三个指标:
写数量writes(Nw)
clush数量clflush(Nclf)
mfence数量mfence(Nmf)
这三个指标越小,说明算法的开销越小
2423



被折叠的 条评论
为什么被折叠?



