AEP入门指北

2020/10/17

[TOC]

简介

用了一段时间AEP了,现在写一个材料给大家分享一下,从零开始的AEP入门。

AEP 与 CPU 的 iMC 相连需要 CPU 使用 load/store 指令进行读写,load/store 指令往往就牵扯到 CPU 的 memory-model,再加上使用 AEP 的时候往往是希望利用其持久化的特性的,所以要做到一个正确的设计就首先需要对CPU的memory-model(或者说指令顺序及效果)有个基本的认识。

同时目前 Intel 的 AEP 实现也相对比较复杂,看起来是只是一个新介质,但实际上是一个复合的存储系统,所以要做到一个高性能的设计还需要对 AEP 本身的架构以及内核是如何进行有一个基本的认识。

因此AEP实际上是一个比较综合的话题,我过去几个月中阅读了一些相关的资料,发现很多资料对于小白也不够友好(因为当时我也是小白),同时也结合一些我的心得,打算系统的分享出来。

整体分成下面几块

  1. 如何持久化的把一块数据写到内存

    在这个部分,先以抽象的方式假设有一段内存地址提供持久化功能,讨论如何写入一条记录,主要目的是引入memory-model相关的话题(cache结构、指令顺序)。为什么先假设有一段内存地址是持久化的,主要是想说明这部分复杂度是由于CPU本身造成的,与AEP本身的硬件无关。

    • 结合CPU内存模型简单介绍一下如何写一段数据到内存
  2. 真正的持久化内存

    通过第一部分,已经了解了一般化的如何持久化的写一条记录,在这一部分,会从最底层介质颗粒开始逐渐向上,介绍AEP的结构以及如何与系统集成的,也介绍了一些必要的特性,比如掉电保护、Interleave。对AEP结构的了解有利于理解AEP一些性能优化的方式。

    • 从介质开始简单介绍一下 AEP 100系列的硬件架构,及其特性
  3. 如何使用持久化内存

    其实AEP用起来还是蛮简单的,在这个部分我们先介绍AEP给我们提供的使用模式,以及如何通过命令行初始化一个AEP创建文件系统,同时也介绍了一下Intel提供的PMDK工具包的功能。

    • 简介DAX,简述一下一个fs-dax是怎么写入AEP的
    • 简单介绍一下PMDK是啥,各个库大概干啥的
  4. 心得&杂记

    这个部分就是介绍一下RPMA,介绍一下性能优化的考虑,以及简单介绍一下之前有个AEP的比赛

    • 简单介绍一下RDMA配合AEP使用的情况
    • 简单介绍一下性能考虑
    • 比赛

水平有限且有的地方写的确实不严谨,欢迎提出任何意见

如何持久化的把一块数据写到内存

那么我们现在假设有一段内存(地址)拥有持久化的能力(假设你能获得物理地址或者说不考虑虚拟内存,且写在 Cache 中不算写入内存)。

我们想要存储一段数据,最简单的方案就是直接 copy,如下

const char* data = "hello world";
strcpy(addr, data);

虽说内存有持久化的能力,但是 CPU 写入这些数据不是原子的,要分多次写入(通常64B/cycle), 可能在掉电的时候只有一部分写入了内存,一般我们希望写入内存的数据是失效原子性的(failure atomicity),即如果写入成功数据就是对的。

这两张图是casecade处理器的微架构,方便大家理解上面提到的几点

sf 是 snoop filter

llc是 last level cache

adpll 是 all digit phase-locked loops 调节始终频率的

fiwr 是 fully integrated voltage regulator 调节核心电压的

可以看iMC(Memory Controller)与内存相连,iMC 通过 snoop filter 与 last level cache相连

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%201.png

内存模型是在指令的层面上角度看的,对于不影响内存模型语义的指令,CPU是可能会乱序执行的,比如说CPU读取数在不同的地址,同时也没别的写入在这两个地址上,我就算真的reorder了,在指令层面还是等价于顺序执行。

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%202.png

