下载本文 PDF 版本 PDF

传统硬件上的持久内存编程

持久内存编程风格可以显著简化应用程序软件。

Terence Kelly

非易失性内存的美好新世界

经过计算机行业多年的期待,字节寻址的 NVM(非易失性内存)现在有望在主流计算机中增强或取代易失性 DRAM(动态 RAM)。7 就像 20 世纪 50 年代和 1960 年代的铁氧体磁芯内存一样,今天的 NVM 即使在断电时也能保留数据。虽然 NVM 比 DRAM 慢,但它比传统的持久介质——HDD(硬盘驱动器)和闪存 SSD(固态驱动器)快得多。10 凭借其高密度和低字节成本,NVM 的速度和非易失性将彻底改变围绕快速小容量易失性内存设备和慢速大容量持久存储设备设计的内存/存储层次结构。12

NVM 提供的最令人兴奋的机会是彻底简化应用程序访问持久数据的方式,这些数据必须在进程终止和机器重启后仍然存在。今天的应用程序通过复杂中间软件层(例如文件系统和数据库)在块寻址存储设备中操作持久数据,这些软件层的接口和语义与内存的接口和语义根本不同。因此,应用程序开发人员必须在不同的编程范例之间进行心理上的上下文切换(例如,使用 LOAD 和 STORE 指令访问内存中富含指针的数据结构的命令式算法,与在关系数据库中对表进行操作的声明式 SQL 相比)。用于临时数据和持久数据的编程风格之间的概念“阻抗失配”通常会增加开发成本,并损害正确性、可维护性和性能。13

NVM 提供了将持久应用程序数据存储在内存中的选项,从而无需使用具有不同规则和特性的单独持久性技术。纯 NVM 编程通过消除复杂的黑盒中间件、消除在内存和存储格式之间进行转换的序列化器和解析器,并允许程序员以内存数据结构和算法的单一范例进行思考,从而带来显著的简化。这些简化反过来可能会提高成本、正确性和性能。NVM 需要新的容错机制,但有广泛的库支持和文档可用。8,15

当然,今天的大多数计算机都缺乏 NVM,并且不会进行改造以配备它。幸运的是,NVM 所提倡的编程风格可以在没有 NVM 硬件的传统计算机上实践,并且可以获得其许多好处。本文描述了持久内存的编程技术和习惯用法,持久内存是一种软件抽象,它支持与 NVM 相同的简化,但可以在传统硬件上实现。这些技术在 NVM 研究人员中广为人知,但既没有为开发人员充分记录,也没有在计算机科学课程中广泛教授。

本文首先描述了一种简单而通用的方法,可以在普通计算机和操作系统上实践持久内存编程风格,并使用适用于 GNU/Linux 的 C 和 C++ 程序(在以下部分中)说明主要观点。持久内存编程的简单方法不能保护持久应用程序数据的完整性,使其免受断电或软件崩溃的破坏,因此在标题为“容忍故障”的部分中检查了可以容忍此类故障的增强功能。最后,本文总结了对持久内存编程的更好支持和更广泛使用的前景。所有示例代码都以机器可读格式提供,网址为 https://queue.org.cn/downloads/2019/Kelly_sample.tar.gz

 

古老的持久内存

持久内存编程占据了前国防部长唐纳德·拉姆斯菲尔德(以其“已知的已知”、“已知的未知”和“未知的未知”而闻名)所忽视的第四类知识;这是你所知道的,但你不知道你知道它。在类 Unix 操作系统上用 C/C++ 进行持久内存编程始于对众所周知的技术的直接组合:你只需在使用指针强制转换将 mmap() 的返回值解释为这些数据结构的入口点的文件支持的内存映射中布局应用程序数据结构(即 structs)。内存映射允许应用程序通过 CPU 指令(LOADSTORE)更新后备文件中的持久数据到文件的内存映像。为了增加便利性,持久堆抽象通常分层在原始持久内存之上。尽管基本机制很常见,但在开发人员中对这种技术组合的意识与其优势并不相称。

作为回忆 mmap() 操作的热身练习,请考虑图 1 中的 C 程序,该程序简单地将文件解释为字符数组,并将每次出现的字符串“abc”转换为大写“ABC”。注重性能的读者可以将这种就地方法与命令行实用程序替代方案进行比较,

