如何使用英特尔® Inspector - Persistence Inspector 检测持久内存编程错误

概述

持久内存一种新兴的内存存储技术,在提高应用性能和可靠性方面具有很大潜力。但开发人员需要解决几个编程挑战,以获得性能最佳的代码。其中一个挑战是,由于缓存问题,存储到持久内存的内容无法立即实现持久化。数据只有在离开缓存层级结构且对内存系统可见时,才能持久存留。由于处理器无序执行和缓存,持久性的顺序可能与存储的顺序不同。

Intel® Inspector - Persistence Inspector是一种新的运行时工具,目前提供技术预览版。开发人员可以用它来检测持久内存程序中的这些编程错误。除了缓存刷新未命中之外,该工具还可以检测

  • 冗余缓存刷新和内存屏障
  • 无序持久内存存储
  • 持久内存开发工具包 (PMDK) 的撤销日志错误

本文将介绍英特尔英特尔 Inspector - Persistence Inspector 的功能并提供入门指南。

背景

持久内存设备使用英特尔和美光科技* 联合开发的新技术(如 3D Xpoint™ 介质),可直接连接到内存控制器。这样的设备通常被称为非易失性双列直插式内存模块 (NVDIMM)。NVDIMM 中的数据支持字节寻址,可在系统或程序崩溃后保留下来。NVDIMM 的访问延迟与 DRAM 相当。程序使用常规的 CPU 加载/存储指令读取和写入 NVDIMM。考虑下面的代码示例:

 

示例 1:将通讯录写入持久内存

#include <stdio.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <string.h>

struct address {
	char name[64];
	char address[64];
	int valid;
}

int main()
{
	struct address *head = NULL;
	int fd;
	fd = open("addressbook.pmem", O_CREAT|O_RDWR, 0666);
	posix_fallocate(fd, 0, sizeof(struct address));

	head = (struct address *)mmap(NULL, sizeof(struct address), PROT_READ|PROT_WRITE,    MAP_SHARED, fd, 0);
    close(fd);

    strcpy(head->name, "Clark Kent");
	strcpy(head->address, "344 Clinton St, Metropolis, DC 95308");
	head->valid = 1;

    munmap(head, sizeof(struct address));

	return 0;
}

在示例 1 中,持久内存作为 addressbook.pmem 文件公开,并使用常规文件系统 API 映射到进程地址空间。一旦映射到持久内存,程序就会直接访问内存,并开始调用 strcpy。如果再调用 munmap 之前断电,持久内存可能会发生下列情形之一:

  • head->namehead->addresshead->valid都没有进入内存系统并持久留存。
  • 它们全部都持久留存。
  • 其中一个或两个持久留存。

缓存效应给持久内存软件开发带来了挑战。为确保数据在断电或系统崩溃后可恢复并保持一致,开发人员需要确定何时何地将缓存层级结构的数据明确刷新到内存系统。

持久内存应用行为

在发生断电或系统崩溃等不幸事件后重启时,持久内存应用必须验证一致性并对内存中存储的数据进行恢复。通常情况下,不幸事件会将持久内存应用分为两个阶段:一个是不幸事件之前的阶段,另一个是不幸事件之后的阶段。这可能会导致数据损坏或不一致。在不幸事件之前的阶段,应用运行读取和写入持久内存的正常流程。在不幸事件之后的阶段,系统检查数据一致性并在应用恢复正常运行之前将不一致的数据恢复到一致性状态。

 

Map of  memory unfortunate event

如何使用英特尔 Inspector - Persistence Inspector

这是英特尔 Inspector - Persistence Inspector 的使用流程:

A flowchart, usage workflow of Intel® Inspector

设置环境

将工具文件安装到所选目录(例如/home/joe/pmeminsp)后,将英特尔 Inspector - Persistence Inspector 路径添加到 PATHLD_LIBRARY_PATH 环境变量。例如:

$ export PATH=/home/joe/pmeminsp/bin64:$PATH

$ export LD_LIBRARY_PATH=/home/joe/pmeminsp/lib64:$LD_LIBRARY_PATH 要验证该工具是否正确安装和设置,可以键入“pmeminsp”。

