使用MPI(C)交换晕/鬼单元时出现未知错误

时间:2013-03-23 13:18:18

标签: c mpi

我是MPI的新手,所以对我很轻松......无论如何,我正在尝试使用MPI_Isend和MPI_Irecv进行非阻塞通信。我写了一个名为“halo_exchange”的子程序,我想在每次需要在相邻子域之间交换光环单元时调用它。我能够正确地分割域名,并且我知道我的每个邻居队伍。在下面的代码中,邻居定向为北/南(即我使用1D行分解)。所有过程都用于计算。换句话说,所有进程都将调用此子例程并需要交换数据。

最初我在北边界和南边界都使用了一组MPI_Isend / MPI_Irecv调用,但后来我把它分开,以为将“MPI_PROC_NULL”传递给函数可能有问题(边界不是周期性的)。这就是if语句的原因。代码继续挂在“MPI_Waitall”语句上,我不知道为什么?字面上只是等待,我不确定它在等什么?

#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>

//---------------------------------------------------------------------------------------
// FUNCTION "halo_exchange"
//---------------------------------------------------------------------------------------
void halo_exchange(PREFIX **array, MPI_Comm topology_comm,              \
       int nn, int S_neighbor, int N_neighbor)
{
  int halo = 2;
  int M = 20;

  ...

  double *S_Recv,*N_Recv;
  double *S_Send,*N_Send;

  // Receive buffers
  S_Recv = (double *) calloc( M*halo,sizeof(double) );
  N_Recv = (double *) calloc( M*halo,sizeof(double) );

  // Send buffers
  S_Send = (double *) calloc( M*halo,sizeof(double) );
  N_Send = (double *) calloc( M*halo,sizeof(double) );

  ...
  // send buffers filled with data
  // recv buffers filled with zeros (is this ok...or do I need to use malloc?)
  ...

  if (S_neighbor == MPI_PROC_NULL)
  {
  MPI_Status status[2];
  MPI_Request req[2];

  MPI_Isend(&N_Send,halo*M,MPI_DOUBLE,N_neighbor,2,topology_comm,&req[0]);
  MPI_Irecv(&N_Recv,halo*M,MPI_DOUBLE,N_neighbor,2,topology_comm,&req[1]);
      ...
      ...
  MPI_Waitall(2,req,status);

  }
  else if (N_neighbor == MPI_PROC_NULL)
  {
  MPI_Status status[2];
  MPI_Request req[2];

  MPI_Isend(&S_Send,halo*M,MPI_DOUBLE,S_neighbor,1,topology_comm,&req[0]);
  MPI_Irecv(&S_Recv,halo*M,MPI_DOUBLE,S_neighbor,1,topology_comm,&req[1]);
      ...
      ...
  MPI_Waitall(2,req,status);

  }
  else
  {
  MPI_Status status[4];
  MPI_Request req[4];

  MPI_Isend(&S_Send,halo*M,MPI_DOUBLE,S_neighbor,1,topology_comm,&req[0]);
  MPI_Isend(&N_Send,halo*M,MPI_DOUBLE,N_neighbor,2,topology_comm,&req[1]);

  MPI_Irecv(&N_Recv,halo*M,MPI_DOUBLE,N_neighbor,2,topology_comm,&req[2]);
  MPI_Irecv(&S_Recv,halo*M,MPI_DOUBLE,S_neighbor,1,topology_comm,&req[3]);
      ...
      ...
  MPI_Waitall(4,req,status);

  }
  ...
}

这是我最初的理解,显然缺少一些东西: 由于每个进程都调用此子例程,因此将调用所有send / recv函数。然后,所有进程将在其MPI_Waitall点等待相应的通信发生。当他们完成它继续前进....有人可以告诉我为什么我的不动?另外我对“标签”论点不太清楚(线索?)感谢你提前帮助!!!

2 个答案:

答案 0 :(得分:3)

