A Try on PMDK

PMDK is popular nowadays. To practice, please start from http://pmem.io.

在 src/examples/libpmem/ 目录下有三个简单的例子:full_copy.c、manpage.c、simple_copy.c。我们选择 full_copy.c 做测试,并在其基础上修改为 copy_mem.c。 在得到的测试数据中可以发现几个明显的拐点,例如:

  1. devdax 模式下,每次 flush 32 字节比 64 字节要慢很多。因为 cache line 的大小是 64 字节。
  2. devdax 模式下,每次 flush 128 字节也比 256 字节要慢很多。 推测是 PMEM_MOVNT_THRESHOLD 默认设置为 256 导致的,256 字节以上的数据将采用 Cache of Non-temporal Data 的写 Cache 模式。

    $ cat src/libpmem/x86_64/init.c

    #define MOVNT_THRESHOLD 256 size_t Movnt_threshold = MOVNT_THRESHOLD;

  3. xfs 模式下,每次 flush 2048 字节也比 4096 字节要慢很多。因为 xfs 的块大小(等同内核的页面大小)是 4096 字节。
  4. fsdax 模式下,每次 flush 128 字节也比 256 字节要慢很多。因为 fsdax 模式下,首次缺页都会要先写零并持久化。
  5. fsdax 模式比 devdax 模式大约会慢 2-3 倍。除了缺页写零之外还有哪些额外操作?

Perf 的输出中可以明显看到 fsdax 模式下有很多 memcpy_flushcache 的调用。主要是走/不走 cache 的差别。

fsdax
    43.25%  copy_mem  libpmem.so.1.0.0  [.] memmove_movnt_sse2_clwb
    39.21%  copy_mem  [kernel.vmlinux]  [k] memcpy_flushcache

devdax
    86.16%  copy_mem  libpmem.so.1.0.0  [.] memmove_movnt_sse2_clwb
     7.14%  copy_mem  libpmem.so.1.0.0  [.] memmove_nodrain_sse2_clwb

那么为什么 fsdax 会有很多 memcpy_flushcache 的调用,而 devdax 没有?从 perf 获得的调用栈:

__libc_start_main
main
memmove_movnt_sse2_clwb
page_fault
do_page_fault
__do_page_fault
handle_mm_fault
__handle_mm_fault
__xfs_filemap_fault
dax_iomap_fault
xfs_file_iomap_begin
xfs_iomap_write_direct
xfs_bmapi_write
xfs_bmapi_convert_unwritten
blkdev_issue_zeroout
submit_bio_wait
submit_bio
generic_make_request
pmem_make_request
pmem_do_bvec
write_pmem
memcpy_flushcache

那么 clwb 函数是怎么定义的?查 libpmem 代码即可。

[root@localhost libpmem]# grep -rwIn memmove_movnt_sse2_clwb /home/test/pmdk-sandbox/src
/home/test/pmdk-sandbox/src/libpmem/x86_64/memcpy/memcpy_nt_sse2_clwb.c:34:#define EXPORTED_SYMBOL memmove_movnt_sse2_clwb
/home/test/pmdk-sandbox/src/libpmem/x86_64/memcpy_memset.h:83:void memmove_movnt_sse2_clwb(char *dest, const char *src, size_t len);

注意上面那个 #define。找到对应的代码:src/libpmem/x86_64/memcpy/memcpy_nt_sse2.h

void
EXPORTED_SYMBOL(char *dest, const char *src, size_t len)
{
    if ((uintptr_t)dest - (uintptr_t)src >= len)
	memmove_movnt_sse_fw(dest, src, len);
    else
	memmove_movnt_sse_bw(dest, src, len);

    maybe_barrier();
}

Since PMDK is in active development, the best practice is to read the code and follow the changes.

As for CLFLUSH、CLFLUSHOPT、CLWB’s difference, read the code and refer to http://research.cs.wisc.edu/sonar/tutorial/03-hardware.pdf.

[ PMDK , NVM ]
Written on February 3, 2019