如何在MPI中传递2D数组并使用C语言创建动态标记值?

我是MPI编程的新手。 我有一个8乘10的数组,我需要用来平行地找到每一行的总和。 在等级0(过程0)中,它将使用二维arrays生成8乘10的矩阵。 然后我会使用tag号作为数组的第一个索引值(行号)。 这样,我可以使用唯一的缓冲区通过Isend发送。 但是,看起来我的Isend标签号生成方法不起作用。 您能否查看以下代码并告诉我是否正确传递2D数组和标签号。 当我运行此代码时,它会在执行runnk 1后停止并等待。 我在本例中使用了3个进程并使用命令mpirun -np 3 test请尽可能通过示例告诉我如何解决此问题。

 #include "mpi.h" #include  #include  int main (int argc, char *argv[]) { MPI_Init(&argc, &argv); int world_rank; MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); int world_size; MPI_Comm_size(MPI_COMM_WORLD, &world_size); int tag = 1; int arr[8][10]; MPI_Request request; MPI_Status status; int source = 0; int dest; printf ("\n--Current Rank: %d\n", world_rank); if (world_rank == 0) { int i = 0; int a, b, x, y; printf("* Rank 0 excecuting\n"); for(a=0; a<8/(world_size-1); a++)//if -np is 3, this will loop 4 times { for(b=0; b<(world_size-1); b++)//if -np is 3, this loops will loop 2 times {//So, if -np is 3, due to both of these loops, Isend will be called 8 times dest = b+1; tag = a+b;//create a uniqe tag value each time, which can be use as first index value of array //Error: This tag value passing to Isend doesn't seems to be workiing MPI_Isend(&arr[tag][0], 10, MPI_INT, dest, tag, MPI_COMM_WORLD, &request); } } for(x=0; x<8; x++)//Generating the whole 8 by 10 2D array { i++; for ( y = 0; y < 10; y++ ) { arr[x][y] = i; } } } else { int a, b; for(b=1; b<=8/(world_size-1); b++) { int sum = 0; int i; MPI_Irecv(&arr[tag][0], 10, MPI_INT, source, tag, MPI_COMM_WORLD, &request); MPI_Wait (&request, &status); //Error: not getting the correct tag value for(i = 0; i<10; i++) { sum = arr[tag][i]+sum; } printf("\nSum is: %d at rank: %d and tag is:%d\n", sum, world_rank, tag); } } MPI_Finalize(); } 

标签问题是因为标签在不同进程上的计算方式(或不计算)。 您正在为所有进程初始化标记值

 int tag = 1; 

然后,对于进程等级0,您将标记设置为

 tag = a+b; 

这是第一次设置,将tag设置为0,因为ab都从零开始。 但是,对于排名大于0的进程,标记永远不会更改。 他们将继续将标记设置为1。

标签唯一标识由MPI_IsendMPI_Irecv发送的消息,这意味着发送及其相应的接收必须具有相同的标记才能成功进行数据传输。 由于大多数接收的进程之间的标记不匹配,因此传输大多不成功。 这导致排名高于0的进程最终在调用MPI_Wait永远阻塞(等待)。

为了解决这个问题,您必须确保更改排名大于零的进程的标记。 但是,在我们能够做到这一点之前,还有其他一些值得探讨的问题。

通过现在为等级0过程设置标记的方式, tag只能有0到4的值(假设有3个过程)。 这是因为a限制在0到3的范围内,而b只能有0或1的值。这些值的最大可能总和为4.这意味着当您使用arr[tag][0]访问数组时,你会错过很多数据,你会多次重新发送相同的行。 我建议您更改发送每个子数组的方式(您当前使用tag访问它),这样您只有一个for循环来确定要发送的子数组,而不是两个嵌入式循环。 然后,您可以计算将arrays发送到的过程

 dest = subarray_index%(world_size - 1) + 1; 

这将使等级大于零的过程之间的设置交替。 您可以将标记保持为subarray_index 。 在接收方,您需要按接收计算每个进程的标记。

最后,我看到你在发送数据后正在初始化你的数组。 你想事先做到这一点。