sed 's/abc/ABC/g' < input > output”,

在随机文本上(例如,来自“openssl rand -base64 10000000 > input”)。图 1 的就地转换和 shell 实用程序都必须读取输入文件的每个字节。然而,前者可以在写入方面更加节俭:虽然 sed 写入的字节数与其读取的字节数一样多,但 mmap() 方法仅将内存映像中已更改的页面写入后备文件——可能远少于数据。

Persistent Memory Programming on Conventional Hardware

图 1 就地字符串替换

 

与传统编程一样,持久内存编程通常涉及各种类型的、动态分配的数据结构,这些数据结构嵌入了引用,例如指针和迭代器。图 2 和图 3 展示了一个 C++ 程序,该程序说明了这些功能。图 2 包含一个最小化的持久内存分配器。关键数据结构是第 18 行的标头,其中包含分配器簿记信息以及根指针,所有正在使用的持久应用程序数据都必须可以从该指针访问。标头将占用后备文件的前几个机器字;后备文件的其余部分将包含持久堆。第 25 行的 malloc() 替换不支持回收已释放的内存;第 34 行的相应 free() 执行健全性检查,但在其他方面是一个空操作。全局运算符 newdelete 被重载以使用从第 38 行开始的简单分配器,确保图 3 中创建的 STL(标准模板库)<map> 将驻留在持久内存中。示例代码为了简洁和清晰而走了捷径;生产质量的持久内存分配器将回收已释放的内存,而复杂的应用程序可能会创建自定义容器分配器,而不是重载 newdelete

图 3 中的代码使用图 2 的持久内存分配器来创建一个持久的 STL <map> 容器(第 63 行)来计算输入单词的频率。由于 <map> 内部包含指向持久堆上对象的传统绝对地址指针,因此后备文件必须始终映射到相同的虚拟地址(变量“madd”)。命令行参数指定后备文件和可选的映射地址。后者仅在创建新的持久堆时才需要;否则,映射地址从后备文件本身获得。mmap() 标志 MAP_FIXED 是可选的(第 73 行):如果没有它,madd 参数是一个提示,mmap() 仅在有充分理由(例如,与现有映射冲突)时才会忽略它。使用 MAP_FIXED,新映射可能会覆盖现有映射。

一个新的持久堆从第 76 行开始初始化:标头被更新以包含正确的映射地址,分配器元数据被初始化,并且一个新的 <map> 从持久堆中分配并变得可以从根指针访问。应用程序从第 85 行的根指针获取 <map> 的入口点。此后,应用程序可以像使用任何容器一样使用持久 <map>:使用迭代器遍历它(第 90 行)并更新其内容。

图 4 显示了一个简单的 C shell 脚本,用于编译和运行图 2 和图 3 的程序。truncate 实用程序创建一个空的且最初稀疏的文件来包含持久堆;文件的大小是系统页面大小(4,096 字节)的倍数。稀疏文件的“按需付费”存储占用空间与持久堆配合得很好:即使其逻辑大小非常大,稀疏文件也仅为非零页面消耗存储资源。示例程序运行两次,每次都向 stdin 提供一些单词,然后转储其内容。程序第一次运行时,你必须提供一个地址来映射后备文件;第二次运行时,地址从后备文件获得。第二次调用的输出显示,持久 <map> 已保留了第一次调用中输入单词的计数。

Persistent Memory Programming on Conventional Hardware

图 2 持久 C++ STL <map>,第 1 部分:持久内存分配器

Persistent Memory Programming on Conventional Hardware

图 3 持久 C++ STL <map>,第 2 部分:词频计数器 

Persistent Memory Programming on Conventional Hardware

图 4 使用持久 <map>:运行脚本及其输出

 

你如何为映射包含持久堆的后备文件选择合适的虚拟地址(图 4 中的魔术十六进制常量)?目前还没有标准的便携式方法来做到这一点;POSIX 和 Linux 目前都没有提供对虚拟地址空间的足够控制。相关的标准和文档没有提供任何方法来保证,例如,共享库将来不会占用地址空间中你过去放置持久堆的部分。然而,在实践中,一个简单的启发式方法足以识别地址空间中的“安静区域”,你可以在其中可靠地映射持久堆:只需找到通过 mmap() 可获得的最大未使用地址范围,并将持久堆放置在该范围的中间附近。图 5 的简单 C 程序是一种查找此类空地址范围的方法;检查 /proc/self/maps 是另一种方法。图 5 的第 16 行避免了一个微妙的错误。2 传递给 mmap() 的地址必须在页面边界上对齐(图 3,第 70 行),如果你还传递了 MAP_FIXED 标志,那么你必须首先验证指定的地址范围是否为空(图 3 中未显示)。