接下来,考虑如何持久化的写入一条 record(或一条log、一段数据),一般我们是通过数据校验的方式来实现我们的需求,这里提供了两种方法。

方案一通过标志位的方式来标志数据写入成功。方案二更加一般化的使用了整体校验的方法。

// 方案一
struct Record {
    char data[N];
    bool valid;
};
const char* data = "hello world";
strcpy(record->data,  data);
fence();
cacheline_writeback();
node->valid = true;
fence();
cacheline_writeback();
// ok to return

// 方案二
struct Record {
    char data[N];
    uint32_t checksum;
};
const char* data = "hello world";
strcpy(record->data, data);
record->checksum = cal_checksum(record->data);
fence();
cacheline_writeback();
// ok to return

我们先在不涉及CPU结构的情况下进行讨论(假设乱序总是可能发生)

方案一中第一个 fence 是保证 order,再使用 cache line writeback 写回确保数据落在持久化内存中了(实际上不一定到介质中了)。这里简述一下 fence,可以理解有两种作用,1是保证order,即fence返回后,之前的写入都完成了(before is before),2是保证写入已经完成,比如在x86上大多数fence都保证写入已经全局可见,这里还要根据写入的类型来分析,有的指令是写cache有的指令是直接写内存。

方案二中则使用了一次 cache line 写回,fence 是在checksum 和 data 之后发起的,所以其实他们可以以任意顺序写到cache上,fence 返回后保证已经写入cache,随后 cacheline 写回 保证进入内存(同样不一定到介质中)

方案一判断写入是否成功是简单明了的,如果读取到 valid 为 true,说明 data 的内容是合法的。因为先保证数据写好再写的flag,flag写入则一定数据没问题。

方案二判断写入是否成功需要去用读取的数据去计算校验和,如何校验和正确则写入成功。

现在,我们更具体一点,看看可以在x86上使用哪些指令来实现上面的两种写入

那么根据是否需要该条数据仍然在缓冲中(即data跟flag写完后是否需要在cache中),方案一有如下两种方式。(方案二就是方案一的简化所以就不单独讨论了)

// 方案一的实现1 需要在 cache 中
MOV m[pmem_addr], m[addr]   // 写入数据
MOV m[pmem_addr+1], m[addr+1] 
.
.
// SFENCE  // 这里需要一个fence吗?不需要 implicitly
CLWB m[pmem_addr]           // cache line 写回
CLWB m[pmem_addr + 64]
SFENCE                      // 这里是必须的吗?如果没有的话,下面这个 MOV 可能先执行,如果恰巧flag跟data在一个cacheline就不行了
MOV m[pmem_addr + N], D1  // 写入 flag
CLWB m[pmem_addr + N]
SFENCE

// 方案而的实现2 不需要 在 cache 中
MOVNTI m[pmem_addr], m[addr]   // nt 写入数据
MOVNTI m[pmem_addr+1], m[addr+1] 
.
.
.
SFENCE // 上面 手册截图中提到了 nt 指令是write-combine,非write-back
MOVNTI m[pmem_addr + N], D1  // 写入 flag
SFENCE

对于需要在 cache 中的情况,细心的同学可能注意到了如果 strcpy 使用的是一般的 store 指令,cache-line使用的是 CLFLUSH,实际上是不需要做 fence 的(翻看最上面Intel memory order,CLFLUSH是遵循TSO的),但是需要注意的是因为使用了 CLWB ,其执行模型是非TSO的,这是由于 CLWB 的实现造成的,需要对不同顺序指令的 order 进行分析

CLWB instruction is ordered only by store-fencing operations. For example, software can use an SFENCE, MFENCE, XCHG, or LOCK-prefixed instructions to ensure that previous stores are included in the write-back. CLWB instruction need not be ordered by another CLWB or CLFLUSHOPT instruction. CLWB is implicitly ordered with older stores executed by the logical processor to the same address.

