はじめに
Persistent Memoryは不揮発メモリ(Non-volatile memory)を使いメモリとして利用できるメディアです。2018年にGoogleがGCPでPersistent Memory搭載のVMをα版としてサポートのニュースを見られた方もいるかと思います。大まかにPersistemt Memoryを説明すると、SSDなどと同様に不揮発メモリを利用しているため、従来のDRAMのメモリと異なり電源を落としてもデータは消えないメモリです。もちろん、メモリですので、ディスクとして利用するSSD(Flashドライブ)よりも低レイテンシです。物理的にはDRAMと同じサーバのDIMMスロットに挿します。Persistent Memoryの詳細な説明はSNIA/educationをご参照ください。
今回は、SNIA(US)が主催するSDC2019のPersistent Memory Programming Tutorial and Hackathonに参加してきたので、その環境を使いPersistent Memoryの動作検証を行います。
動作検証
SNIAがあらかじめ用意してくれたPersistent Memoryを搭載したDocker Containerに入り検証を行います。
まずは、Persistent Memoryを確認します。
$ df -h
Filesystem Size Used Avail Use% Mounted on
overlay 15G 9.8G 5.3G 66% /
tmpfs 64M 0 64M 0% /dev
tmpfs 189G 0 189G 0% /sys/fs/cgroup
shm 64M 0 64M 0% /dev/shm
/dev/pmem1 976G 117M 926G 1% /pmem
/dev/nvme2n1p1 667G 8.2G 659G 2% /home/pmemuser51
/dev/mapper/fedora-root 15G 9.8G 5.3G 66% /etc/hosts
tmpfs 189G 0 189G 0% /proc/acpi
tmpfs 189G 0 189G 0% /proc/scsi
tmpfs 189G 0 189G 0% /sys/firmware
$ df -h /pmem
Filesystem Size Used Avail Use% Mounted on
/dev/pmem1 976G 117M 926G 1% /pmem
$ mount |grep pmem
/dev/pmem1 on /pmem type ext4 (rw,relatime,seclabel,dax)
/dev/nvme2n1p1 on /home/pmemuser51 type xfs (rw,relatime,seclabel,attr2,inode64,noquota)
Persistent Memoryはdax
で/pmem
にマウントされています。
DAX(Direct Access)は WindowsとLinuxでサポートされており、Linux Kernelでは、v4.0から実装がはいり、v4.2でデフォルトサポートとなっています。WindowsではWindows 10とWindows Server 2016からサポートが開始されています。
このDAXにより、アプリケーションが/pmem
上のファイルをマップすると、直接Load/Storeでアクセスできるようになります。つまり、アプリケーションはKernelを経由せずにダイレクトにPersistent Memoryにアクセスできるようになります。
次に、Persistent Memoryの簡単なプログラムを使って動作を確認します。
以下にソースコード(basic_mmap.c
)を示します。
#include <sys/mman.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int
main(int argc, char *argv[])
{
int fd;
char *pmaddr;
if (argc != 2) {
fprintf(stderr, "Usage: %s filename\n", argv[0]);
exit(1);
}
if ((fd = open(argv[1], O_RDWR)) < 0)
err(1, "open: %s", argv[1]);
if ((pmaddr = (char *)mmap(NULL, 4096, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0)) == MAP_FAILED)
err(1, "mmap: %s", argv[1]);
close(fd);
/* write to persistent memory... */
strcpy(pmaddr, "Hello, persistent memory!");
printf("%s\n", pmaddr);
/* flush the changes... */
if (msync((void *)pmaddr, 4096, MS_SYNC) < 0)
err(1, "msync: %s", argv[1]);
printf("done.\n");
exit(0);
}
まず、DAXでマウントしたPersistent Memory上のファイル(/pmem/XXXX
)をシステムコールのひとつであるmmap()でメモリマッピングします。
マッピング後、ファイルディスクリプタをCloseします。
これまで、通常のファイルをmmap()で利用(DAX以外でマウントしたファイル(e.g. Swapファイルなど)をmmapで利用)だとファイルをOSの仮想メモリアドレスにマッピングした後、仮想アドレスにアクセスするたび、Kernelの中でファイルI/Oの命令セットに変換されていました。
別の言い方をすると、アプリケーション(ユーザプログラム)がメモリに対するオペレーションを行うと、Kernel内でファイルに対するオペレーションに変換されていました。
そのため、mmap()で仮想アドレスにファイルをマッピングしている間は、ファイルディスクリプタをOpenし続ける必要がありました。
しかし、Persistent MemoryはDAXでマウントされており、Kernel内でファイルに対するオペレーションへの変換が必要なくなったため、mmap()でPersistnt Memory上のファイルを仮想アドレスにマッピングした直後にファイルディスクリプタをCloseできます。
その後、strcpy()でマッピングされている仮想アドレスに対してデータを書き込みます。
最後に、仮想メモリへ書き込んだデータがPersistent Memoryに確実に書き込むために、msync()を呼び出します。
これは、通常のファイルでもHDDやSSDに確実にデータを書き込んだことを保証するためにsync()を呼んでいると思いますが、それと同じ目的のオペレーションになります。
Persistent Memoryはメモリとして動作できつつ、不揮発性のためファイルと同じようにメディアに書き込まれたことを保証する必要があります。
次に、作成したプログラム(basic_mmap.c
)をコンパイルします。コンパイルのためのMakefileを以下に示します。
PROGS = basic_mmap
CFLAGS = -g -Wall -Werror -std=gnu99
all: $(PROGS)
basic_mmap: basic_mmap.o
$(CC) -o $@ $(CFLAGS) $^ $(LIBS)
clean:
$(RM) *.o a.out core
clobber: clean
$(RM) $(PROGS) basic_mmap
.PHONY: all clean clobber
Makefileを使いコンパイルします。
$ make
cc -g -Wall -Werror -std=gnu99 -c -o basic_mmap.o basic_mmap.c
cc -o basic_mmap -g -Wall -Werror -std=gnu99 basic_mmap.o
出来上がったプログラム(basic_mmap
)を実行します。
実行では、事前にPersistemt Memory上にファイル(/pmem/myfile
)を作成し、プログラムに渡します。
$ truncate --size=1M /pmem/myfile
$ ./basic_mmap /pmem/myfile
Hello, persistent memory!
done.
上記は実行結果です。
意図通りに動いているものの、何が起こっているのかわからないため、実際にPersistent Memoryに書き込まれている様子を確認しながら実行します。
プログラムの実行前に、/pmem/myfile
を再作成します。
$ rm -f /pmem/myfile
$ truncate --size=1M /pmem/myfile
プログラム(basic_mmap
)の実行前に/pmem/myfile
を確認します。
$ hexdump -C /pmem/myfile
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00100000
データが何も入っていない状態です。
次にプログラム(basic_mmap
)を実行した後、/pmem/myfile
を確認します。
$ ./basic_mmap /pmem/myfile
Hello, persistent memory!
done.
$ hexdump -C /pmem/myfile
00000000 48 65 6c 6c 6f 2c 20 70 65 72 73 69 73 74 65 6e |Hello, persisten|
00000010 74 20 6d 65 6d 6f 72 79 21 00 00 00 00 00 00 00 |t memory!.......|
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00100000
すると、今度はプログラム(basic_mmap
)で書き込んだデータ(Hello, persistent memory!
)がPersistent Memoryに書き込まれているのが確認できます。
このようにファイルディスクリプタをCloseしているにも関わらずメモリへ書き込んだデータが正しくPersistent Memoryへ保存されているのが分かります。
感想
この検証では、非常に簡単なプログラムを使いPersistent Memoryを試しました。
Persistemt Memoryは、メモリとファイルのオペレーションをミックスしたような使い方です。Swapファイルを勉強したことがある人にとっては、イメージがつきやすいのではないかと思います。メモリとして見たときには、通常の揮発性メモリ(DRAM)と違い不揮発性(電源を切ってもデータが消えない)であることを意識する必要がありますが、それ以外はメモリと変わらず扱えます。今回は、非常にシンプルなオペレーションしか行なっていませんが、Persistent Memoryを本格的に使いたい人は、Persistemt MemoryのSDKライブラリであるPersistent Memory Development Kit(PMDK)を使うと良いかと思います。
アーキテクチャ&市場の視点から見た場合、Persistent Memoryは、これまでのコンピューティングのアーキテクチャを大きく変えるポテンシャルを持っているメモリで、2019年になりようやく一般ユーザでも触れるようになりました。価格も2019年時点で$6.57/GBとDRAMに比べ低価格です(参考)。さらには、不揮発メモリ向けのデータプロトコルであるNVMeやNVMe-oFも育ってきました。今回は触れていませんが、RDMA(Remote Direct Memory Access)も利用できます。これらが揃ってきたことで Persistent Memory+NVMe(-oF)でRDMAを使い、複数台のコンピュートノードから巨大なメモリを利用するようなユースケースも海外のカンファレンスでは発表され始めました。また、HPCなどで複数コンピュートノード間でメモリを共有するためのアーキテクチャであるNUMAなどは、置き換わるか又は作りが変わってくる可能性があるかと思います。他には、SAP HanaなどのIn memoryデータベースではPersistent Memoryのサポートを発表していますし、HadoopやSparkなどで利用されているHDFSなどは大きく置き換わる可能性もあります。さらには、もしかするとKubernetesのようなオープンの分散システムにおいても、複数コンピュートノードで共有できる巨大なメモリを低コストで実現できるようになるかもしれません。
Persistent Memoryは、従来のメモリとディスク(ファイル)の関係にメスを入れ、従来のコンピュータアーキテクチャを大きく変化させる可能性をもったメモリの新しいメディアのため、今後面白い使われ方が出てくるのが楽しみです。