Persistent Memory Programming on Conventional Hardware

图 5 在虚拟地址空间中查找大的空隙

 

始终如一地以恒定地址映射持久堆允许你以熟悉且方便的方式使用传统的 C 风格指针。如图 2 和图 3 的程序所示,它还允许通过简单地在指针下方滑动持久堆来重用现有的基于指针的库。因此,恒定地址堆使持久内存编程能够利用为普通临时内存设计的海量现有软件。然而,不灵活的映射除了已经提到的缺点之外,还有其他缺点。独立管理的应用程序之间共享数据变得困难:如果碰巧两个人都创建了必须映射到重叠虚拟地址范围的持久堆文件,他们就无法在单个进程中同时使用这两个文件。

总而言之,如果持久堆要像传统堆一样对进程是私有的,那么为了绝对指针的优势而付出以恒定地址映射的小代价可能是值得的。但是,如果要跨独立应用程序共享持久数据(即,如果数据类似于数据库而不是堆),则可重定位的数据结构是合适的。

可重定位的持久数据结构使用相对偏移量而不是绝对地址指针。偏移量是相对于映射后备文件的基地址计算的,现在允许该基地址在程序的每次运行之间变化。用偏移量替换指针消除了以恒定地址一致地映射持久数据的需要,并且使在不同应用程序和用户之间共享持久数据变得容易。偏移量的缺点是它们与现有的基于指针的库(例如,C++ STL)不兼容,并且它们缺乏指针的熟悉性、便利性和类型安全性。

图 6 和图 7 展示了一个 C 程序,该程序在可重定位的持久堆之上构建了一个二叉搜索树。与之前的 C++ 示例一样,此程序计算输入单词的频率。图 6 的通用持久堆基础设施与图 2 的基础设施大致相似,但映射地址不再存储在标头中(第 18 行),并且添加了函数 o2p() 以将偏移量转换为指针(第 43 行)。图 7 包含一个自制的二叉搜索树,该树从图 6 的持久堆中分配基于可重定位偏移量的数据结构。手动编码基于偏移量的数据结构通常比这个简单的示例所暗示的更令人讨厌;偏移量和指针之间的转换会扰乱编程的自然流程。此外,必要的指针强制转换会损害类型检查。精心设计的 C++ 库可以隐藏偏移量的许多麻烦,如主要的 NVM 特定库集合中所示。8 虽然语法糖可以使基于偏移量的编程看起来更自然,但与现有基于指针的软件失去兼容性仍然是为可重定位性付出的巨大代价。

无论使用偏移量还是指针,持久内存编程都会施加一些新的规则和指南。避免从持久内存到普通临时内存的引用(例如,在持久堆上分配的 struct 包含指向堆栈或传统堆的指针)。此类引用是错误的滋生地,因为持久内存通常比易失性内存寿命更长;全局变量、外部变量和静态变量也会因类似原因引起问题。必须谨慎使用 C++,以免对象包含不可见的函数指针 (vtbl)。

多线程带来了额外的规则。嵌入在持久数据中的互斥锁不会在程序重启后重置;最好将索引嵌入到临时内存中的互斥锁数组中,因为后者可以在进程启动时轻松重置。对于需要更精细初始化的同步原语,已经设计了更复杂的重置机制。9msync() 调用期间,任何线程都不得修改内存映像;否则,会发生数据竞争,这在 C、C++ 和 POSIX 中是非法的,并且会导致后备文件处于不确定状态。

 

Persistent Memory Programming on Conventional Hardware

图 6 可重定位的持久搜索树,第 1 部分:持久堆

Persistent Memory Programming on Conventional Hardware

图 7 可重定位的持久搜索树,第 2 部分