对于不需要在cache中情况,由于使用了 NT 指令,NT指令是特殊的 store 指令,使用的内存模型是 Write-Combine( weak order ),也是非TSO的,所以也需要一个 sfence 来保证不同的 NT store 间的 order

看起来很复杂,但是总结一下实际上写一段数据的模式是差不多的,就三步骤,无论是哪一种都需要 sfence (因为CLFLUSH太慢了)

func write_data(data):
    mov 写数据(nt or normal)
    sfence 
    if (not use nt store): 
        clwb
        sfence

那么我们的
方案一是
write_data(str_data)
write_data(flag_data)

方案二是
write_data(str_with_checksum_data)

真正的持久化内存

上面我们使用抽象的持久化内存分析了写入的流程,那么接下来我们看看真正的持久化内存,也就是intel的 AEP,有很多缩写意思差不多,这里列一下,后面可能不加分辨的使用

Apache Pass(AEP): Intel’s codename for Intel® Optane™ DC Persistent Memory

DCPMM:Intel® Optane™ DC Persistent Memory

Optane DIMM:也是指AEP,dual in-line memory module 指接口,因为傲腾还有走NVMe接口的产品

3D Xpoint:指的是AEP存储颗粒使用的技术

NVMEM(non-volatile memory):AEP属于nvmem,nvmem的概念原来就有,挂个电池的那种

PMEM(persistent memory):在一些文章中经常出现,大多数就是指AEP

为什么AEP能做到持久化以及低时延,我认为主要得以与两方面,首先是介质上的优势,3dxpoint 本身拥有很好的读写时延以及较长的耐久性,所以本身就可以作为高性能的磁盘使用,所以最开始Intel先推出了Optane的SSD,由于其读时延非常小,所以让Intel看到了绕开PCIe直接byte-addr的可能性,再配合Intel本身的技术整合,所以pmem就这样产生了

介质

现在有很多 NVM 非易失性内存的技术正在开发中,其中就包括 PCM 、FRAM 等好多种技术[3],据说 intel 已经搞 PCM 搞了40 多年了,目前我们用到的AEP 就是 Intel 的 PCM 介质产品

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%204.png

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%205.png

intel 的这一代AEP产品使用的是 GST(锗锑碲为基的相变化材料) 的相变材料,就如上图所示通过不断的加热来使得材料在结晶和非结晶两个状态间转换。不同状态的材料有不同的特性,比如电阻不同,从而可以表示数据。

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%206.png

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%207.png

图中就是存储颗粒的结构图示,绿色的部分就是相变材料,黄色的部分是一个选择器,通过接收特定的电压对相变材料进行读写。每一个cell都是可以独立进行读写,当写入的时候不像NAND需要block级别进行擦除。

对比内存的优点:一持久化、二可以直接读取(xy索引内存需要行列 读破坏 刷新)

如何组成一个AEP系统

有了存储介质,Intel 在此之上提供了两种方式接入系统,一个是类似传统SSD的方式,使用PCIe与CPU相连,使用NVMe进行通信,叫 Optane SSD,相比传统SSD其随机读写性能以及耐久性有很大优势。

受限于 PCIe 带宽以及使用 NVMe 协议,Optane 并不能完全体现颗粒低时延的优势,所以第二种方式是使用DIMM 物理插槽直接与iMC连接,直接使用 load/store 命令去对数据进行读写(也就是AEP了)。具体长这样:

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%208.png

对图片简单的说明:

整体的跟CPU的连接方式是这样的:

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%209.png

可以看出整个AEP还是相当复杂的,是一个介质加控制器的存储器,不像内存一样基本上就是一堆电容。

所以 AEP 能做出来实际上还是得益于 Intel 比较好的技术整合能力,介质跟系统缺一不可(比如镁光也有3dxpoint)

