使用 MPI (C) 交换光环/重影单元时出现未知错误
Posted
技术标签:
【中文标题】使用 MPI (C) 交换光环/重影单元时出现未知错误【英文标题】:Unknown error with exchanging halo/ghost cells using MPI (C) 【发布时间】:2013-03-13 07:20:35 【问题描述】:我是 MPI 新手,所以请放轻松...无论如何,我正在尝试使用 MPI_Isend 和 MPI_Irecv 进行非阻塞通信。我编写了一个名为“halo_exchange”的子程序,每次我需要在相邻子域之间交换光环单元时都想调用它。我能够正确地拆分域,并且我知道我的每个邻居等级。在下面的代码中,邻居是北/南的(即我使用一维行分解)。所有过程都用于计算。也就是说,所有的进程都会调用这个子程序,并且需要交换数据。
最初我对北边界和南边界使用一组 MPI_Isend/MPI_Irecv 调用,但后来我将其拆分,认为将“MPI_PROC_NULL”传递给函数可能有问题(边界不是周期性的)。这就是 if 语句的原因。代码继续挂在“MPI_Waital”语句上,我不知道为什么?它实际上只是在等待,我不确定它在等待什么?
#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);
...
这是我最初的理解,显然遗漏了一些东西: 由于每个进程调用这个子程序,所有的发送/接收函数都被调用。然后所有进程将在它们的 MPI_Waital 点等待相应的通信发生。当他们完成后,它会继续前进....有人能告诉我为什么我的不动吗???另外我对“标签”论点不太清楚(线索?)提前感谢您的所有帮助!!!
【问题讨论】:
【参考方案1】:这段代码
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);
大部分都很好,你不应该在MPI_PROC_NULL
邻居周围使用if
;这就是 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]);
【讨论】:
感谢大家的帮助!!!不幸的是,这两种解决方案都不起作用。我不断收到“分段错误 (11)”“地址未映射”。使用上述任一解决方案时,我都会收到相同的错误。我花了上周试图修复它......没有运气! 1) 是否所有等级都可以调用该子程序? 2) 出于调试目的,子程序简单地交换 2 个预先分配有 0 的数组。数组的大小在发送/接收例程中是相同的,那么分段错误来自哪里? 3) 这是 OpenMPI 问题还是我的问题?再次感谢您的帮助!!! 好的...我想我可能已经修复了它,但它可能值得确认。由于我将变量(例如 S_Send)分配为指向 double 类型指针的指针,我是否应该只传递变量(例如 S_Send)而不是 &S_Send?它现在运行良好,但这是否有意义......再次,MPI 的新手。谢谢!! 嗨,@ThatsRightJack;这是正确的。您将通信例程指针传递给您正在发送或接收的数据(例如,指向type
的count
的指针)。因此,如果 S_Send 已经是一个指向数据的指针(例如,一个数组名),您需要将其作为S_Send
传递,而不是&S_Send
。我应该从您的代码摘录中看到这一点;我会相应地更新答案。
非常感谢乔纳森的帮助!!【参考方案2】:
除了正确获取标签之外,您还可以进一步改进您的代码。您使用非阻塞操作实现的数据通信操作非常常见,以至于 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)。
【讨论】:
感谢大家的帮助!!!不幸的是,这两种解决方案都不起作用。我不断收到“分段错误 (11)”“地址未映射”。使用上述任一解决方案时,我都会收到相同的错误。我花了上周试图修复它......没有运气! 1) 是否所有等级都可以调用该子程序? 2) 出于调试目的,子程序简单地交换 2 个预先分配有 0 的数组。数组的大小在发送/接收例程中是相同的,那么分段错误来自哪里? 3) 这是 OpenMPI 问题还是我的问题?再次感谢您的帮助!!! 好的...我想我可能已经修复了它,但它可能值得确认。由于我将变量(例如 S_Send)分配为指向 double 类型指针的指针,我是否应该只传递变量(例如 S_Send)而不是 &S_Send?它现在运行良好,但这是否有意义......再次,MPI 的新手。谢谢!! @ThatsRightJack,所有 MPI 通信调用都采用指向实际数据的指针。传递指向指针的指针是行不通的,除非你想发送指针本身的值,即数据的虚拟地址而不是实际数据。以上是关于使用 MPI (C) 交换光环/重影单元时出现未知错误的主要内容,如果未能解决你的问题,请参考以下文章
InvalidArgumentException:编写单元测试时出现未知的格式化程序