持久内存编程容易出现开发人员必须缓解的新风险。由于应用程序软件直接操作持久数据,因此常见的 C/C++ 错误的爆炸半径会扩大。持久内存还会加剧所谓的“模式演化”问题:如果应用程序需求发生变化,指示必须向现有 struct 添加新字段,则将需要对持久堆进行广泛的更改,因为数据结构紧密地打包在堆中。开发人员可以通过在数据结构中填充未使用的空间以预测未来的扩展来避免或推迟此问题,但在最坏的情况下,他们必须编写重新格式化代码。如果持久堆迁移到具有例如不同指针大小或不同字节序的新硬件,也必须编写重新格式化代码。

基于 mmap() 的持久内存的最大问题是,诸如断电、操作系统内核崩溃和应用程序进程崩溃之类的故障可能会破坏持久数据。标准 POSIX mmap() 及其在类 Unix 操作系统上的实现允许操作系统随时以任何顺序将内存映像的已修改页面写入后备文件。因此,在更新持久数据期间发生的崩溃可能会使后备文件处于不一致状态,从而违反应用程序级别的固定条件和正确性标准,从而使恢复变得不可能。

 

容忍故障

即使在存在故障的情况下也能原子地运行的更新机制可以防止不适时的崩溃破坏持久数据。然后,应用程序可以将持久数据从一个一致状态原子地演化到下一个状态;在崩溃之后,保证恢复从一致的数据开始。故障原子性 msync() (FAMS) 保证,无论崩溃如何,内存映射下的后备文件始终反映最近一次成功的 msync()。FAMS 非常适合基于 mmap() 的持久内存编程,因为它只是限制了标准系统调用的行为,因此很容易推理。FAMS 也非常适合传统硬件:虽然 NVM 特定的原子性机制以机器字或缓存行粒度跟踪和记录内存修改,3,4,8,19 但 FAMS 在内存页(通常为 4 KiB)上运行,类似于块寻址存储设备的更新粒度。

故障原子性 msync() 可以容忍故障停止型故障,例如断电、操作系统内核崩溃和应用程序进程崩溃。它不是魔法;直接破坏后备文件的硬件或软件错误不会被容忍。除非调用 FAMS,否则破坏内存映像的错误不会破坏后备文件。如果在调用 FAMS 之前程序检测到内存中的损坏,然后避免调用它,则后备文件的完整性不受影响。因此,明智的做法是在调用 FAMS 之前尽可能彻底地检查应用程序级别的固定条件和正确性标准。

在持久内存编程中使用 FAMS 的规则很容易掌握:当且仅当占用文件支持的内存映射的内存映像的持久应用程序数据是一致的时,程序员才可以调用故障原子性 msync()。例如,允许的 FAMS 调用位置在图 3(第 94 和 96 行)和图 7(第 94 和 96 行)中用注释指示。如果 FAMS 实现用于内存映射文件,则后备文件仅在调用 FAMS 时更新,因此在示例程序中指示的位置之一需要 FAMS 调用。从历史上看,未能正确配对调用——malloc()/free()、lock()/unlock()、open()/close()——一直是错误的滋生地。幸运的是,FAMS 不涉及成对的“开始事务”/“结束事务”调用;连续 FAMS 调用之间的每个间隔都定义了一个原子更新束。最后,在多线程程序中,与传统的 msync() 一样,在 FAMS 调用期间,任何线程都不得修改内存映像。

银行帐户之间转账的经典示例显示了何时可以(不)调用 FAMS,并说明了检查固定条件的谨慎做法

1. sum = acct[A] + acct[B];
2. acct[A] -= 10;
3. failure_atomic_msync();             /* 错误! */
4. acct[B] += 10;
5. assert(acct[A] + acct[B] == sum);
6. failure_atomic_msync();             /* 正确 */

在帐户 A 减少和帐户 B 增加之间调用 FAMS 是一个错误,它允许崩溃(例如,在第 3 行和第 4 行之间)来销毁资金,因为“资金守恒”固定条件不成立。相反,在增加帐户 B 后调用 FAMS 是允许的,这会恢复固定条件。

虽然对于上面的简单示例来说有点过分,但在调用 FAMS 之前强烈建议检查固定条件。使用普通断言是安全的,因为如果正确使用 FAMS,则整个程序可以容忍在其执行过程中的任何点发生的崩溃(或断言失败)。对于经验丰富的开发人员来说,FAMS 学习曲线中最困难的方面之一是“自信地崩溃”。拥抱 FAMS 启用的安全崩溃的程序员通常可以通过避免边界情况恢复和异常代码来编写更简单的软件(有关指南和示例,请参阅 Yoo 等人,第 3.3 节,21)。

