nat*_*ouf 5 c parallel-processing mpi shared-memory numa
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]);
}
Run Code Online (Sandbox Code Playgroud)
在这里,我通过使用确保同步MPI_Barrier()并假设硬件使内存视图保持一致。此外,因为我有多个共享窗口,所以对 MPI_Barrier 的单次调用似乎比调用MPI_Win_fence()每个共享内存窗口更有效。
它似乎在我的 x86 笔记本电脑和服务器上运行良好。但是这个程序是有效/正确的 MPI 程序吗?有没有更有效的方法来实现同样的目标?
这里有两个关键问题:
MPI_Barrier绝对不是内存屏障,永远不应该以这种方式使用。在大多数情况下,它可能会同步内存作为其实现的副作用,但用户永远不能假设这一点。 MPI_Barrier仅保证同步进程执行。(如果有帮助的话,你可以想象一个系统MPI_Barrier使用不超过 MPI 标准要求的硬件小部件实现的系统。IBM Blue Gene 在某些情况下就这样做了。)while(1) {
MPI_Barrier(comm_shared);
... // write anywhere on shared memory
MPI_Barrier(comm_shared);
... // read on shared memory written by other processes
}
Run Code Online (Sandbox Code Playgroud)
它可能写得不清楚,但 MPI-3 标准相关文本的作者(我是该组的一员)假设可以使用底层/主机语言的内存模型来推理共享内存。因此,如果您在 C11 中编写此代码,您可以根据 C11 内存模型进行推理。
如果你想使用 MPI 来同步共享内存,那么你应该在所有窗口上使用它来MPI_Win_sync进行加载-存储访问和RMAMPI_Win_flush操作(Put/////GetAccumulateGet_accumulateFetch_and_opCompare_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。
我很想说这个 MPI 程序无效。
解释我的观点的依据
用户程序观察到的来自/到共享内存的加载/存储访问的一致性取决于体系结构。通过利用窗口同步函数(参见第 11.5 节)或显式完成未完成的存储访问(例如,通过调用 MPI_WIN_FLUSH),可以在统一内存模型(参见第 11.4 节)中创建一致的视图。MPI 没有定义访问单独内存模型中的共享内存窗口的语义。
在 RMA 统一模型中,公共副本和私有副本是相同的,并且最终通过加载操作观察到通过 put 或accumulate 调用进行的更新,而无需额外的 RMA 调用。对窗口的存储访问最终对远程获取或累积调用可见,而无需额外的 RMA 调用。RMA 统一模型的这些更强的语义允许用户省略一些同步调用,并有可能提高性能。
如果 RMA 统一模型中的访问不同步(使用锁定或刷新,请参阅第 11.5.3 节),则加载和存储操作可能会在进行过程中观察到内存的更改。
MPI_BARRIER 提供进程同步,但不提供内存同步。
唯一解决的同步始终是单方面的,即在您的情况下,MPI_Win_flush{,_all}, 或MPI_Win_unlock{,_all}(除了必须由用户强制执行的主动和被动并发同步的互斥,或使用 MPI_MODE_NOCHECK 断言标志)。
因此,要么您使用存储直接访问内存,并且需要MPI_Win_sync()在调用之前调用每个窗口MPI_Barrier(如示例 11.10 中所述)以确保同步,要么您正在进行 RMA 访问,然后您必须至少在MPI_Win_flush_all第二个窗口之前调用屏障,以确保操作得到传播。如果您尝试使用加载操作进行读取,则可能必须在第二个屏障之后进行同步,然后才能执行此操作。
另一种解决方案是在屏障之间解锁和重新锁定,或者使用编译器和硬件特定符号可以确保在数据更新后发生加载。