使用 MPI 发送和接收派生数据类型

Send and Receive derived Datatypes with MPI

我想以最简单的方式将 NXN 矩阵的反对角元素从根进程发送到另一个进程。遗憾的是,目前我无法测试我的代码,因为计算节点已关闭。有人可以检查我的简单代码吗?

我不确定我是否正确发送了A的反诊断元素。反对角线元素会落在接收缓冲区 B 中吗?

#include "mpi.h"
#include <stdio.h>

int main(int argc, char** argv){

MPI_Init(&argc,&argv);
int size, rank;

MPI_Comm_rank(MPI_COMM_WORLD,&rank);
MPI_Comm_size(MPI_COMM_WORLD,&size);

int N=4;
double A[N][N];
double B[N];
MPI_Datatype antidiag;

int* blockleng=(int*)malloc(N*sizeof(int));
int* displace=(int*)malloc(N*sizeof(int));

for(int i=0; i<N; ++i){
    blockleng[i]=1;
    displace[i] = (i+1)*(N-1);
}

MPI_Type_indexed(N,blockleng,displace, MPI_DOUBLE,antidiag);
MPI_Type_commit(&antidiag);
MPI_Status status;

if(rank==0){
    A= {
            {1.0,5.0,9.0,13.0},
            {2.0,6.0,10.5,14.5},
            {3.0,7.2,11.0,15.0},
            {4.0,8.0,12.0,16.0}
    };


    MPI_Send(A,1,antidiag,1,100,MPI_COMM_WORLD);

}
if(rank==1){
    MPI_Recv(B,1,antidiag,0,100,MPI_COMM_WORLD,status);
}

MPI_Type_free(&antidiag);

MPI_Finalize();
return 0;
}

你的程序有几个问题。我不会 post 整个程序,而是将重点放在问题上。

  1. 循环内的变量声明从C99开始有效。如果您需要使用旧标准,这就是循环的样子,

    int i, j;  
    for(i=0; i<N; ++i){
        blockleng[i]=1;
        displace[i] = (i+1)*(N-1);
    }
    
  2. 在使用 MPI_Type_indexed 创建 MPI 数据类型时,您的最后一个参数(新类型)必须作为 pointer/handle:

    传递
    MPI_Type_indexed(N, blockleng, displace, MPI_DOUBLE, &antidiag);
    
  3. 您将值分配给矩阵 A 的方式只能作为初始化与声明相结合的一部分,更多 here。当然,你可以分别为 rank 0 和其他 ranks 这样做,但你也可以只从 rank 0 的文件中读取矩阵数据,

    char* file_in = "matrix_A.txt";
    FILE *fpi;
    
    fpi=fopen(file_in, "r");
    for (i=0; i<N; i++)
        for (j=0; j<N; j++)
            if (!fscanf(fpi, "%lf", &A[i][j]))
                break;
    

    作为 if(rank==0){ 块的一部分。您还可以将矩阵文件名作为命令行参数,这将为您提供更大的灵活性。在这一点上,您还希望将 N 作为命令行参数,以便能够定义您正在阅读的矩阵的大小。本例中使用的文件结构简单,

    1.0 5.0 9.0 13.0
    2.0 6.0 10.5 14.5
    3.0 7.2 11.0 15.0
    4.0 8.0 12.0 16.0
    
  4. 同一块中的发送部分只发送到一个等级,等级 1。如果我理解你的问题是正确的,你想将反对角线发送到等级 0 的所有其他等级,这需要以下循环,

    for (i=1; i<size; i++)
        MPI_Send(A, 1, antidiag, i, 100, MPI_COMM_WORLD);
    
  5. 同样,接收部分应该适用于除 0 以外的所有等级,

    else{ 
        MPI_Recv(B, N, MPI_DOUBLE, 0, 100, MPI_COMM_WORLD, &status);
    }
    

    请注意,这里还有其他更正 - 您正在接收 N 大小的 buffer/array B 中的数据,而不是 NxN 矩阵 A。atidiag 是为矩阵 A 创建的类型(或一个 N*N 元素一维缓冲区,在这种情况下是等效的)-它不适用于 N 元素缓冲区 B。因此,您需要将 MPI_Recv 更改为 expect/retrieve N 类型的元素 MPI_DOUBLE 反而。这是一项巧妙的功能,因为它允许您将数据接收到与发送数据结构不同的数组中。您还需要传递指向 status 的指针,因此 &status.

最后,您可以打印结果了,

 if (rank != 0){
    printf("Rank %d:\n", rank);
    for (i=0; i<N; i++)
      printf("%.2lf ", B[i]);
  }
  printf("\n\n");

请记住,打印到 stout 的 MPI 不是根据等级编号排序的,而且在打印过程中等级可能会相互中断。刷新 stdout 可能会有所帮助,但更好的选择是写入文件。在这种情况下,在我的系统上,输出是按应有的方式打印的。最多测试 6 个进程。

最后一点 - 通常在 1 个进程发送给所有其他进程的情况下,您应该考虑使用集体通信功能,如 MPI_Bcast。这些函数经过高度优化,并且在它们专门处理的情况下优于 Send\Recv。现在,使用 Bcast 您需要实际将接收到的反对角线存储在所有等级的 A 矩阵中,然后将其复制到array/buffer B 在 0 以外的等级上。这会增加几个步骤,所以我将它保留在你的 post 中。