故障原子性 msync() 至少可以通过三种方式实现:在操作系统、文件系统或用户空间库中。FAMS 在底层持久存储系统方面是完全灵活的,因为这些实现都没有对存储层施加约束或要求;存储只是一个占位符。选项包括单个 HDD 或 SSD、网络存储阵列和地理复制的云存储——甚至是配置为公开块存储接口的 NVM 设备。这种灵活性允许 FAMS 应用程序的用户和操作员根据成本、性能、容量、可用性和其他考虑因素的要求定制存储选择,而无需对应用程序进行任何更改。对于将数据存储在文件中的库实现,相同的灵活性适用于文件系统的选择。

自 2011 年以来,我的同事和我已经多次实现了 FAMS,并在两种商业产品中部署了它。第一个实现嵌入在名为 Ken 的容错分布式计算库中。11,21 Ken 的用户空间 FAMS 后来成为惠普 Indigo 系列大批量印刷机中崩溃容错的基础。1 我们的下一个 FAMS 实现是 Linux 内核中的原型。14 惠普的存储部门在 AdvFS(高级文件系统)中实现了 FAMS 的概括,AdvFS 是一种用于存储设备产品的 Linux 文件系统。17 学术界的一个小组独立地在为混合 NVM/DRAM 内存设计的文件系统中实现了 FAMS。20

由于存储设备和容错机制经常达不到设计师的期望和用户的期望,22,23 我的同事和我广泛测试了我们的 FAMS 实现。我们使用“崩溃点”检测了源代码的关键部分,这允许测试框架在每对分号之间有条不紊地使软件崩溃。我们还通过“kill -ABRT”以及通过使用 Linux 上 /proc/ 伪文件系统公开的接口注入内核崩溃,在随机时间从外部注入崩溃。最重要的是,所有实现都经受了突然的全系统电源中断,这比任何其他类型的测试都更严格地强调了整个硬件/软件栈。电源故障测试产生了迄今为止最有用的和最有趣的信息。例如,一轮涉及五个 SSD 的测试显示,两个丢失了数据,一个因断电而变成昂贵的镇纸。14 无论使用哪种容错机制,开发人员的正确格言是像战斗一样训练:测试应该使整个硬件/软件栈经受必须在生产环境中容忍的全部故障。

广泛的经验表明,将基于 FAMS 的崩溃容错改造到未针对 FAMS 设计的复杂软件上可能非常容易。更改 Kyoto Tycoon 键值服务器中一行代码就足以将其非崩溃容错模式的操作升级为基于 Linux 内核 FAMS 的崩溃安全模式;此外,基于 FAMS 的崩溃容错被证明明显快于 Tycoon 的本机事务机制。14 使用 AdvFS 中的 FAMS 实现对 SQLite 的非崩溃容错模式执行类似的升级需要更改两行代码,并且性能提高了更大的幅度。18 在控制 HP Indigo 印刷机的复杂、性能优化的软件下使用用户空间 FAMS 需要单个开发人员付出适度的努力,并将崩溃恢复时间从几天减少到几分钟。1 分层在 FAMS 之上的简单持久堆允许其他开发人员将崩溃安全性改造到 Scheme 解释器5 和 JavaScript 解释器16 上。

FAMS 的性能在很大程度上取决于实现(操作系统、文件系统或库)和底层持久存储介质;详细的性能评估在上面引用的参考文献中给出。幸运的是,即使不知道哪种存储系统将提供持久性,也很容易推理 FAMS 性能的基础知识:FAMS 调用的性能成本大约是一个恒定的开销加上与自上次调用以来修改的内存页数成比例的额外成本。任何实现都必须在每个 FAMS 调用(恒定开销)和每个修改的内存页(线性项)上执行固定量的工作。注重性能的程序员避免不必要的页面修改并谨慎地调用 FAMS。