细节

DDR-T

上面我们浏览了整个 AEP 的组成部分以及如何跟 CPU 连接的,其中提到了 AEP 是通过 DIMM 插槽直接连接到iMC 的,也就是使用了跟 DDR4 相同的物理连接(DDR4 pin-compatible, meaning they use the same electrical and mechanical interface as DDR4)。虽然 3D XPoint 的读时延比起 NAND 已经快了非常多了,大概是几百ns的级别,但是我们看一下DDR4的时序,一般 DDR4-2400 的时序是15-15-15(CL-tRCD-tRP),CL 代表从 read 命令到能获取数据的时延、tRCD 代表开启一行的时延,随即读最长时延大概在30ns左右[9],这导致即使是 3D XPoint 也无法满足要求,所以Intel在DDR4的物理接口上实现了一套 DDR-T 的 Protocol,来实现异步的内存获取。大概的意思就是当 iMC 需要读取一个地址的时候,先通知 AEP 要读取的地址,然后 AEP 就进行处理,处理后将结果放到 DQ-buffer 上,再通知 iMC 数据获取完成,iMC 获取数据。

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%2012.png

当然目前这个协议是Intel独有的,不过后面有很多类似标准正在制定中。

ADR/Interleaving

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%2013.png

左图是是 AEP 的 ADR 示意图,其中也包括了 AEP 内部的一些框图,注意上面好像提到了 DQbuffer,跟 AEP 里面这个 Controller buffer(一般叫做XPBuffer)是不同的(为了不搞混,最好现在就把DQ buffer忘记就可以了),ADR全称 Asynchronous DRAM Refresh,可以直接理解为掉电保护,意味着只要数据到达了iMC,就保证掉电不会丢了,那么如何保证数据进入 iMC 呢,最上面的架构图中可以看到,只要数据从 memory cahce 中出去,或者直接 NT store 写入后,数据就到达 iMC 了,整体的结构可以看下面这幅图。

这个 Custom Power fail protected domain,就是eADR,200系列AEP才能支持

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%2014.png

对了,同样的 WPQ (在 iMC中的)与 store buffer (核心里) 不要弄混了(同样为了不搞混,最好把store buffer忘记)。

Intel 为了利用多根AEP,在iMC中实现了 interleave(4KiB),看上上副图右边,这样就增加了吞吐,降低了时延

时延

intel 给出的空闲读时延读时延是 170ns,随机读时延大概在320ns 2,与下面测量的实际读时延相符。

AEP 内部有个小型的 buffer 称之为 xp-buffer cache-line size 是256B,(上面提到了 256B 的原因可能是 AEP 内部为了做 ECC),实际上是以 256B 的粒度存储在介质中的,XP-buffer 大小应该是16KiB[5]

如果一个读请求 hit 了xpbuffer,时延会降至 150ns 左右,否则是 350ns 左右 [1]

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%2015.png

由于 ADR 的存在,一个写请求到达 iMC 的时候就可以认为是持久化了,具体说来就是到达 iMC 中的 WPQ,如果WPQ 中有空闲位置,一个写请求到达这里 WPQ 就立即返回了,所以此时写时延基本就等同于内存的写时延。为了测量真正的写时延,文章中使用的办法是让各个内存层次(memory hierarchy)的都满载,这样有的写入需要等待WPQ中有空闲位置的时候才能写入进去(因为这时所有内存层次的出口,相当于多个水池都满了才能获取最下面那个池子的放水速度),而等待的时间大概就等于的写入时延,写时延大概在 1200ns [1](文章中没提写入的 pattern 大小,这个数值只能参考一下)

对于WPQ有空的时候,aep 写就跟内存写时延基本一样了,nt指令写大概90ns,clwb指令大概60ns(均为8B)

