如何在MPI中传递二维数组并使用C语言创建动态标签值?
How to pass 2D array in MPI and create a dynamic tag value using C language?
我是 MPI 编程新手。我有一个 8 x 10 数组,我需要用它来并行查找每一行的总和。在等级 0(进程 0)中,它将使用二维数组生成 8 x 10 矩阵。然后我会使用 tag
数字作为数组的第一个索引值(行号)。这样,我可以使用一个独特的缓冲区通过 Isend 发送。但是,我为 Isend 生成标签号的方法似乎不起作用。您能否查看以下代码并告诉我是否正确传递了二维数组和标签号。当我 运行 这段代码时,它在执行 rannk 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();
}
标签问题是因为标签在不同进程中的计算方式(或不计算方式)。您正在将所有进程的标记值初始化为
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 循环来确定要发送哪个子数组,而不是两个嵌入式循环。然后,你可以计算出将数组发送到as
的过程
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 个工作进程(非零秩进程)。
但是,如果这不是您关心的问题,那么您就不需要处理此类情况。
除了显而易见的问题:"why on earth would you want to do that?",这里还有很多问题,我不确定是否能够一一列举。不过我会试一试:
标签:看来您的大部分方法是使用标签作为在何处寻找接收者的指示器。但是这里(至少)有两个主要缺陷:
- 由于
tag
在接收前不知道,所以 &arr[tag][0]
应该是什么?
- MPI 中的标签是消息"identifier"...在正常情况下,给定的通信(发送和匹配接收)应该有一个匹配的标签。这可以通过在接收方使用
MPI_ANY_TAG
特殊标记并使用接收状态的 MPI_TAG
字段检索其实际值来缓解。但那是另外一回事了。
这里的底线是该方法不是很好。
数据初始化:非阻塞 MPI 通信的主要原则之一是您应该永远不要修改用于 post 的通信(此处为 MPI_Isend()
)及其最终确定(此处缺失)。因此,您的数据生成必须发生在之前尝试传输数据。
说到这里,通信完成:你也完成了你发送的通信。这可以使用等待类型调用(MPI_Wait()
或 MPI_Waitall()
)或测试类型调用的 "infinite" 循环(MPI_Test()
等)来完成...
MPI_Irecv()
:为什么在下一个调用是 MPI_Wait()
时使用非阻塞接收?如果你想要阻塞接收,直接调用MPI_Recv()
即可。
所以从根本上说,您在这里尝试做的事情看起来并不正确。因此,我不太愿意向您提出更正后的版本,因为我不了解您要解决的实际问题。这段代码是一个更大的真实代码的缩减版本(或者应该增长的初始版本),还是只是一个玩具示例,旨在让您了解 MPI send/receive 是如何工作的?您没有使用 MPI_Scatter()
等集体交流的根本原因是什么?
根据你对这些问题的回答,我可以尝试制作一个有效的版本。
我是 MPI 编程新手。我有一个 8 x 10 数组,我需要用它来并行查找每一行的总和。在等级 0(进程 0)中,它将使用二维数组生成 8 x 10 矩阵。然后我会使用 tag
数字作为数组的第一个索引值(行号)。这样,我可以使用一个独特的缓冲区通过 Isend 发送。但是,我为 Isend 生成标签号的方法似乎不起作用。您能否查看以下代码并告诉我是否正确传递了二维数组和标签号。当我 运行 这段代码时,它在执行 rannk 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();
}
标签问题是因为标签在不同进程中的计算方式(或不计算方式)。您正在将所有进程的标记值初始化为
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 循环来确定要发送哪个子数组,而不是两个嵌入式循环。然后,你可以计算出将数组发送到as
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 个工作进程(非零秩进程)。
但是,如果这不是您关心的问题,那么您就不需要处理此类情况。
除了显而易见的问题:"why on earth would you want to do that?",这里还有很多问题,我不确定是否能够一一列举。不过我会试一试:
标签:看来您的大部分方法是使用标签作为在何处寻找接收者的指示器。但是这里(至少)有两个主要缺陷:
- 由于
tag
在接收前不知道,所以&arr[tag][0]
应该是什么? - MPI 中的标签是消息"identifier"...在正常情况下,给定的通信(发送和匹配接收)应该有一个匹配的标签。这可以通过在接收方使用
MPI_ANY_TAG
特殊标记并使用接收状态的MPI_TAG
字段检索其实际值来缓解。但那是另外一回事了。
这里的底线是该方法不是很好。
- 由于
数据初始化:非阻塞 MPI 通信的主要原则之一是您应该永远不要修改用于 post 的通信(此处为
MPI_Isend()
)及其最终确定(此处缺失)。因此,您的数据生成必须发生在之前尝试传输数据。说到这里,通信完成:你也完成了你发送的通信。这可以使用等待类型调用(
MPI_Wait()
或MPI_Waitall()
)或测试类型调用的 "infinite" 循环(MPI_Test()
等)来完成...MPI_Irecv()
:为什么在下一个调用是MPI_Wait()
时使用非阻塞接收?如果你想要阻塞接收,直接调用MPI_Recv()
即可。
所以从根本上说,您在这里尝试做的事情看起来并不正确。因此,我不太愿意向您提出更正后的版本,因为我不了解您要解决的实际问题。这段代码是一个更大的真实代码的缩减版本(或者应该增长的初始版本),还是只是一个玩具示例,旨在让您了解 MPI send/receive 是如何工作的?您没有使用 MPI_Scatter()
等集体交流的根本原因是什么?
根据你对这些问题的回答,我可以尝试制作一个有效的版本。