多个节点上的 mmap
mmap on multiple nodes
下面的脚本使用 mmap 并行写入内存映射数组。但是,它仅在所有进程都在同一节点上时才有效 - 否则它会为不在等级 0 节点上的处理器或输出中的其他杂散零生成 0 行。为什么是这样?我觉得我遗漏了一些有关 mmap 工作原理的信息。
编辑:相同的结果出现在 NFS 系统和并行分布式系统上。下面的评论者建议这与 mmap 的页面长度有关。当我的切片的 'length' 正好是 4KiB 时,脚本仍然会产生错误的输出。当切片长于 4 KiB 时也会发生同样的情况。
#!/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是节点,可能会出现以下情况:
- N1和N2打开同一个零填充文件;文件内容 [00],最后修改时间:t=0.00.
- N1 和 N2 内核请求比需要更多的文件内容并将其存储在缓存中。 N1 缓存:[00] (t=0.00); N2缓存:[00] (t=0.00).
- 在时间t=0.01,N2写入文件的后半部分。服务器状态:[02] (t=0.01); N1缓存:[00](0.00); N2缓存:[02](0.01).
- 在时间t=0.02,N1写入前半部分。服务器:[12] (0.02); N1 缓存:[10] (0.02)。 N2缓存:[02](0.01).
- N1 上的进程尝试读取后半部分。
- N1内核向服务器请求最后修改时间;结果:t=0.02.
- N1 内核检索前半部分的陈旧缓存内容 [0]。
因此,您需要确保其他客户端(节点)更新时间戳。
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;我用按键模拟了它。我不确定这个障碍是否真的有必要。 sleep(0.1)
也可能不是必需的;它只是为了防止 touch()
函数返回和 NFS 服务器接收更新之间的任何延迟。
我假设您安排了数据,以便每个节点访问与 4096 字节边界对齐的内存映射文件的部分。我用 length=4096
.
测试过
这个解决方案有点乱,它依赖于 NFS 驱动程序的未记录行为。这是在 Linux 内核 3.10.0-957 上,NFS 挂载选项包括 relatime,vers=3,rsize=8192,wsize=8192
。如果您使用这种方法,我建议您包括一个自测:基本上,上面的代码带有一个 assert
语句来验证输出。这样,如果它由于不同的文件系统而停止工作,您将捕获它。
下面的脚本使用 mmap 并行写入内存映射数组。但是,它仅在所有进程都在同一节点上时才有效 - 否则它会为不在等级 0 节点上的处理器或输出中的其他杂散零生成 0 行。为什么是这样?我觉得我遗漏了一些有关 mmap 工作原理的信息。
编辑:相同的结果出现在 NFS 系统和并行分布式系统上。下面的评论者建议这与 mmap 的页面长度有关。当我的切片的 'length' 正好是 4KiB 时,脚本仍然会产生错误的输出。当切片长于 4 KiB 时也会发生同样的情况。
#!/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是节点,可能会出现以下情况:
- N1和N2打开同一个零填充文件;文件内容 [00],最后修改时间:t=0.00.
- N1 和 N2 内核请求比需要更多的文件内容并将其存储在缓存中。 N1 缓存:[00] (t=0.00); N2缓存:[00] (t=0.00).
- 在时间t=0.01,N2写入文件的后半部分。服务器状态:[02] (t=0.01); N1缓存:[00](0.00); N2缓存:[02](0.01).
- 在时间t=0.02,N1写入前半部分。服务器:[12] (0.02); N1 缓存:[10] (0.02)。 N2缓存:[02](0.01).
- N1 上的进程尝试读取后半部分。
- N1内核向服务器请求最后修改时间;结果:t=0.02.
- N1 内核检索前半部分的陈旧缓存内容 [0]。
因此,您需要确保其他客户端(节点)更新时间戳。
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;我用按键模拟了它。我不确定这个障碍是否真的有必要。 sleep(0.1)
也可能不是必需的;它只是为了防止 touch()
函数返回和 NFS 服务器接收更新之间的任何延迟。
我假设您安排了数据,以便每个节点访问与 4096 字节边界对齐的内存映射文件的部分。我用 length=4096
.
这个解决方案有点乱,它依赖于 NFS 驱动程序的未记录行为。这是在 Linux 内核 3.10.0-957 上,NFS 挂载选项包括 relatime,vers=3,rsize=8192,wsize=8192
。如果您使用这种方法,我建议您包括一个自测:基本上,上面的代码带有一个 assert
语句来验证输出。这样,如果它由于不同的文件系统而停止工作,您将捕获它。