如何在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,因为 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 循环来确定要发送哪个子数组,而不是两个嵌入式循环。然后,你可以计算出将数组发送到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?",这里还有很多问题,我不确定是否能够一一列举。不过我会试一试:

  • 标签:看来您的大部分方法是使用标签作为在何处寻找接收者的指示器。但是这里(至少)有两个主要缺陷:

    1. 由于 tag 在接收前不知道,所以 &arr[tag][0] 应该是什么?
    2. 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() 等集体交流的根本原因是什么?

根据你对这些问题的回答,我可以尝试制作一个有效的版本。