FAMS 性能的最佳点同样容易描述:持久数据的总大小应该很大;如果它很小,则 FAMS 的增量操作是过度的,并且读取文件、在内存中更改它并将其写入新文件的简单权宜之计就足够了。在连续 FAMS 调用之间更改的内存页面的比例应该很小;否则,FAMS 的增量性是无益的,你还不如简单地将整个内存映像写入存储。在 FAMS 调用之间修改的页数应该很大;否则,FAMS 调用的固定成本可能会支配线性项。最后,对每个修改的内存页面的修改次数应该很大,因为仅仅为了翻转页面上的单个位而支付日志记录的开销是浪费的。像计算和编程的许多其他方面一样,基于 FAMS 的持久内存奖励访问局部性。

在撰写本文时,至少有两个新的 FAMS 实现正在进行中。第一个是在 XFS 文件系统中的实现。6 像早期的 HP AdvFS FAMS 实现一样,17 XFS 实现有望比用户空间库更有效,因为它直接对文件系统数据结构进行操作,从而避免了日志记录的双重写入。XFS 实现基本上已经完成,并且计划进行彻底的测试。

第二个正在进行的 FAMS 实现是一个名为 famus(用户空间中的故障原子性 msync())的库,我正在编写它。虽然我早期的用户空间 FAMS11 通过捕获 SIGSEGV(段错误信号)来检测修改的内存页,并使用 REDO 日志记录来实现原子性,但 famus 使用 Linux“软脏位”来检测内存修改,并使用 UNDO 日志记录来实现原子性。用户空间 FAMS 中 UNDO 日志记录的一个附带好处是数据版本控制:每个 FAMS 调用都定义了应用程序持久数据的版本,并且可以通过将 UNDO 日志应用于后备文件来恢复以前的版本。如果不需要版本控制,则可以在成功完成创建它的 FAMS 调用后删除每个 UNDO 日志。无论日志是否保留或删除,后备文件始终包含持久数据的最新 FAMS 提交状态。

优化减少了 famus 日志的大小。通常,famus 为在连续 FAMS 调用之间修改的每个内存页面执行两次持久页面写入:页面的先前版本从后备文件写入 UNDO 日志,修改后的页面从内存写入后备文件。在两种情况下,famus 优化掉了写入 UNDO 日志的页面写入:首先,如果页面仅包含零字节,则日志仅记录页面 ID 并将其标记为空页面,省略了 4 KiB 的零;其次,如果“修改后的”页面与其在上次 FAMS 调用时的状态逐字节完全相同,则可以安全地忽略该页面。

第一个优化有助于随着最初稀疏的后备文件逐渐填充非零页面;如果没有第一个优化,UNDO 日志将包含许多零页面。第二个优化适用于一种令人惊讶的广泛但很少被注意到的现象:STORE 指令用相同的内容覆盖内存。例如,实际的输入导致图 7 中几乎每次调用 tree_insert() 都会用已经存在的值覆盖内存字。当使用 famus 运行此类程序时,第二个优化显着减小了 UNDO 日志的大小。

在撰写本文时,我认为 famus 是完整且正确的,尽管尚未像之前的 FAMS 实现那样对其进行彻底测试。初步版本可在 http://web.eecs.umich.edu/~tpkelly/famus/ 获取

 

总结与展望

结合一些容易掌握的技术和习惯用法,mmap() 支持持久内存的硬件无关的软件抽象。当以优雅的方式并以合理的判断力实践时,持久内存编程风格可以显着简化应用程序软件。如果需要崩溃容错,故障原子性 msync() 抽象允许各种各样的实现,所有这些实现都为底层持久存储介质提供了不受限制的灵活性。经验表明,FAMS 可以使将崩溃容错改造到复杂的遗留软件上变得非常容易。至少已经开发了四种 FAMS 实现,其中两种已并入商业产品。两种新的实现即将完成。

先前的工作已经广泛但并非详尽地探索了 FAMS 实现。一种新的用户空间设计可能会使用现有的 Linux userfaultfd() 接口跟踪内存修改;Windows GetWriteWatch() 接口的 Linux 实现将是一个更好的起点。FAMS 的另一个可能的基础是通过 BtrFS 和 XFS 等文件系统中的 ioctl(FICLONE) 进行每文件快照。类 Unix 操作系统上文件支持的内存映射的 mlock() 的更强语义将极大地促进用户空间 FAMS。最后,仍然需要编写为 FAMS 性能成本模型(每次 msync() 调用之间修改的每个页面支付 1 美元)设计的复杂持久内存分配器。

