【问题标题】:MPI communicate large two dimensional arraysMPI 通信大型二维数组
【发布时间】:2014-04-28 15:16:07
【问题描述】:

与我的previous question 涉及将二维数组复制到一维数组有关,我想知道是否有更好的方法来完成整个业务,我试图实现。因此,我想通过 MPI 将几个大型二维数组(它们作为数组的数组动态分配,但在编译时具有已知的大小)从主机传送到客户端,反之亦然。数据应该按列分散到客户端/从客户端收集。

我目前正在做的是将 2 维数组(实际上是 3 维数组的子数组)映射到静态分配的 1 维数组中,发送这些 1 维数组。通过 MPI 生成数组,然后在接收器处再次重建二维数组。但是,我觉得应该有比这更有效的东西(很多)......

非常感谢!

【问题讨论】:

  • 为什么要使用数组数组数组?如果它们仅用于“语法糖”,请现在摆脱它们。您正在优化、并行化——这是第一步:没有副本、更好地使用缓存、使用线性代数库的可能性......
  • @Sigismondo 我不能同意你的观点,说你使用数组作为已知维度的表,你宁愿查找 foo[3][4] 还是 *(foo + 3 * width + 4)?不使用二维数组有什么好处?其次,如果询问者使用 MPI,他们需要使用副本,而 MPI 通常在进程之间没有共享内存,它们可能在不同的机器上,无法在它们之间加倍映射内存。
  • @Vality。 C 的问题是 foo[3][4] 在使用数组数组时表示 *(*(foo+3)+4) ,在使用 2D 静态数组时表示 *(foo +3*width +4) 。第二个是更好,是所有优化的线性代数库(ATLAS,libgoto)所做的,是支持多维数组的语言所做的(Fortran)。 C 的唯一问题是它不支持多维动态数组。但是数组的数组和二维数组是不一样的。
  • @Sigismondo 感谢您的回复,看看它是如何工作的真的很有趣。我很想看看在静态编译时间维度的情况下是否有任何常见的编译器能够显着优化这一点。但是,我确实看到您的观点,即明确将迫使它使用最有效的方式,谢谢。
  • @Vality。其实我错了... C89 不支持动态多维数组。但是 C99 DOES 支持多维动态数组 - 我也该停止使用 a[i*n+j] 了 - 在下面添加一个答案。谢谢你让我找这个:)

标签: c arrays multidimensional-array mpi


【解决方案1】:

如果数组维度在编译时已知,例如使用静态维度,那么它就像分配一个线性数组然后使用一个指针转换为与多维数组形状对应的类型一样简单。例如,对于一个 3x5 整数数组,即

int array[3][5];

你可以改用:

int (*array)[5] = malloc(3*5*sizeof(int));

int (*)[5] 类型是一个指向 5 个元素的整数数组的指针。 array[0] 是第一个这样的 5 元素数组,array[0][3] 是第一个数组的第四个元素。

您也可以使用 3 维数组来做到这一点。

int array[3][4][5];

变成:

int (*array)[4][5] = malloc(3*4*5*sizeof(int));

对于只有在运行时才知道大小的数组,通常使用不同的技术。分配一大块内存,然后在其中构建一棵指针树。所以不是

int **array = malloc(3*sizeof(int *));
for (i = 0; i < 5; i++)
   array[i] = malloc(5*sizeof(int));

这通常会给你非连续的数据存储,你会这样做:

int *array_storage = malloc(3*5*sizeof(int));
int **array = malloc(3*sizeof(int *));
for (i = 0; i < 3; i++)
   array[i] = &array_storage[i*5];

现在存储是连续的。该技术的一个缺陷是必须使用两次对free() 的调用才能释放数组:

free(array[0]); // Since array[0] == array_storage this frees the big chunk
free(array);    // Frees the array of pointers

不用分配两个单独的动态存储,指针数组的位置可以与数据存储一起分配,然后单个free(array); 将处理所有内容。

【讨论】:

    【解决方案2】:

    C99 支持动态多维数组:使用它们可以完全避免复制数据。例如下面的代码使用gcc -std=c99编译:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc, char **argv) {
      int m = atoi(argv[1]);
      int n = atoi(argv[2]);
      int p = atoi(argv[3]);
    
      // if defined in stack... actually I'm not sure it's defined in stack
      // in this way - shouldn't its size be known at compile time?
      // float a[m][n][p];
      // in heap:
      float (*a)[n][p] = malloc(m*n*p*sizeof(float));
      for (int i=0; i<m; ++i) {
        for (int j=0; j<n; ++j) {
          for (int k=0; k<p; ++k) {
            a[i][j][k] = 100.*i + 10.*j + k;
          }
        }
      }
      for (int i=0; i<m; ++i) {
        for (int j=0; j<n; ++j) {
          for (int k=0; k<p; ++k) {
            if (k>0) printf(",");
            printf("%7.2f", a[i][j][k]);
          }
          printf("\n");
        }
        printf("\n");
      }
      free(a);
    }
    

    (例如使用./a.out 2 3 4 执行 - 没有错误检查...请耐心等待)。

    如果使用 C89,在寻找优化代码时,我认为您应该牺牲语法以获得最佳内存布局,并且我会编写与以下相同的代码:

    #include <stdio.h>
    #include <stdlib.h>
    
    #define ARR(A,i,j,k) ((A).a[(i)*A.p*A.n + (j)*A.p + (k)]) 
    
    struct Arr3d {
      float *a;
      int m;
      int n;
      int p;
    };
    
    int main(int argc, char **argv) {
      struct Arr3d a;
      int m,n,p;
      int i,j,k;
    
      m = a.m = atoi(argv[1]);
      n = a.n = atoi(argv[2]);
      p = a.p = atoi(argv[3]);
      a.a = malloc(m*n*p*sizeof(float));
      for (i=0; i<m; ++i) {
        for (j=0; j<n; ++j) {
          for (k=0; k<p; ++k) {
            ARR(a,i,j,k) = 100.*i + 10.*j + k;
          }
        }
      }
      for (i=0; i<m; ++i) {
        for (j=0; j<n; ++j) {
          for (k=0; k<p; ++k) {
            if (k>0) printf(",");
            printf("%7.2f", ARR(a,i,j,k));
          }
          printf("\n");
        }
        printf("\n");
      }
      free(a.a);
    }
    

    在这两种方式中,数组位置在内存中都是连续的,并且可以通过单个 MPI 通信发送:

    MPI_Send(&a[0][0][0], m*n*p, MPI_FLOAT, ...       (c99)
    MPI_Send(&ARR(a,0,0,0), m*n*p, MPI_FLOAT, ...     (c89)
    

    或者按照你的要求,发送第 i 个子数组:

    MPI_Send(&a[i][0][0], n*p, MPI_FLOAT, ...         (c99)
    MPI_Send(&ARR(a,i,0,0), n*p, MPI_FLOAT, ...       (c89)
    

    【讨论】:

      猜你喜欢
      • 2021-12-15
      • 2011-02-08
      • 2013-09-27
      • 1970-01-01
      • 2011-08-19
      相关资源
      最近更新 更多