我是MPI编程的新手。我有一个8乘10的数组,我需要用它来平行地找到每一行的总和。在等级0(过程0)中,它将使用二维阵列生成8乘10的矩阵。然后我会使用tag
数作为数组的第一个索引值(行号)。这样,我可以使用唯一的缓冲区通过Isend发送。但是,看起来我的Isend标签号生成方法不起作用。您能否查看以下代码并告诉我是否正确传递2D数组和标签号。当我运行此代码时,它会在执行runnk 1后停止并等待。我在本例中使用3个进程并使用命令mpirun -np 3 test
如果可能的话请通过示例告诉我如何解决这个问题。
#include "mpi.h"
#include <stdio.h>
#include <stdlib.h>
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();
}
答案 0 :(得分:3)
标签问题是因为标签在不同进程上的计算方式(或不计算)。您正在将所有流程的标记值初始化为
int tag = 1;
以后,对于流程等级0,您将标记设置为
tag = a+b;
这是第一次设置此项时,会将tag
设置为0,因为a
和b
都从零开始。但是,对于排名大于0的进程,标记永远不会更改。他们将继续将标记设置为1。
标记唯一标识MPI_Isend
和MPI_Irecv
发送的消息,这意味着发送及其相应的接收必须具有相同的标记才能成功进行数据传输。由于大多数接收的进程之间的标记不匹配,因此传输大多不成功。这导致排名高于0的进程最终在调用MPI_Wait
时永久阻塞(等待)。
为了解决这个问题,您必须确保更改排名大于零的进程的标记。但是,在我们做到这一点之前,还有一些其他问题值得探讨。
通过现在为等级0流程设置标记的方式,tag
只能有0到4的值(假设有3个进程)。这是因为a
限制在0到3的范围内,b
只能有0或1的值。这些值的最大可能总和为4.这意味着当您使用arr[tag][0]
,您将错过很多数据,并且您将多次重新发送相同的行。我建议您更改发送每个子数组的方式(当前正使用tag
访问),这样您只有一个for循环来确定要发送哪个子数组,而不是两个嵌入式循环。然后,您可以计算将数组发送到
dest = subarray_index%(world_size - 1) + 1;
这将在等级大于零的进程之间交替进行设置。您可以将代码保留为subarray_index
。在接收方,您需要按接收计算每个流程的标记。
最后,我发现您在发送数据后正在初始化数组。你想事先做到这一点。
结合所有这些方面,我们得到
#include "mpi.h"
#include <stdio.h>
#include <stdlib.h>
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 :(得分:1)
除了一个显而易见的问题:&#34;为什么你想要这样做?&#34;,这里有很多问题,我不确定我是否能够列出所有。我试试看:
标签:您的方法似乎是使用标签作为查找接收器位置的指示器。但是这里至少存在两个主要缺陷:
tag
在接收之前不知道,&arr[tag][0]
应该是什么?MPI_ANY_TAG
特殊标记,并使用接收方状态的MPI_TAG
字段检索其实际值来缓解。但那是另一个故事。这里的底线是该方法不是很好。
数据初始化:非阻塞MPI通信的主要原则之一是您应该永远修改用于通信帖子之间通信的缓冲区({{ 1}}这里)和它的终结(这里缺少)。因此,您的数据生成必须在之前尝试传递数据。
谈到这一点,沟通最终确定:您已经完成发送通信。这可以使用等待类型调用(MPI_Isend()
或MPI_Wait()
)或&#34;无限&#34;来完成。测试类型调用循环(MPI_Waitall()
等)......
MPI_Test()
:为什么在下次通话MPI_Irecv()
时使用非阻止接收?如果您想要阻止接收,只需直接致电MPI_Wait()
。
所以从根本上说,你在这里尝试做的事情看起来并不正确。因此,我非常不愿意向您提出更正版本,因为我不了解您尝试解决的实际问题。这段代码是一个更大的真实版本(或者应该增长的东西的初始版本)的简化版本,还是只是一个玩具示例,意味着你可以学习MPI发送/接收的工作方式?您有没有使用MPI_Recv()
等集体沟通的任何根本原因?
根据您对这些问题的回答,我可以尝试制作有效的版本。