MPI-3标准引入了共享内存,所有共享此内存的进程都可以读写,而无需调用MPI库。虽然有使用共享或非共享内存的单边通信的示例,但我没有找到太多关于如何正确使用直接访问的共享内存的信息。
我最终做了这样的事情,效果很好,但我想知道MPI标准是否保证它总是有效?
// initialization:
MPI_Comm comm_shared;
MPI_Comm_split_type(MPI_COMM_WORLD, MPI_COMM_TYPE_SHARED, i_mpi, MPI_INFO_NULL, &comm_shared);
// allocation
const int N_WIN=10;
const int mem_size = 1000*1000;
double* mem[10];
MPI_Win win[N_WIN];
for (int i=0; i<N_WIN; i++) { // I need several buffers.
MPI_Win_allocate_shared( mem_size, sizeof(double), MPI_INFO_NULL, comm_shared, &mem[i], &win[i] );
MPI_Win_lock_all(0, win);
}
while(1) {
MPI_Barrier(comm_shared);
... // write anywhere on shared memory
MPI_Barrier(comm_shared);
... // read on shared memory written by other processes
}
// deallocation
for (int i=0; i<N_WIN; i++) {
MPI_Win_unlock_all(win[i]);
MPI_Win_free(&win[i]);
}
在这里,我使用MPI_Barrier()
确保同步,并假设硬件使内存视图一致。此外,因为我有几个共享窗口,所以对MPI_Barrier的单个调用似乎比在每个共享内存窗口上调用MPI_Win_fence()
更有效。
它似乎在我的x86笔记本电脑和服务器上工作得很好。但是这个程序是一个有效/正确的MPI程序吗?有没有更有效的方法来实现同样的事情?
2条答案
按热度按时间myzjeezk1#
这里有两个关键问题:
MPI_Barrier
绝对不是内存屏障,永远不应该这样使用。在大多数情况下,它可能会同步内存作为其实现的副作用,但用户永远不能假设.MPI_Barrier
只保证同步进程执行。(如果有帮助,您可以想象一个系统,其中MPI_Barrier
是使用不超过MPI标准要求的硬件部件实现的。IBM Blue Gene在某些情况下做到了这一点。)1.这个问题是无法回答的,如果没有详细说明您在这里实际使用共享内存做了什么:
它可能写得不清楚,但MPI-3标准相关文本的作者(我是这个小组的一员)假设可以使用底层/主机语言的内存模型来推理共享内存。因此,如果你用C11编写这段代码,你可以根据C11内存模型来推理。
如果你想使用MPI来同步共享内存,那么你应该在所有窗口上使用
MPI_Win_sync
进行加载-存储访问,并使用MPI_Win_flush
进行RMA操作(Put
/Get
/Accumulate
/Get_accumulate
/Fetch_and_op
/Compare_and_swap
)。我希望
MPI_Win_sync
被实现为CPU内存屏障,因此为每个窗口调用它是多余的。这就是为什么假设C11或C++11内存模型并分别使用https://en.cppreference.com/w/c/atomic/atomic_thread_fence和https://en.cppreference.com/w/cpp/atomic/atomic_thread_fence可能更有效。svdrlsy42#
我很想说这个MPI程序是无效的。
来解释我的观点是基于什么
用户程序所观察到的从/到共享内存的加载/存储访问的一致性取决于体系结构。在统一内存模型中可以创建一致的视图(参见第11.4节)(参见11.5节)或显式完成未完成的存储访问MPI不定义用于访问单独存储器模型中的共享存储器窗口的语义。
在RMA统一模型中,公共副本和私有副本是相同的,并且通过put或accumulate调用进行的更新最终由加载操作观察到,而无需额外的RMA调用。对窗口的存储访问最终对远程get或accumulate调用可见,而无需额外的RMA调用。RMA统一模型的这些更强的语义允许用户省略一些同步调用,并可能提高性能。
如果RMA统一模型中的访问没有同步(使用锁定或刷新,请参见第11.5.3节),则加载和存储操作可能会在进行中观察到内存的更改。
MPI_BARRIER提供进程同步,但不提供内存同步。
唯一解决的同步始终是单侧同步,即在您的情况下,
MPI_Win_flush{,_all}
或MPI_Win_unlock{,_all}
(除了必须由用户强制执行的主动和被动并发同步的互斥,或使用MPI_MODE_NOCHECKAssert标志)。因此,您可以使用store直接访问内存,并且在调用
MPI_Barrier
之前,需要在每个窗口上调用MPI_Win_sync()
(如例11.10所述)以确保同步,或者您正在进行RMA访问,然后您必须在第二个屏障之前至少调用MPI_Win_flush_all
以确保操作已经传播。在执行此操作之前,您可能还必须在第二个屏障之后进行同步。另一种解决方案是在屏障之间解锁和重新锁定,或者使用编译器和硬件特定的符号可以确保在数据更新之后发生加载。