Numpy 多节点上的mmap
下面的脚本使用mmap并行写入内存映射数组。但是,它仅在所有进程都位于同一节点上时才起作用-否则,它会为不在秩0节点上的处理器生成0行,或在输出中生成其他零散值。为什么会这样?我觉得我错过了一些关于mmap如何工作的东西 编辑:在NFS系统和并行分布式系统上都会出现相同的结果。下面的一位评论员建议这与mmap的页面长度有关。当我的切片的“长度”正好为4KB时,脚本仍会生成错误的输出。当切片长度远大于4kib时,也会发生同样的情况Numpy 多节点上的mmap,numpy,parallel-processing,mmap,Numpy,Parallel Processing,Mmap,下面的脚本使用mmap并行写入内存映射数组。但是,它仅在所有进程都位于同一节点上时才起作用-否则,它会为不在秩0节点上的处理器生成0行,或在输出中生成其他零散值。为什么会这样?我觉得我错过了一些关于mmap如何工作的东西 编辑:在NFS系统和并行分布式系统上都会出现相同的结果。下面的一位评论员建议这与mmap的页面长度有关。当我的切片的“长度”正好为4KB时,脚本仍会生成错误的输出。当切片长度远大于4kib时,也会发生同样的情况 #!/usr/bin/python3 from mpi4py i
#!/usr/bin/python3
from mpi4py import MPI
import numpy as np
comm = MPI.COMM_WORLD
rank = comm.Get_rank()
size = comm.Get_size()
length = int(1e6) # Edited to make test case longer.
myfile = "/tmp/map"
if rank == 0:
fp = np.memmap(myfile, dtype=np.float32, mode='w+', shape=(size,length))
del fp
comm.Barrier()
fp = np.memmap(myfile, dtype=np.float32, mode='r+', shape=(1,length),
offset=rank*length*4)
fp[:,:] = np.full(length,rank)
comm.Barrier()
if rank == 0:
out = np.memmap(myfile, dtype=np.float32, mode='r', shape=(size,length))
print(out[:,:])
正确输出:
[[ 0. 0. 0. 0.]
[ 1. 1. 1. 1.]
[ 2. 2. 2. 2.]
[ 3. 3. 3. 3.]
[ 4. 4. 4. 4.]]
输出不正确。级别为3和4的处理器不进行写入
[[ 0. 0. 0. 0.]
[ 1. 1. 1. 1.]
[ 2. 2. 2. 2.]
[ 0. 0. 0. 0.]
[ 0. 0. 0. 0.]]
这个答案适用于NFS文件。其他网络文件系统上的YMMV 问题与MPI或
numpy.memmap
无关,而是与Linux内核如何缓存NFS文件数据有关。从一些实验中可以看出,在从NFS服务器请求读取之前,客户机请求最后修改的时间戳。如果此时间戳不比客户机上次写入的时间最近,则将从客户机的缓存中获取数据,而不是再次从服务器请求数据。如果N1和N2是节点,则可能发生以下情况:
if rank == 0:
fp = np.memmap(myfile, dtype=np.float32, mode='w+', shape=(size,length))
del fp
comm.Barrier() # B0
fp = np.memmap(myfile, dtype=np.float32, mode='r+', shape=(1,length),
offset=rank*length*4)
comm.Barrier() # B1 (this one may be superfluous)
fp[:,:] = np.full(length, rank)
del fp # this will flush the changes to storage
comm.Barrier() # B2
from pathlib import Path
from time import sleep
if rank == 1:
# make sure that another node updates the timestamp
# (assuming 1 s granularity on NFS timestamps)
sleep(1.01)
Path(myfile).touch()
sleep(0.1) # not sure
comm.Barrier() # B3
if rank == 0:
out = np.memmap(myfile, dtype=np.float32, mode='r', shape=(size,length))
print(out[:,:])
关于屏障B1
:我没有在这里设置MPI;我用按键模拟它。我不确定这个障碍是否真的有必要。睡眠(0.1)
也可能是不必要的;它只是在touch()
函数返回和NFS服务器接收更新之间出现延迟的情况下出现的
我假设您对数据进行了安排,以便每个节点访问与4096字节边界对齐的内存映射文件的部分。我使用length=4096
进行测试
这个解决方案有点像黑客,依赖于NFS驱动程序的未记录行为。这是在Linux内核3.10.0-957上,NFS装载选项包括
relatime、vers=3、rsize=8192、wsize=8192
。如果使用这种方法,我建议您包括一个自检:基本上,上面的代码带有一个assert
语句来验证输出。这样,如果它由于不同的文件系统而停止工作,您将捕获它。我假设/tmp/map
是一个本地文件,它只存在于运行rank0
的节点上。但是,我很惊讶memmap
在其他节点上没有失败(例如,该文件不存在)。您缺少网络文件系统,例如NFS(速度慢且通用,易于部署,非常流行)或Lustre(快速且并行,难以部署,主要用于HPC)。我在NFS和并行分布式文件系统上都进行了测试,这两种情况下都存在同样的问题。这与内存映射在页面粒度上起作用这一事实有关。页面通常不小于4kib。如果触摸页面中的一个字节,则整个页面将被标记为脏页,然后将其全部刷新到磁盘。无论最后哪个进程通过网络刷新其修改的页面,都将覆盖其他进程所做的更改。当所有进程都位于同一主机上时,这不是问题,因为FS缓存中的同一物理页映射到所有进程中。解决方案是不映射如此小的区域,而是每个进程至少有4 KiB。请注意,即使您的数组是1 x 4 x 4=16
字节,内存映射的大小也会向上舍入到整个页面,并定位为从页面大小边界开始。