这段代码

  MPI_Status status[4];
  MPI_Request req[4];

  MPI_Isend(&S_Send,halo*M,MPI_DOUBLE,S_neighbor,1,topology_comm,&req[0]);
  MPI_Isend(&N_Send,halo*M,MPI_DOUBLE,N_neighbor,2,topology_comm,&req[1]);

  MPI_Irecv(&N_Recv,halo*M,MPI_DOUBLE,N_neighbor,2,topology_comm,&req[2]);
  MPI_Irecv(&S_Recv,halo*M,MPI_DOUBLE,S_neighbor,1,topology_comm,&req[3]);
      ...
      ...
  MPI_Waitall(4,req,status);

大部分都很好,你不应该在if邻居附近MPI_PROC_NULL;这就是MPI_PROC_NULL的用途,因此您可以将角落案例推入MPI例程,从而大大简化面向开发人员的通信代码。

这里的问题实际上是标签。标签附加到单个邮件。标签可以是任何非负整数,最大可达一定,但关键是发送方和接收方必须就标签达成一致。

如果您向北方邻居发送带有标签2的一些数据,那很好,但现在假装您是北方邻居;您将从您的南方邻居收到标记为2 的相同消息。同样,如果您要使用标记1发送南邻居数据,则该南邻居将需要从其北邻居使用标记1接收该邻居数据。

所以你真的想要

  MPI_Isend(&S_Send,halo*M,MPI_DOUBLE,S_neighbor,1,topology_comm,&req[0]);
  MPI_Isend(&N_Send,halo*M,MPI_DOUBLE,N_neighbor,2,topology_comm,&req[1]);

  MPI_Irecv(&N_Recv,halo*M,MPI_DOUBLE,N_neighbor,1,topology_comm,&req[2]);
  MPI_Irecv(&S_Recv,halo*M,MPI_DOUBLE,S_neighbor,2,topology_comm,&req[3]);

根据以下OP评论进行更新:

事实上,由于S_Recv等已经指向数据,因为:

  S_Recv = (double *) calloc( M*halo,sizeof(double) );

你真正想要的是:

  MPI_Isend(S_Send,halo*M,MPI_DOUBLE,S_neighbor,1,topology_comm,&req[0]);
  MPI_Isend(N_Send,halo*M,MPI_DOUBLE,N_neighbor,2,topology_comm,&req[1]);

  MPI_Irecv(N_Recv,halo*M,MPI_DOUBLE,N_neighbor,1,topology_comm,&req[2]);
  MPI_Irecv(S_Recv,halo*M,MPI_DOUBLE,S_neighbor,2,topology_comm,&req[3]);

答案 1 :(得分:1)

除了正确获取标签之外,您还可以进一步改进代码。您使用非阻塞操作实现的数据通信操作非常常见,MPI提供了自己的调用 - MPI_SENDRECV。有了它,您的代码将简化为:

MPI_Sendrecv(&S_Send, halo*M, MPI_DOUBLE, S_neighbor, 0,
             &N_Recv, halo*M, MPI_DOUBLE, N_neighbor, 0,
             topology_comm, MPI_STATUS_IGNORE);
MPI_Sendrecv(&N_Send, halo*M, MPI_DOUBLE, N_neighbor, 0,
             &S_Recv, halo*M, MPI_DOUBLE, S_neighbor, 0,
             topology_comm, MPI_STATUS_IGNORE);

这里有几点:

  • 您不需要使用单独的标签进行不同方向的通信。它只会像你原来的问题一样导致混淆。
  • 按照概述的MPI_ISEND顺序替换MPI_IRECV / MPI_SENDRECV方案,可以轻松地将光环交换扩展为2D,3D和anyD情况。当然你仍然可以使用非阻塞发送,但如果按顺序使用MPI_SENDRECV,它会自动将对角线元素移动到相应的光环(即它将最左边的本地元素移动到左上角的光环2D中的对角邻居。