MPI_Bcast() 之前的障碍?

问题描述

我看到一些开源代码在广播根值之前使用了 MPI_Barrier:

MPI_Barrier(MPI_COMM_WORLD);
MPI_Bcast(buffer,N,MPI_FLOAT,MPI_COMM_WORLD);
MPI_Barrier(MPI_COMM_WORLD);

我不确定 MPI_Bcast() 是否已经具有自然阻塞功能。如果这是真的,我可能不需要 MPI_Barrier() 来同步所有内核的进度。那我只能用:

MPI_Bcast(buffer,MPI_COMM_WORLD);

一个是正确的?

解决方法

很少需要在 MPI 中执行显式同步,这样的代码一般来说意义不大。 MPI 中的 Rank 主要在本地处理数据,不共享对全局对象的访问权限,并按照发送和接收操作的语义隐式同步。当 i 在本地处理接收到的数据时,为什么 rank j 应该关心其他一些 rank i 是否收到了广播?

在以下情况下通常需要显式障碍:

  • 基准测试 - 在代码的定时区域之前设置一个屏障,消除由于一个或多个队列迟到而导致的任何无关等待时间
  • 并行 I/O - 在这种情况下,有一个全局对象(共享文件),其内容的一致性可能取决于 I/O 操作的正确顺序,因此需要显式同步
  • 单边操作 (RMA) - 与并行 I/O 情况类似,某些 RMA 方案需要显式同步
  • 共享内存窗口 - 这些是 RMA 的一个子集,其中对多个 rank 之间共享的内存的访问不通过 MPI 调用,而是直接发出内存读写指令,这带来了共享内存固有的所有问题编程喜欢发生数据竞争的可能性,因此需要锁定和障碍进入 MPI

很少有代码真正有意义的情况。根据等级的数量、它们在整个处理元件网络中的分布、要广播的数据的大小、互连的延迟和带宽以及 MPI 库用于实际实现数据分布的算法,可能需要由于延迟传播现象,当行列在时间上稍微不对齐时,完成的时间要长得多,这也可能适用于用户代码本身。这些都是病理情况,通常发生在特定条件下,这就是为什么有时您可能会看到如下代码:

#ifdef UNALIGNED_BCAST_IS_SLOW
MPI_Barrier(MPI_COMM_WORLD);
#endif
MPI_Bcast(buffer,N,MPI_FLOAT,MPI_COMM_WORLD);

甚至

if (config.unaligned_bcast_performance_remedy)
  MPI_Barrier(MPI_COMM_WORLD);
MPI_Bcast(buffer,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,root,comm,MPI_STATUS_IGNORE);
}

在这种情况下,排名 0(如果 root 不是 0)或排名 1(当 root0)将成为第一个接收数据的人,并且由于没有更多的通信指向或来自它,可以安全地从广播调用返回。如果数据缓冲区很大且互连速度较慢,则会在行列之间产生相当多的时间交错。