考虑到传统指针的便利性和兼容性,需要更好地支持应用程序指定的地址的内存映射。启用对此类映射的自信和便携式使用仅需要一个标准化的共识,即应用程序映射在地址空间的保留区域中具有最高优先级;链接器、传统内存分配器以及其他需要未使用地址空间的组件仅同意仅将该区域作为最后的手段使用,从而允许应用程序首先占用它。当需要可重定位的数据结构时,Microsoft C/C++ “基于指针”语言扩展很有帮助;Unix 编译器中的类似功能将是一个受欢迎的改进。改进的编译器对控制虚拟地址布局的支持也将有所帮助。例如,程序员应该能够强制变量和数组的页面对齐,从而可以轻松地在它们下映射文件。

持久内存编程风格对于类型和内存安全的语言与对于 C/C++ 同样有用。未来的努力可能会从 Scheme5 和 JavaScript16 的容错解释器的研究原型中汲取灵感,这些原型使应用程序能够使用普通变量而不是外部数据库来存储持久数据。JavaScript 是一个特别有希望的候选者,因为它具有事件驱动的特性;每个事件的处理都只是变成一个微型事务(例如,通过以故障原子性 msync() 调用结束它)。

在字节寻址非易失性内存的推动下,持久内存编程风格将在开发人员中获得吸引力,并在管理持久应用程序状态的现有范例中占据其应有的位置。在 NVM 在所有计算机上可用之前,开发人员可以使用本文中介绍的技术来享受传统硬件上持久内存编程的好处。

 

参考文献

1. Blattner, A., Dagan, R., Kelly, T. 2013. Indigo 及更远领域的通用崩溃弹性存储。惠普实验室技术报告 HPL-2013-75; http://www.hpl.hp.com/techreports/2013/HPL-2013-75.pdf

2. Bloch, J. 2006. 特别报道——阅读所有关于它的内容:几乎所有的二分查找和归并排序都已损坏。Google AI 博客(6 月); https://ai.googleblog.com/2006/06/extra-extra-read-all-about-it-nearly.html

3. Chakrabarti, D., Boehm, H., Bhandari, K. 2014. Atlas:利用锁来实现非易失性内存一致性。在面向对象系统语言和应用程序 国际会议 (OOPSLA) 会议记录中,433-452; https://dl.acm.org/citation.cfm?id=2660224http://www.hpl.hp.com/techreports/2013/HPL-2013-78R1.pdf

4. Coburn, J., Caulfield, A., Akel, A., Grupp, L., Gupta, R., Jhala, R., Swanson, S. 2011. NV-heaps:使用下一代非易失性内存使持久对象快速且安全。在第 16 届编程语言和操作系统架构支持国际会议 (ASPLOS) 会议记录中,118; https://dl.acm.org/citation.cfm?id=1950380

5. Harnie, D., De Koster, J., Van Cutsem, T. SchemeKen:一个崩溃弹性的 Scheme 解释器; https://github.com/tvcutsem/schemeken

6. Hellwig, C. 2019. Linux 的故障原子性文件更新。即将发表于 Linux Piter(10 月); https://linuxpiter.com/en/materials/2307; 补丁:https://www.spinics.net/lists/linux-xfs/msg04536.htmlhttp://git.infradead.org/users/hch/vfs.git/shortlog/refs/heads/O_ATOMIC

7. 英特尔。英特尔傲腾技术; http://www.intel.com/optane/

8. 英特尔。持久内存开发工具包; http://pmem.io/pmdk/

9. 英特尔. 2017. NVM 的同步原语。 http://pmem.io/2016/05/31/cpp-08.html; http://pmem.io/2015/06/18/threads.html

10. Izraelevitz, J., Yang, J., Zhang, L., Memaripour, A., Soh, Y. J., Dulloor, S. R., Zhao, J., Kim, J., Liu, X., Wang, Z., Xu, Y., Swanson, S. 2019. 英特尔傲腾 DC 持久内存模块的基本性能测量。(四月); https://arxiv.org/abs/1903.05714

11. Kelly, T. Ken:一个用于容错分布式计算的平台; http://ai.eecs.umich.edu/~tpkelly/Ken/

12. Nanavati, M., Schwarzkopf, M., Wires, J., Warfield, A. 2015. 非易失性存储。《》13(9); https://queue.org.cn/detail.cfm?id=2874238

