很少需要在 MPI 中执行显式同步,这样的代码通常没有什么意义。 MPI 中的排名主要在本地处理数据,不共享对全局对象的访问权限,并且按照发送和接收操作的语义隐式同步。当i在本地处理接收到的数据时,rank i为什么要关心其他rank j是否收到了广播?
以下情况通常需要明确的障碍:
- 基准测试 - 代码的计时区域之前的障碍消除了由于一个或多个队伍迟到而导致的任何无关的等待时间
- 并行 I/O - 在这种情况下,存在一个全局对象(共享文件),其内容的一致性可能取决于 I/O 操作的正确顺序,因此需要显式同步
- 单面操作 (RMA) - 与并行 I/O 情况类似,某些 RMA 方案需要显式同步
- 共享内存窗口 - 这些是 RMA 的一个子集,其中访问多个列之间共享的内存不通过 MPI 调用,而是发出直接内存读取和写入指令,这带来了共享内存固有的所有问题像发生数据竞争的可能性这样的编程,因此需要在 MPI 中使用锁和屏障
很少有代码真正有意义的情况。根据等级的数量、它们在处理元素网络中的分布、要广播的数据大小、互连的延迟和带宽以及 MPI 库用于实际实现数据分布的算法,可能需要由于延迟传播的现象,当行列在时间上稍微不对齐时,完成的时间要长得多,这也可能适用于用户代码本身。这些都是病态的情况,通常在特定条件下发生,这就是为什么有时您可能会看到如下代码:
#ifdef UNALIGNED_BCAST_IS_SLOW
MPI_Barrier(MPI_COMM_WORLD);
#endif
MPI_Bcast(buffer, N, MPI_FLOAT, 0, MPI_COMM_WORLD);
甚至
if (config.unaligned_bcast_performance_remedy)
MPI_Barrier(MPI_COMM_WORLD);
MPI_Bcast(buffer, N, MPI_FLOAT, 0, MPI_COMM_WORLD);
我见过至少一个支持 MPI 的量子化学模拟软件包包含类似的代码。
也就是说,MPI 中的集体操作不一定是同步的。唯一能保证在某个时间点所有等级同时在调用中的是MPI_BARRIER。 MPI 允许队伍在参与集体行动完成后提前退出。例如,MPI_BCAST 可以实现为从根发送的线性序列:
int rank, size;
MPI_Comm_rank(comm, &rank);
MPI_Comm_size(comm, &size);
if (rank == root)
{
for (int i = 0; i < size; i++)
if (i != rank)
MPI_Send(buffer, count, type, i, SPECIAL_BCAST_TAG, comm);
}
else
{
MPI_Recv(buffer, count, type, root, SPECIAL_BCAST_TAG, comm, MPI_STATUS_IGNORE);
}
在这种情况下,排名0(如果root不是0)或排名1(当root是0)将是第一个接收数据的人,因为有没有更多的指向或来自它的通信,可以安全地从广播调用返回。如果数据缓冲区很大并且互连速度很慢,则会在列之间产生相当多的时间交错。