c - 如何从处理器中获取 MPI_Gatherv 列,其中每个进程可能发送不同数量的列

标签 c mpi

假设有 2 个进程参与。进程 0(等级 0)有

A = { a d
      b e
      c f
    }

进程1(等级1)有

A = { g
      h
      i
    }

我希望两个处理器都将这些列发送到排名 0,以便排名 0 将在另一个二维数组中具有以下内容。

B = { a d g
      b e h
      c f i
    }

我为 MPI_Gatherv 创建了一个新的列数据类型,并正在尝试以下代码,这让我不知所措。

我的具体问题是:

  1. 我应该如何处理这个问题
  2. send_type 和 recv_type 应该是什么。
  3. 应如何指定位移(它们应该根据新数据类型还是 MPI_CHAR)

谢谢。

这是我的代码:

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

int main(int argc, char *argv[])
{
  int numprocs, my_rank;
   long int i, j;
   MPI_Status status;
   char **A;
   char **B;
  MPI_Init(&argc, &argv);
  MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
  MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);

  if(my_rank == 0)
  {
    A = (char **)calloc((3), sizeof(char *));
    B = (char **)calloc((3), sizeof(char *));
    for(i=0; i<3; ++i)
    {
       A[i] = (char *)calloc(2, sizeof(char));
       B[i] = (char *)calloc(3, sizeof(char));
    }

    A[0][0] = 'a';
    A[1][0] = 'b';
    A[2][0] = 'c';
    A[0][1] = 'd';
    A[1][1] = 'e';
    A[2][1] = 'f';
  }
  else
  {
    A = (char **)calloc((3), sizeof(char *));
    for(i=0; i<3; ++i)
    {
       A[i] = (char *)calloc(1, sizeof(char));
    }
    A[0][0] = 'g';
    A[1][0] = 'h';
    A[2][0] = 'i';

  }
  MPI_Datatype b_col_type;
  MPI_Type_vector(3, 1, 1, MPI_CHAR, &b_col_type);
  MPI_Type_commit(&b_col_type);
  int displs[2] = {0, 2};
  int recvcounts[2] = {2, 1};
  MPI_Gatherv(&A[0][0], recvcounts[my_rank], b_col_type, &B[0][0], recvcounts, displs,    b_col_type, 0, MPI_COMM_WORLD);
  if(my_rank == 0)
  {
    for(i=0; i<3; ++i)
    {
      for(j=0; j<3; ++j)
        printf("%c ", B[i][j]);
      printf("\n");
    }
  }
  MPI_Finalize();
  return 0;
}

最佳答案

所以首先 - 这一直伴随着 MPI 和 C 数组 - 你不能真正做标准的 C 二维数组。让我们看看这个:

A = (char **)calloc((3), sizeof(char *));
for(i=0; i<3; ++i)
{
   A[i] = (char *)calloc(2, sizeof(char));
}

这肯定会分配一个 3x2 字符数组,但您不知道结果数据在内存中的布局方式。特别是,完全不能保证 A[1][0] 紧跟在 A[0][1] 之后。这使得创建跨越数据结构的 MPI 数据类型变得非常困难!您需要分配 3x2 连续字节,然后使数组指向它:

char **charalloc2d(int n, int m) {
    char *data = (char *)calloc(n*m,sizeof(char));
    char **array = (char **)calloc(n, sizeof(char *));
    for (int i=0; i<n; i++)
        array[i] = &(data[i*m]);

    return array;
}

void charfree2d(char **array) {
    free(array[0]);
    free(array);
    return;
}

/* ... */
nrows = 3;
ncols = 2;
A = charalloc2d(nrows,ncols);

现在我们了解了数组的布局,并且可以依赖它来构建数据类型。

您在数据类型方面走在正确的轨道上 --

MPI_Datatype b_col_type;
MPI_Type_vector(3, 1, 1, MPI_CHAR, &b_col_type);
MPI_Type_commit(&b_col_type);

MPI_Type_vector 的签名是(count, blocklen, stride, old_type, *newtype)。
我们想要 nrows 个字符,以 1 个为一组;但它们相隔 ncols;这就是进步。

请注意,这实际上是A 数组的列类型,而不是B;类型将取决于数组中的列数。所以每个进程都使用不同的发送类型,这很好。

MPI_Datatype a_col_type;
MPI_Type_vector(nrows, 1, ncols, MPI_CHAR, &a_col_type);
MPI_Type_commit(&a_col_type);