$ pmeminp 键入 'pmeminsp help’ 以便使用。

准备应用

如前所述,持久内存应用通常由两个阶段组成。若要使用英特尔 Inspector -Persistence Inspector,您需要确定这两个阶段的代码。

  • 不幸事件之前的阶段。执行您希望工具检查的代码。
  • 不幸事件之后的阶段。执行您将在断电或系统崩溃后运行的代码。

如果您的应用使用 持久内存开发工具包 (PMDK) 实现对事务的持久内存支持,则 PMDK 事务运行时支持负责事务块内的数据一致性和恢复。不幸事件后的代码驻留在 PMDK 事务运行时中。因此,无需使用英特尔 Inspector - Persistence Inspector 验证该特定阶段。

通知英特尔® Inspector - Persistence Inspector 不幸事件之后的阶段

一旦确定不幸事件之后的阶段,我们强烈建议您将不幸事件之后阶段的确切开始时间和结束时间通知工具,以大幅缩短分析时间。英特尔 Inspector - Persistence Inspector 提供一套面向应用的 API,用于在不幸事件之后阶段开始和结束时通知工具。

#define PMEMINSP_PHASE_AFTER_UNFORTUNATE_EVENT 0x2 void __pmeminsp_start(unsigned int phase); void __pmeminsp_pause(unsigned int phase); void __pmeminsp_resume(unsigned int phase); void __pmeminsp_stop(unsigned int phase);

若要将不幸事件之后阶段的开始时间通知工具,您只需在不幸事件之后阶段开始之前,在应用中调用 __pmeminsp_start (PMEMINSP_PHASE_AFTER_UNFORTUNATE_EVENT)。同样,若要将不幸事件之后阶段的结束时间通知工具,您只需在不幸事件之后阶段结束后,在应用中调用__pmeminsp_stop(PMEMINSP_PHASE_AFTER_UNFORTUNATE_EVENT)

__pmeminsp_pause(PMEMINSP_PHASE_AFTER_UNFORTUNATE_EVENT) and __pmeminsp_resume (PMEMINSP_PHASE_AFTER_UNFORTUNATE_EVENT) 调用可让您更精细地控制分析开始之后和结束之前的分析暂停和恢复。

例如,如果不幸事件之后阶段是函数 recover() 调用的持续时间,您只需在函数入口放置 __pmeminsp_start(PMEMINSP_PHASE_AFTER_UNFORTUNATE_ EVENT),并在函数出口放置 __pmeminsp_stop(PMEMINSP_PHASE_AFTER_ UNFORTUNATE_EVENT)即可。

示例 2:将不幸事件之后阶段的开始时间和结束时间通知英特尔 Inspector - Persistence Inspector。

#include “pmeminsp.h”

 …

 … …