由于 WPQ 的存在, 当 WPQ 满载的时候不可避免的会出现 Tail-Latency,可以看到当 hotspot 变大99.999%的值有提升(时延降低),可能就是 region 太小导致数据关联(后面的写入需要等前面的写入完成,这个等待可能在IMC中,也可能在AEP的controller中)。注意这个是极限情况下情况,可以发现即使是99.99%,也是是小于0.5us的。

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%2016.png

吞吐

根据上面的介绍,可以发现 AEP 实际上还是与内存不同,内存是你咋访问时延吞吐都是差不多的,AEP实际上是一个相对复杂的系统,所以顺序的各种性能都高于随机的,并且由于 256B 的介质粒度,小于 256B 的读会造成读放大,写会造成 COW (copy on write),大于256的情况下,顺序和随机其实性能差不太多,单根写2.2GiB 读6GiB,图中NI是 non-interleaving,也就是单根的情况,最右边的凹下去的那块是饥饿造成的最后优化中会谈论一下。

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%2017.png

使用方式

AEP使用方式可以大概分成两类,当然也可以两类混用

Far-Memory (Memory Mode)

一类是不需要感知的,称为 far-memory,将 pmem 当做易失性内存使用,dram此时作为pmem的cache

使用后就是内存空间变大了,程序不用改动,

App-Direct

另一类是感知的,称为 app-direct

app-direct 需要使用方适配 AEP,AEP 内存通过 ndctl 映射为 字符设备或者块设备。直接使用映射后的字符设备就是 dev-dax(就类似与用裸盘),块设备则需要搭配支持 dax 的文件系统(比如ext4)通过 mmap 的方式直接访问 AEP,这种方式就称之为 fs-dax。

如果是不支持DAX的文件系统(就是普通的mmap), mamp 系统调用后访问 memory-mapped file,实际上发生的还是 4KB 页大小的 block I/O(实际上还是走的block跟filesystem layer)。

DAX 的机制则是允许直接把用户空间的虚地址直接通过 IOMMU 映射到字符设备或者文件上(需要设备 dax 或者文件系统 dax,dev-dax or fsdax的支持)。当一个 DAX 映射的地址空间触发了缺页中断,此时会调用 dax 的 fault handler function 进行处理。

结构见下图

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%2018.png

可以结合内核具体如何处理一个 DAX 文件系统中 mmap 之后的一个缺页中断来了解整个过程(最终虚拟地址获得了PFN被装载进IOMMU),同时了解一下各个mm、fs、driver之间是如何交互的。

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%2019.png

具体使用

工具及概念