最后一步是MPI_Gatherv,在这里你得有点可爱。诀窍是,我们想一次发送(和接收)多个这样的东西——也就是说,几个连续的东西。但是我们需要下一列不是 nrows*ncols 个字符,而是一个字符。幸运的是,我们可以通过将数据结构的上限设置为距下限仅一个字符来做到这一点,这样下一个元素就可以从正确的位置开始。 the standard 允许这样做,事实上他们在第 4.1.4 节中的示例之一取决于它。

为此,我们创建了一个调整大小的类型,它在开始后仅一个字节结束:

MPI_Type_create_resized(a_col_type, 0, 1*sizeof(char), &new_a_col_type);
MPI_Type_commit(&new_a_col_type); 

B 也类似;现在我们可以像预期的那样发送和接收多个。所以以下对我有用:

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

char **charalloc2d(int n, int m) {
    char *data = (char *)calloc(n*m,sizeof(char));
    char **array = (char **)calloc(n, sizeof(char *));
    for (int i=0; i<n; i++)
        array[i] = &(data[i*m]);

    return array;
}

void charfree2d(char **array) {
    free(array[0]);
    free(array);
    return;
}


int main(int argc, char *argv[])
{
    int numprocs, my_rank;
    int nrows, ncols, totncols;
    long int i, j;
    char **A;
    char **B;
    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);

    if(my_rank == 0)
    {
        nrows=3;
        ncols=2;
        totncols = 3;

        A = charalloc2d(nrows, ncols);
        B = charalloc2d(nrows, totncols);

        A[0][0] = 'a';
        A[1][0] = 'b';
        A[2][0] = 'c';
        A[0][1] = 'd';
        A[1][1] = 'e';
        A[2][1] = 'f';
    }
    else
    {
        nrows = 3;
        ncols = 1;
        A = charalloc2d(nrows, ncols);
        B = charalloc2d(1,1); /* just so gatherv survives */
        A[0][0] = 'g';
        A[1][0] = 'h';
        A[2][0] = 'i';

    }
    MPI_Datatype a_col_type, new_a_col_type;
    MPI_Type_vector(nrows, 1, ncols, MPI_CHAR, &a_col_type);
    MPI_Type_commit(&a_col_type);

    /* make the type have extent 1 character -- now the next
     * column starts in the next character of the array 
     */
    MPI_Type_create_resized(a_col_type, 0, 1*sizeof(char), &new_a_col_type);
    MPI_Type_commit(&new_a_col_type);

    MPI_Datatype b_col_type, new_b_col_type;
    if (my_rank == 0) {
        MPI_Type_vector(nrows, 1, totncols, MPI_CHAR, &b_col_type);
        MPI_Type_commit(&b_col_type);

        /* similarly "resize" b columns */
        MPI_Type_create_resized(b_col_type, 0, 1*sizeof(char), &new_b_col_type);
        MPI_Type_commit(&new_b_col_type);
    }

    int displs[2] = {0, 2};
    int recvcounts[2] = {2, 1};
    MPI_Gatherv(A[0], recvcounts[my_rank], new_a_col_type,
                B[0], recvcounts, displs, new_b_col_type,
                0, MPI_COMM_WORLD);
    if(my_rank == 0)
    {
        for(i=0; i<3; ++i)
        {
            for(j=0; j<3; ++j)
                printf("%c ", B[i][j]);
            printf("\n");
        }
    }
    MPI_Finalize();
    return 0;
}

关于c - 如何从处理器中获取 MPI_Gatherv 列,其中每个进程可能发送不同数量的列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5371733/

相关文章:

c - 如何将 fprintf 输出重定向到 C 套接字?

C - 如何释放动态分配的内存?

c - 如何在c中将枚举名称转换为字符串

c - 为什么 MPI_Gather 会出现缓冲区错误?

mpi - MPI_Allgather 和 MPI_Alltoall 函数之间的区别?

sockets - 在不同的 MPI 程序之间进行通信

c - 逻辑错误-插入排序

c - 我应该链接哪个库才能获得 WinRT "EXTERN_C const IID IID___x_ABI_*"的正确定义

c++ - 使用 MPI 环形拓扑通过比较每个处理器的局部最大值来找到函数的全局最大值

rmpi 无法以非 root 用户身份加载共享库