13. Neward, T. 2006. 对象/关系映射:计算机科学的越南。《Ted Neward's Blog》; http://blogs.tedneward.com/post/the-vietnam-of-computer-science/

14. Park, S., Kelly, T., Shen, K. 2013. Failure-atomic msync():一种用于保持持久数据完整性的简单高效机制。在《欧洲计算机系统会议 (EuroSys) 论文集》中,225-238; https://dl.acm.org/citation.cfm?id=2465374; http://web.eecs.umich.edu/~tpkelly/papers/Failure_atomic_msync_EuroSys_2013.pdf

15. Rudoff, A. 2017. 持久内存编程。《;login;》42(2), 34-40; https://www.usenix.org/system/files/login/articles/login_summer17_07_rudoff.pdf

16. Van Ginderachter, G. V8Ken:一个崩溃恢复的 JavaScript 解释器; https://github.com/supergillis/v8-kenhttp://web.eecs.umich.edu/~tpkelly/Ken/V8Ken_value_proposition.html

17. Verma, R., Mendez, A. A., Park, S., Mannarswamy, S., Kelly, T., Morrey, B. 2015. Linux 文件系统中应用程序数据的 Failure-atomic 更新。在《第 13 届 Usenix 文件和存储技术会议 (FAST) 论文集》中; https://www.usenix.org/system/files/conference/fast15/fast15-paper-verma.pdf

18. Verma, R., Mendez, A. A., Park, S., Mannarswamy, S., Kelly, T., Morrey, B. 2015. SQLean:通过原子文件更新加速数据库。技术报告 HPL-2015-103,惠普实验室; http://www.labs.hpe.com/techreports/2015/HPL-2015-103.pdf

19. Volos, H., Tack, A. J., Swift, M. 2011. Mnemosyne:轻量级持久内存。在《第 16 届国际编程语言和操作系统体系结构支持会议 (ASPLOS) 论文集》中,91-104; https://dl.acm.org/citation.cfm?id=1950379

20. Xu, J., Swanson, S. 2016. NOVA:用于混合易失性/非易失性主内存的日志结构文件系统。在《第 14 届 Usenix 文件和存储技术会议 (FAST) 论文集》中; https://www.usenix.org/system/files/conference/fast16/fast16-papers-xu.pdf

21. Yoo, S., Killian, C., Kelly, T., Cho, H. K., Plite, S. 2012. 异步系统的可组合可靠性。Usenix 年度技术会议 (ATC); https://www.usenix.org/system/files/conference/atc12/atc12-final206-7-20-12.pdf

22. Zheng, M., Tucek, J., Huang, D., Qin, F., Lillibridge, M., Yang, E. S., Zhao, B. W., Singh, S. 2014. 为了乐趣和利益而折磨数据库。在《第 11 届 Usenix 操作系统设计与实现研讨会 (OSDI) 论文集》中; https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-zheng_mai.pdf. (请注意,勘误表另行提供。)

23. Zheng, M., Tucek, J., Qin, F., Lillibridge, M. 2013. 了解 SSD 在电源故障下的鲁棒性。在《第 11 届 Usenix 文件和存储技术会议 (FAST) 论文集》中; https://www.usenix.org/system/files/conference/fast13/fast13-final80.pdf

 

相关文章

实践研究
分布式共识以及 NVM 对数据库管理系统的影响

专家策划的 CS 研究最佳指南
https://queue.org.cn/detail.cfm?id=2967618

The Morning Paper
SageDB 和 NetAccel

数据库系统中的学习模型;网络加速的查询处理
Adrian Colyer
https://queue.org.cn/detail.cfm?id=3317289

 

Terence Kelly [email protected] 是 的杰出会员和终身会员。他于 2002 年在密歇根大学安娜堡分校获得计算机科学博士学位。Kelly 在惠普实验室工作了 14 年;在 HPL 的最后五年中,他开发了非易失性内存的软件支持。Kelly 现在教授并宣传持久内存编程。他的专利和出版物列在 http://ai.eecs.umich.edu/~tpkelly/papers/

 

版权 © 2019 由所有者/作者持有。出版权已授权给 。

acmqueue

最初发表于《Queue》杂志第 17 卷,第 4 期
数字图书馆 中评论这篇文章








© 保留所有权利。

© . All rights reserved.