新メモリ: Persistent Memoryの動作検証



# はじめに
Persistent Memoryは不揮発メモリ(Non-volatile memory)を使いメモリとして利用できるメディアです。2018年にGoogleがGCPでPersistent Memory搭載のVMをα版としてサポートのニュースを見られた方もいるかと思います。大まかにPersistemt Memoryを説明すると、SSDなどと同様に不揮発メモリを利用しているため、従来のDRAMのメモリと異なり電源を落としてもデータは消えないメモリです。もちろん、メモリですので、ディスクとして利用するSSD(Flashドライブ)よりも低レイテンシです。物理的にはDRAMと同じサーバのDIMMスロットに挿します。Persistent Memoryの詳細な説明は[SNIA/education](https://www.snia.org/education/what-is-persistent-memory)をご参照ください。
今回は、SNIA(US)が主催するSDC2019の[Persistent Memory Programming Tutorial and Hackathon](https://www.snia.org/events/storage-developer/special-events)に参加してきたので、その環境を使い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にアクセスできるようになります。

![mmap.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229880/f473f1fa-c1f0-0bcf-718a-b9813caf656f.jpeg)

次に、Persistent Memoryの簡単なプログラムを使って動作を確認します。
以下にソースコード(`basic_mmap.c`)を示します。

```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を以下に示します。

```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)](http://pmem.io/pmdk/)を使うと良いかと思います。
　アーキテクチャ&市場の視点から見た場合、Persistent Memoryは、これまでのコンピューティングのアーキテクチャを大きく変えるポテンシャルを持っているメモリで、2019年になりようやく一般ユーザでも触れるようになりました。価格も2019年時点で$6.57/GBとDRAMに比べ低価格です([参考](https://www.anandtech.com/show/14180/pricing-of-intels-optane-dc-persistent-memory-modules-leaks))。さらには、不揮発メモリ向けのデータプロトコルである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は、従来のメモリとディスク(ファイル)の関係にメスを入れ、従来のコンピュータアーキテクチャを大きく変化させる可能性をもったメモリの新しいメディアのため、今後面白い使われ方が出てくるのが楽しみです。