结合所有这些方面,我们得到

 #include "mpi.h" #include  #include  int main (int argc, char *argv[]) { MPI_Init(&argc, &argv); int world_rank; MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); int world_size; MPI_Comm_size(MPI_COMM_WORLD, &world_size); int tag = 1; int arr[8][10]; MPI_Request request; MPI_Status status; int source = 0; int dest; printf ("\n--Current Rank: %d\n", world_rank); if (world_rank == 0) { int i = 0; int a, b, x, y; printf("* Rank 0 excecuting\n"); //I've moved the array generation to before the sends. for(x=0; x<8; x++)//Generating the whole 8 by 10 2D array { i++; for ( y = 0; y < 10; y++ ) { arr[x][y] = i; } } //I added a subarray_index as mentioned above. int subarray_index; for(subarray_index=0; subarray_index < 8; subarray_index++) { dest = subarray_index%(world_size - 1) + 1; tag = subarray_index; MPI_Isend(&arr[subarray_index][0], 10, MPI_INT, dest, tag, MPI_COMM_WORLD, &request); } } else { int a, b; for(b=0; b<8/(world_size-1); b++) { int sum = 0; int i; //We have to do extra calculations here. These match tag, dest, and subarray. int my_offset = world_rank-1; tag = b*(world_size-1) + my_offset; int subarray = b; MPI_Irecv(&arr[subarray][0], 10, MPI_INT, source, tag, MPI_COMM_WORLD, &request); MPI_Wait (&request, &status); for(i = 0; i<10; i++) { sum = arr[subarray][i]+sum; } printf("\nSum is: %d at rank: %d and tag is:%d\n", sum, world_rank, tag); } } MPI_Finalize(); } 

在这个版本中有一件事似乎还有点未完成供您考虑:如果您的流程数量发生变化会发生什么? 例如,如果你有4个进程而不是3个进程,看起来你可能会遇到循环问题

 for(b=0; b<8/(world_size-1); b++) 

因为每个进程将执行相同的次数,但发送的数据量不会干净地分配给3个工作者(非秩零进程)。

但是,如果您不关心这一点,那么您不需要处理此类情况。

除了一个显而易见的问题:“为什么你想要这样做呢?”,这里有很多问题,我不确定我是否能够列出所有问题。 我会尝试一下:

  • 标签:似乎你的方法的大部分是使用标签作为寻找接收器的位置的指示器。 但这里至少存在两个主要缺陷:

    1. 由于tag在接收之前不知道,什么是&arr[tag][0]应该是什么?
    2. MPI中的标签是消息“标识符”……在正常情况下,给定的通信(发送和匹配接收)应该具有匹配的标记。 这可以通过在接收方使用MPI_ANY_TAG特殊标记来缓解,并使用接收状态的MPI_TAG字段检索其实际值。 但这是另一个故事。

    这里的底线是该方法不是那么好。

  • 数据初始化:非阻塞MPI通信的主要原则之一是,您永远不应该修改用于通信postMPI_Isend()此处为MPI_Isend() )与其终结(此处缺失)之间通信的缓冲区。 因此,您的数据生成必须尝试传递数据之前发生。

  • 说到这一点,沟通最终确定:你完成了你的发送通信。 这可以使用wait-type调用( MPI_Wait()MPI_Waitall() ),或者测试类型调用的“无限”循环( MPI_Test()等)来完成…

  • MPI_Irecv() :为什么在下次调用MPI_Wait()时使用非阻塞接收? 如果您想要阻止接收,只需直接调用MPI_Recv()

所以从根本上说,你在这里尝试做的事情看起来并不正确。 因此,我非常不愿意向您提出更正版本,因为我不了解您尝试解决的实际问题。 这段代码是一个更大的真实版本的减少版本(或者应该增长的东西的初始版本),或者只是一个玩具示例,意味着你可以学习MPI如何发送/接收工作? 你没有使用像MPI_Scatter()这样的集体通信的根本原因是什么?

根据您对这些问题的回答,我可以尝试生成有效版本。