当前可以使用的工具主要是以下两个

  1. ndctl

    这个工具是用来管理内核的 LIBNVDIMM 驱动的,设计上是厂商无关的,因为目前NVDIMM的相关标准已经进入 ACPI(ACPI v6.0 NFIT NVDIMM Firmware Interface Table),厂商会根据标准提供相应的寄存器或者函数地址。

    主要功能是以下几个

    • 提供容量管理(创建 namespace
    • 枚举设备
    • 开启关闭 NVDIMM、Regions、Namespace
    • 管理 NVDIMM Labels
  2. ipmctl

    这个工具是 intel 提供的 pm 开源管理工具,只支持自家的 intel optane dcpmm,主要功能以下几个

    • 发现、查看信息
    • 提供平台内存配置(创建 region
    • 查看升级固件
    • 数据安全配置
    • 性能监控 健康监控 debug相关(主要是硬件相关的debug功能,如dump 固件,dump NFIT,错误注入)

从上面两个工具中可以看出主要的概念有 region、namespace,关系见下图

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%2020.png

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%2021.png

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%2022.png

region 需要使用 ipmctl 进行创建,创建后使用 ndctl 再其之上创建 namespace

region 分为三中模式,一个 socket 下的 DCPMM 可以分成若干个下述三种 region(也就是可以混搭)

region 为上层的 libnvdimm(ndctl) 中的提供了 DPA(DIMM Physical Address),在 ndctl 的概念中,region 是指的 ipmctl 中分出来的非 far-memory类型的的 region,使用 ndctl 创建 namespace,可以是下面四种类型之一

实际操作

$ ipmctl show -dimm
 DimmID | Capacity    | LockState | HealthState | FWVersion
===============================================================
 0x0001 | 126.422 GiB | Disabled  | Healthy     | xxxxxxxxxxxxx
 0x0011 | 126.422 GiB | Disabled  | Healthy     | 
 0x0101 | 126.422 GiB | Disabled  | Healthy     | 
 0x0111 | 126.422 GiB | Disabled  | Healthy     | 
 0x1001 | 126.422 GiB | Disabled  | Healthy     | 
 0x1011 | 126.422 GiB | Disabled  | Healthy     | 
 0x1101 | 126.422 GiB | Disabled  | Healthy     | 
 0x1111 | 126.422 GiB | Disabled  | Healthy     | 

# 创建 region
$ ipmctl create -goal PersistentMemoryType=AppDirect

$ ipmctl show -region
 SocketID | ISetID             | PersistentMemoryType | Capacity    | FreeCapacity | HealthState
=================================================================================================
 0x0000   | 0x541bc3d08abd8888 | AppDirect            | 504.000 GiB | 0.000 GiB    | Healthy
 0x0001   | 0x816fc3d0ccc78888 | AppDirect            | 504.000 GiB | 0.000 GiB    | Healthy

# 创建 namespace
$ ndctl create-namespace -r region0 

# 查看 创建的
$ ndctl list
[
  {
    "dev":"namespace1.0",
    "mode":"fsdax",
    "map":"dev",
    "size":532708065280,
    "uuid":"4416b22a-a683-4dca-92a3-a61d3f197da0",
    "sector_size":512,
    "align":2097152,
    "blockdev":"pmem1"
  },
  {
    "dev":"namespace0.0",
    "mode":"fsdax",
    "map":"dev",
    "size":532708065280,
    "uuid":"59fa0dc1-e8ed-4395-b975-a14c8ae7d123",
    "sector_size":512,
    "align":2097152,
    "blockdev":"pmem0"
  }
]

# 分区挂载
$ mkfs -t ext4 /dev/pmem0
$ mount -t ext4 -o dax /dev/pmem0 /mnt/aep0

$ lsblk
pmem0  259:1    0 496.1G  0 disk /mnt/aep0

PMDK

PMDK 是 Intel 推出的 AEP 套件,提供了一大堆开源的工具方便用户使用AEP,按照用户使用AEP的目的,可以大概把PMDK提供的库(工具)分成两类

  1. Volatie Libraries,主要面向那些需要把 AEP 当成内存使用的,比如说提供了类似 tcmalloc 这种形式的分配器库,直接把内存分配在 aep 上
  2. Persistent Libraries,面向想要使用AEP持久化特性的用户。

对于第二个目的的各种库,主要就是在 fs-dax 模式下提供了很多内置的数据结构,是在比较高层的地方提供的,怎么说呢,上面也看到只要使用 fs-dax 的文件系统,直接 mmap 之后就可以访问 AEP 了,没有什么需要特别的驱动。 PMDK 主要 还是基于 fs-dax 做了一层抽象把常用的操作进行了打包,提供了很多方便使用的抽象

// 这类似与实际的代码 调用 instrict.h 内联汇编
// 数据留在cache中
memcpy(dst, buf, len);
_mm_sfence();
_mm_clwb(dst); // 可能需要多次

// 数据不留在cache中
_mm256_stream_pddstdata); // nt 需要多次
_mm_sfence();

//-------------------------
// 如果使用 libpmem,与上面的等价

pmem_memcpy_persistent(dst, buf, len) // 这个是clwb的

pmem_memcpy_nodrain(dstbuf, len);  // 这个是ntstore的
pmem_drain();

各个包简介

Persistent lib:


Volatie Lib:具体有使用过不过应该业界投产的比较多,比如直接集成redis

Debug

Valgrind

intel搞了个插件可以配合上去使用,具体参考pm书

pintool

也是intel的工具可以hook指令,hook指令后可以记录每个load store指令访问的位置。

比如想要测试atomic的时候,可以记录每个store执行的写地址,模拟掉电的时候随机把fence之前的store指令的内容抹去。使用方需要单独写逻辑,然后用编译之后的程序加载要运行的程序

pcm-monitor

非常好用,可以监控各个内存channel的流量(包括ddr-t的流量 ddr流量),以及hit-rate

杂记&心得

With RDMA

https://downloads.openfabrics.org/WorkGroups/ofiwg/remote persistent memory/RPMEM 2.0 Public.pdf

RDMA

直接内存访问(DMA)允许数据不经过CPU进行传输,数据传输的工作 off-loaded 给了硬件DMA引擎,绕开内核,使得CPU可以处理更加重要的工作,同时DMA技术也使得小包传输的时延大大降低。

RDMA 主要用的接口是 verb,verb接口主要的操作分成两个类型

  1. on side operation

    WRITEREAD 直接操作另一台机器的内存,此时不需要另一台机器的CPU介入,配合无损网络可以做到不用ACK

  2. two side operation

    SENDRECV 类似于 socket,需要另一方CPU的介入。

为什么AEP结合RDMA是个好主意

对于分发写的协议,跨机器做Replication需要拷贝数据到别的机器,传统方式数据就从NIC 进来到了CPU,然后CPU再到内核,最后到介质,即使是使用了DPDK跟SPDK,实际上还是要另一台机器的CPU介入,而AEP的到来给了整个Replication不介入接收方CPU的希望(实际上这不是个新主意 微软FaRM)。

怎么做

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%2027.png

DDIO 是啥

当DDIO打开的时候,对于DMA而言相当于在 L3 上给开了一块Buffer,DMA直接写到了L3上,对于绝大多数应用来说,这是好的,因为接下来 CPU 马上就要使用这些数据,但是对于现在的AEP来讲,ADR并不能覆盖L3,所以直接使用 RDMA 写 AEP的内存地址是不能持久化的。

但是,DDIO是可以关闭的(这个选项叫做 Non-Allocating Write 在BIOS),这个关闭的范围是 PCIe-root complex 级别的,就是 CPU 中的 PCIe控制器关联的所有设备都会收到影响。

那么 rpma的方案就有以下两种

  1. 关闭 DDIO,这个可能会导致机器其他应用的性能降低,但是可以直接使用RDMA持久化one-side写
  2. 开启 DDIO,若干次one-side 写之后使用send通知接收者把cache刷回内存

http://janekmi.github.io/2020/06/21/ddio.html

别慌,还是有解决方法的,这个人(PMDK核心开发)发现可以直接修改 CPU 的 CSR 从而强制某一个port 的写操作全部转换为non-allocating写,也就相当于是在这个设备上关闭了DDIO

%E6%95%B4%E7%90%86aep%20dbdaa212e0fd4a4fa801822a8f80cbcd/Untitled%2028.png

具体操作需要先找到网卡的pci地址,然后手动修改CSR,对于专用的服务器,即使有性能下降也是可以接受的,下面摘自文章中

# as root

# 1. identify an address of the PCIe Root Port
$ export PCIe_Root_Port=0000:17:00.0

# 2. read the current value of the PERFCTRLSTS_0 register
$ setpci -s $PCIe_Root_Port 180.b
91

# 3. turn off the Use_Allocating_Flow_Wr bit
$ setpci -s $PCIe_Root_Port 180.b=11

# 4. verify the result
$ setpci -s $PCIe_Root_Port 180.b
11

这些东西在librpma中都有

优化

我认为需要注意的点

上面列举了很多AEP的使用的考量,同时下面是我认为比较有价值的地方,设计时可以着重考虑

个人感觉,对于绝大多数设计不是特别针对AEP的算法,如果适配到AEP上,最好不要在线 update AEP 中的数据结构(比如说搞一个在AEP中的树之类的),直接使用 log 就可以了(log 写 AEP,数据结构还是在内存中),构建的时候顺序读 log,带宽肯定不是瓶颈,如果CPU处理起来实在跟不上,需要做一些snapshot。当然前提是内存放得下。

竞赛

阿里之前搞了个AEP竞赛三人一组分初赛复赛两轮,就不说初赛了,复赛要求简述如下

  1. 提供一个kv服务,要求kv操作有原子性,只有set get,没有remove
  2. 64G存储,写入量75G(需要GC)
  3. key size 定长16Byte,value长度从128~1024不等
  4. 测试是先先写100GiB左右,然后运行10次读写混合测试

前十名的成绩基本都在40s以内,第一名大概25多s的写入时间折算下来大概1500w 写入qps(这是持久化的写,就是每次Set之后都可以保证断电后Set的结果还在

我最后获得了第25名,耗时大概120s,其中问题最大就是GC跟写入没有做thread-local, GC跟写入的时候一定要充分考虑thread-local,每个线程至对自己负责的区域做写入或者回收,基本上log结构配合thread-local不需要太多优化就可以到45s左右,剩下的就是如何去优化index,或是使用更优化的指令。

QA

参考资料

[1] System measurement of Intel AEP Optane DIMM https://arxiv.org/pdf/2009.14469.pdf

[2] Intel 64 and IA-32 ArchitecturesOptimization Reference ManualOrder Number: 248966-042bSeptember 2019

[3] Yu, S., & Chen, P.-Y. (2016). Emerging Memory Technologies: Recent Trends and Prospects.

[4] https://www.youtube.com/watch?v=BShO6h8Lc1s Intel Optane DC Persistent Memory Architecture Overview

[5] yang 2020 An Empirical Guide to the Behavior and Use of Scalable Persistent Memory

[6]FlatStore: An Efficient Log-Structured Key-Value Storage Engine for Persistent Memory

[7]ArchTM: Architecture-Aware, High Performance Transaction for Persistent Memory

[8]Understanding the Idiosyncrasies of Real Persistent Memory

[9]NVDIMM-C: A Byte-Addressable Non-Volatile Memory Module for Compatibility with Standard DDR Memory Interfaces

内核态的dirver如何工作

https://www.kernel.org/doc/html/latest/driver-api/nvdimm/nvdimm.html

order相关

http://materials.dagstuhl.de/files/15/15021/15021.MichaelSwift1.Slides.pdf

使用手册

https://www.intel.com/content/dam/support/us/en/documents/memory-and-storage/data-center-persistent-mem/Intel-Optane-DC-Persistent-Memory-Quick-Start-Guide.pdf

overview

https://www.suse.com/media/presentation/SPO1422_Persistent_Memory_in_Operation.pdf

nt implementation

https://stackoverflow.com/questions/37070/what-is-the-meaning-of-non-temporal-memory-accesses-in-x86

nt order

https://stackoverflow.com/questions/34501243/what-happens-with-a-non-temporal-store-if-the-data-is-already-in-cache

ddr-t细节|Samsung的compatible protocol

https://www.researchgate.net/profile/Changmin-Lee-6/publication/339586501_NVDIMM-C_A_Byte-Addressable_Non-Volatile_Memory_Module_for_Compatibility_with_Standard_DDR_Memory_Interfaces/links/5e5a288f299bf1bdb8445f44/NVDIMM-C-A-Byte-Addressable-Non-Volatile-Memory-Module-for-Compatibility-with-Standard-DDR-Memory-Interfaces.pdf

osu 的分析aep特性的文章

http://www.vldb.org/pvldb/vol14/p626-gugnani.pdf