void recover(void)
{
    __pmeminsp_start(PMEMINSP_PHASE_AFTER_UNFORTUNATE_EVENT);

    …

    __pmeminsp_stop((PMEMINSP_PHASE_AFTER_UNFORTUNATE_EVENT);
}

void main()
{

    …

    … = mmap(…..);

    __pmeminsp_stop((PMEMINSP_PHASE_AFTER_UNFORTUNATE_EVENT);

    …
    recover();
}

英特尔 Inspector - Persistence Inspector API 在libpmeminsp.so 中定义。当您构建应用时,确保指定正确的选项。例如

-I /home/joe/pmeminsp/include –L /home/joe/pmeminsp/lib64 –lpmeminsp.

分析不幸事件之前的阶段

运行不幸事件之前分析阶段的命令为

pmeminsp check-before-unfortunate-event [options] --

如果应用使用系统 API 直接映射持久内存文件,您还需要使用 option -pmem-file 指定该文件的路径(即使该文件在应用运行之前不存在)。例如:

pmeminsp check-before-unfortunate-event -pmem-file ./addressbook.pmem [options] --

如果应用创建了多个文件,应该可以使用这些文件指定文件夹的路径。从该位置映射的任何文件都将被解析为持久内存文件。

请注意,如果您的应用是基于 PMDK 的,那么这两个选项是多余的。英特尔 Inspector - Persistence Inspector 会自动跟踪由 PMDK 管理的所有持久内存文件,即使这些选项不存在。

分析不幸事件之后的阶段

运行不幸事件之后分析阶段的命令为

pmeminsp check-after-unfortunate-event [options] --

确保指定持久内存文件的位置,除非应用使用 PMDK 来运行持久内存。选项名称与 check-before-unfortunate-event 命令类似。

检测到报告问题

要生成检测到的持久内存问题的报告,请运行

pmeminsp report [option] --

以下是英特尔 Inspector - Persistent Inspector 生成的一些诊断示例:

缺少缓存刷新

持久内存存储(第一个缓存)的缺失缓存刷新始终参照后面的持久内存存储(第二个存储)。其潜在的不利影响是,如果在第二个存储之后发生不幸事件,第二个存储将成为持久存储,但第一个存储不会成为持久存储。

第一个内存存储

in /home/joe/pmeminsp/addressbook/writeaddressbook!main at writeaddressbook.c:24 - 0x6ED, in /lib/x86_64-linux-gnu/libc.so.6!__libc_start_main, at: - 0x21F43 in /home/joe/pmeminsp/addressbook/writeaddressbook!_start at: - 0x594

,

未在第二个内存存储之前刷新

in /home/joe/pmeminsp/addressbook/writeaddressbook!main at: writeaddressbook.c:26 - 0x73F, in /lib/x86_64-linux-gnu/libc.so.6!__libc_start_main, at: - 0x21F43, in /home/joe/pmeminsp/addressbook/writeaddressbook!_start at: - 0x594

.

内存从第一个存储的位置加载

in /lib/x86_64-linux-gnu/libc.so.6!strlen at: - 0x889DA

取决于从第二个存储的位置加载的内存

in /home/joe/pmeminsp/addressbook/readaddressbook!main at readaddressbook.c:22 - 0x6B0.

冗余或不必要的缓存刷新

冗余或不必要的缓存刷新可以从分析的执行路径中删除,但不影响程序的正确性。尽管冗余或不必要的缓存刷新不会影响程序的正确性,但它可能会影响程序性能。

缓存刷新

in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!test_redundant_flush at main.cpp:134 - 0x1721, in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!create_data_file at main.cpp:52 - 0x151F,in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!create_data_file at main.cpp:48 - 0x14FD,in /home/joe/pmeminsp//tests/pmemdemo/src/pemmdemo!main at main.cpp:231 - 0x1C74,in /lib/x86_64-linux-gnu/libc.so.6!__libc_start_main, at: - 0x21F43,in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!_start at: - 0x12A4

在缓存刷新方面是冗余的

in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!test_redundant_flush at main.cpp:135 - 0x1732,in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!create_data_file at main.cpp:52 - 0x151F,in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!create_data_file at main.cpp:48 - 0x14FD,in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!main at main.cpp:231 - 0x1C7,in /lib/x86_64-linux-gnu/libc.so.6!__libc_start_main at: - 0x21F43,in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!_start at: - 0x12A4

内存存储

in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!test_redundant_flush at main.cpp:133 - 0x170F,in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!create_data_file at main.cpp:52 - 0x151F,in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!create_data_file at main.cpp:48 - 0x14FD,in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!main at main.cpp:231 - 0x1C74,in /lib/x86_64-linux-gnu/libc.so.6!__libc_start_main at: - 0x21F43,in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!_start at: - 0x12A4

无序持久内存存储

无序持久内存存储是无法执行正确持久顺序的两个存储。值得指出的是,显式高速缓存刷新不会执行正确的顺序。
无序持久内存存储可以通过 'report’ 命令中的查看无序存储 (check-out-of-order-store) 选项进行启用。

内存存储

in /home/joe/pmemcheck/mytest/writename6!writename at writename6.c:13 - 0x6E0, in /home/joe/pmemcheck/mytest/writename6!main at writename6.c:21 - 0x72D, in /home/joe/pmemcheck/mytest/writename6!main at writename6.c:20 - 0x721, in /lib/x86_64-linux-gnu/libc.so.6!__libc_start_main at: - 0x21F43,in /home/joe/pmemcheck/mytest/writename6!_start at: - 0x594

应在内存存储之后

in /home/joe/pmemcheck/mytest/writename6!writename at writename6.c:14 - 0x6EB,in /home/joe/pmemcheck/mytest/writename6!main at writename6.c:21 - 0x72D,in /home/joe/pmemcheck/mytest/writename6!main at writename6.c:20 - 0x721, in /lib/x86_64-linux-gnu/libc.so.6!__libc_start_main at: - 0x21F43,in /home/joe/pmemcheck/mytest/writename6!_start at: - 0x594

不撤销记录更新

开发人员负责在 PMDK 事务中更新内存位置之前撤消其日志记录。开发人员通常调用 PMDK 函数 pmemobj_tx_add_range()pmemobj_tx_add_range_direct(),,或使用宏 TX_ADD() 撤消内存位置的日志记录。如果内存位置在更新之前未被撤消记录,那么如果未成功提交事务,则 PMDK 无法将变更回滚到内存位置。

有用户报告无撤消记录更新的问题,并提到内存存储和更新内存的事务。

内存存储

in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!test_tx_without_undo at main.cpp:190 - 0x1963,in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!run_tx_test at main.cpp:175 - 0x1877,in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!run_tx_test at main.cpp:163 - 0x180D,in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!main at main.cpp:245 - 0x1CF4,in /lib/x86_64-linux-gnu/libc.so.6!__libc_start_main at: - 0x21F43,in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!_start at: - 0x12A4

事务未撤销记录

in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!test_tx_without_undo at main.cpp:185 - 0x1921,in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!run_tx_test at main.cpp:175 - 0x1877,in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!run_tx_test at main.cpp:163 - 0x180D,in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!main at main.cpp:245 - 0x1CF4,in /lib/x86_64-linux-gnu/libc.so.6!__libc_start_main at: - 0x21F43, in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!_start at: - 0x12

无更新的日志撤销

开发人员负责在 PMDK 事务中更新内存位置之前撤消其日志记录。开发人员通常调用 PMDK 函数 pmemobj_tx_add_range()pmemobj_tx_add_range_direct(),或使用宏 TX_ADD() 撤销内存位置的日志记录。如果内存位置被撤销记录,但从未在 PMDK 事务内更新,那么性能可能会降低。或者如果未成功提交事务,内存可能会回滚到 dirty/uncommitted/stale value

有用户报告无更新撤销记录的问题,并提到 PMDK 撤销记录调用和内存被撤销记录的交易。

Memory region is undo logged in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!test_tx_without_update  at main.cpp:190 - 0x1963, in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!run_tx_test at main.cpp:175 - 0x1877, in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!run_tx_test at main.cpp:163 - 0x180D, in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!main at main.cpp:245 - 0x1CF4, in /lib/x86_64-linux-gnu/libc.so.6!__libc_start_main at: - 0x21F43, in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!_start at: - 0x12A4

但未在事务中更新

in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo! test_tx_without_update at main.cpp:185 - 0x1921, in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!run_tx_test at main.cpp:175 - 0x1877, in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!run_tx_test at main.cpp:163 - 0x180D, in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!main at main.cpp:245 - 0x1CF4, in /lib/x86_64-linux-gnu/libc.so.6!__libc_start_main at: - 0x21F43, in /home/joe/pmeminsp/tests/pmemdemo/src/pemmdemo!_start at: - 0x12A4.

结论

持久内存是一项令人兴奋的新技术。正如我们在本文中讨论的,这一技术带来了一些编程挑战。使用英特尔 Inspector - Persistence Inspector 等工具可以帮助应对这些挑战,让您在程序生命周期的早期发现问题,并获得非常高的投资回报。

后续步骤

若要试用英特尔® Inspector - Persistent Inspector 测试版,请访问测试版注册网站 并下载技术预览版。

有关编译器优化的更完整信息,请参阅优化通知