MPI_Allgather是一个瓶颈,如何用MPI_Send和MPI_Recv打破它?

问题描述 投票:-2回答:1
       float * simulate(const float alpha, const long n_segments, const int n_steps, float *d_buf1, float *d_buf2, const int rank, const int world_size, const long segments_per_process) {

      float* d_t  = d_buf1;  // buffer for d(*, t)
      float* d_t1 = d_buf2;  // buffer for d(*, t+1)

      const long start_segment = segments_per_process*((long)rank)   +1L;
      const long last_segment  = segments_per_process*((long)rank+1L)+1L;

      const float dx = 1.0f/(float)n_segments;
      const float phase = 0.5f;

      MPI_Status stat;
      for(int t = 0; t < n_steps; t++) {
    #pragma omp parallel for simd
        for(long i = start_segment; i < last_segment; i++) {
          const float L_x = L(alpha,phase,i*dx);
          d_t1[i] = L_x*(d_t[i+1] + d_t[i-1])
                    +2.0f*(1.0f-L_x)*(d_t[i]) 
                    - d_t1[i]; // The algorithm calls for d(i, t-1) here, but that is currently contained in d_t1
        }

        float* temp = d_t1; d_t1 = d_t; d_t = temp;
        MPI_Allgather(MPI_IN_PLACE, 0, MPI_DATATYPE_NULL, &d_t[1], segments_per_process, MPI_FLOAT, MPI_COMM_WORLD);

      }
      return d_t;
    }

这是使用MPI计算字符串振动的程序。在这个程序中,我们必须使用给定的等级来完成MPI_Send和MPI_Recv的任务。这样可以更有效地执行


这是我为实现@Peter Cordes的答案所做的修改。它没有给出正确的输出,你能看出我做错了什么吗?

float * simulate(const float alpha, const long n_segments, const int n_steps, float *d_buf1, float *d_buf2, const int rank, const int world_size, const long segments_per_process) {

  float* d_t  = d_buf1;  // buffer for d(*, t)
  float* d_t1 = d_buf2;  // buffer for d(*, t+1)

  const long start_segment = segments_per_process*((long)rank)   +1L;
  const long last_segment  = segments_per_process*((long)rank+1L)+1L;

  const float dx = 1.0f/(float)n_segments;
  const float phase = 0.5f;

  MPI_Status stat;
  for(int t = 0; t < n_steps; t++) {
      MPI_Barrier(MPI_COMM_WORLD);
#pragma omp parallel for simd
    for(long i = start_segment; i < last_segment; i++) {
      const float L_x = L(alpha,phase,i*dx);
      d_t1[i] = L_x*(d_t[i+1] + d_t[i-1])
                +2.0f*(1.0f-L_x)*(d_t[i]) 
                - d_t1[i]; // The algorithm calls for d(i, t-1) here, but that is currently contained in d_t1
    }

    float* temp = d_t1; d_t1 = d_t; d_t = temp;

    /*MPI_Bcast(&d_t,1,
    MPI_Allgather(MPI_IN_PLACE, 0, MPI_DATATYPE_NULL, &d_t[1], segments_per_process, MPI_FLOAT, MPI_COMM_WORLD);
    */

    MPI_Send(&d_t, 1, MPI_FLOAT, rank - 1, 0, MPI_COMM_WORLD);
    MPI_Send(&d_t[segments_per_process-1], 1, MPI_FLOAT, rank + 1, 1, MPI_COMM_WORLD);

    MPI_Recv(&d_t, 1, MPI_FLOAT, rank, 0, MPI_COMM_WORLD,&stat);
    MPI_Recv(&d_t[segments_per_process-1], 1, MPI_FLOAT, rank, 1, MPI_COMM_WORLD, &stat);
  }
  return d_t;
}
c++ optimization mpi intel openmpi
1个回答
0
投票

我认为需要在MPI任务之间进行任何通信的唯一原因是分别在段的开头和结尾访问d_t[i-1]i+1

如果您没有交换数据,那么您将阅读此任务未重新计算的陈旧元素。

但是,不是全部同步,而是每个任务只需要将其段的开头发送到之前处理该段的任务。 (同样,它的细分到下一个等级)。

通过发送/接收执行此操作。


更好的是,在最后将这些段重叠一点,这样您就可以更少地进行通信。 “错误的”数据每个外循环迭代将传播1个元素,因此8个元素(1个32字节AVX向量)的重叠应该意味着您只需要每8次迭代进行一次通信。

理想情况下,我们可以管理消息以隐藏网络延迟。与千兆位以太网上的机器之间的延迟相比,计算速度非常快(1微秒= 3GHz时约3000个时钟周期= ~48k浮点FMA操作,假设每个时钟FMA为2个,32字节向量= Haswell / Skyake的理论最大吞吐量)。所以我认为让接收器复制一些关于这些元素的工作是一个不错的选择。

如果每12次迭代发送16个元素(每个在开始/结束时),但在调用receive之前发送2个外循环迭代,则在收到它时将过时2次迭代。 (实际上,如果可以避免破坏自动向量化和OMP自动并行化,则展开外部循环或使用嵌套循环。)

但这很好,与一个段的整个大小相比,接收方需要花费大量时间对该数据运行额外的2次迭代并将其捕获。如果任务保存它们发送时的2个元素,它们可以将它与接收到的块组合起来,需要2个更少的重叠元素,最终在接收端正确> = 13个数组元素。

调试时,您可能希望至少有一个额外的重叠元素,这样你就可以assert冗余元素互相==。 (包括验证您的代码是否以相同的方式进行优化,通过收缩倍增+添加到FMA中.IEEE FP数学是确定性的,但编译器有一定的自由......)

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.