MPI_Type_create_subarray和MPI_Gather

时间:2011-04-07 18:15:33

标签: c mpi parallel-processing

我必须解决一个小的mpi问题。我有4个从属进程,每个进程都希望向master 0发送一个2d子阵列(CHUNK_ROWS X CHUNK_COLUMNS).Master 0收集ddd [ROWS] [COLUMNS]中的所有块并打印出来。我想使用MPI_Gather()

#include <mpi.h>
#include <iostream>
using namespace std;

#define ROWS 10
#define COLUMNS 10
#define CHUNK_ROWS 5
#define CHUNK_COLUMNS 5
#define TAG 0

int** alloca_matrice(int righe, int colonne)
{
int** matrice=NULL;
int i;

matrice = (int **)malloc(righe * sizeof(int*));

if(matrice != NULL){
  matrice[0] = (int *)malloc(righe*colonne*sizeof(int));
  if(matrice[0]!=NULL)
    for(i=1; i<righe; i++)
        matrice[i] = matrice[0]+i*colonne;
  else{
    free(matrice);
    matrice = NULL;
  }
}
else{
  matrice = NULL;
}
return matrice;

}

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

int my_id, numprocs,length,i,j;
int ndims, sizes[2],subsizes[2],starts[2];
int** DEBUG_CH=NULL;
int** ddd=NULL;
char name[BUFSIZ];
MPI_Datatype subarray=NULL;
//MPI_Status status;
MPI_Init(&argc, &argv) ;    
MPI_Comm_rank(MPI_COMM_WORLD, &my_id) ;
MPI_Comm_size(MPI_COMM_WORLD, &numprocs) ;  // Ottiene quanti processi sono attivi
MPI_Get_processor_name(name, &length);    

if(my_id!=0){
  //creo una sottomatrice ripulita dalle ghost cells
  ndims=2;
  sizes[0] = CHUNK_ROWS+2;
  sizes[1] = CHUNK_COLUMNS+2;
  subsizes[0] = CHUNK_ROWS;
  subsizes[1] = CHUNK_COLUMNS;
  starts[0] = 1;
  starts[1] = 1;
  MPI_Type_create_subarray(ndims,sizes,subsizes,starts,MPI_ORDER_C,MPI_INT,&subarray);
  MPI_Type_commit(&subarray);

  DEBUG_CH = alloca_matrice(CHUNK_ROWS+2,CHUNK_COLUMNS+2);
  for(i=0; i<CHUNK_ROWS+2; i++){
    for(j=0; j<CHUNK_COLUMNS+2; j++){
        if(i==0 || i==CHUNK_ROWS+1 || j==0 || j==CHUNK_COLUMNS+1)
            DEBUG_CH[i][j] = 5;
        else
            DEBUG_CH[i][j] = 1;
    }
  }
//MPI_Send(DEBUG_CH[0],1,subarray,0,TAG,MPI_COMM_WORLD);
}
if(my_id==0){
 ddd = alloca_matrice(ROWS,COLUMNS);
}

MPI_Gather(DEBUG_CH[0],1,subarray,ddd[0],CHUNK_ROWS*CHUNK_COLUMNS,MPI_INT,0,MPI_COMM_WORLD);
if(!my_id){
  for(i=0; i<ROWS; i++){
    for(j=0; j<COLUMNS; j++){
        printf("%d ",ddd[i][j]);
    }
    printf("\n");
  }
}

if(my_id)
 MPI_Type_free(&subarray);

MPI_Finalize();                             // Chiusura di MPI.
return 0;
}

谢谢大家。

1 个答案:

答案 0 :(得分:20)

所以这有点微妙,需要了解Gather集体如何放置复杂的类型。

如果你看大多数examples of MPI_Gather,它们都是一维数组,并且很容易解释应该发生什么;你从每个进程得到(比方说)10个整数,并且Gather足够聪明,可以从开始时的0级中获得10个整数,从数组中的10-19个等级1中获得10个,依此类推。

但是,像这样的更复杂的布局有点复杂。首先,从发送者的角度来看,数据布局与来自接收者的数据布局不同。在发件人的角度来看,你从数组元素[1][2]开始,转到[1][5](在大小为7x7的数组中),然后跳转到数组元素[2][3] - [2][5]等 有CHUNK_ROWS数据块,每个数据块间隔2个整数。

现在考虑接收者如何接收它们。假设它正在接收0级数据。它将接收到数组元素[0][0]-[0][4] - 到目前为止一直很好;但随后它会在大小为10x10的数组中将下一个数据块接收到[1][0]-[1][4]。这是超过5个元素的跳跃。内存中的布局是不同的。因此,接收器必须接收到发送者正在发送的不同Subarray类型,因为内存布局不同。

所以,虽然你可能会发送一些看起来像这样的东西:

  sizes[0] = CHUNK_ROWS+2;
  sizes[1] = CHUNK_COLUMNS+2;
  subsizes[0] = CHUNK_ROWS;
  subsizes[1] = CHUNK_COLUMNS;
  starts[0] = 1;
  starts[1] = 1;
  MPI_Type_create_subarray(ndims,sizes,subsizes,starts,MPI_ORDER_C,MPI_INT,&sendsubarray);
  MPI_Type_commit(&sendsubarray);

你会收到这样的东西:

  sizes[0]    = ROWS; sizes[1] = COLUMNS;
  subsizes[0] = CHUNK_ROWS; subsizes[1] = CHUNK_COLUMNS;
  starts[0]   = 0; starts[1] = 0;
  MPI_Type_create_subarray(ndims,sizes,subsizes,starts,MPI_ORDER_C,MPI_INT,&recvsubarray);
  MPI_Type_commit(&recvsubarray);

至关重要的是,请注意sizes数组中的差异。

现在我们离得更近了。请注意您的MPI_Gather行更改为以下内容:

MPI_Gather(DEBUG_CH[0],1,sendsubarray,recvptr,1,recvsubarray,0,MPI_COMM_WORLD);

有一些事情对以前的版本MPI_Gather(DEBUG_CH[0],1,subarray,ddd[0],CHUNK_ROWS*CHUNK_COLUMNS,MPI_INT,0,MPI_COMM_WORLD);无效 - 首先,请注意您引用的是ddd[0],但对于排名为0的每个等级,{{1这将失败。因此,创建一个名为ddd=NULL的新变量,在排名为零的情况下,将其设置为recvptr。 (其他流程认为它在哪里并不重要,因为它们没有收到。)另外,我认为您不希望收到ddd[0] CHUNK_ROWS*CHUNK_COLUMS,因为这会将它们放在一起在记忆中是连续的,我的理解是你希望它们与奴隶任务一样,但在更大的阵列中。

好的,所以现在我们到了某个地方,但上面的仍然无法正常工作,原因很有趣。对于1d数组示例,很容易找出第n个数据排名的位置。它的计算方法是找到正在接收的数据的范围,然后在该数据之后启动下一个元素。但这不会起作用。 “就在”排名零的数据的结尾不是第一排的数据应该从哪里开始(MPI_INTs)而是[0][5] - 在排名0s子阵列中的最后一个元素之后的元素。在这里,您从不同级别回收的数据重叠!因此,我们将不得不调整数据类型的范围,并手动指定每个排名的数据开始的位置。第二个是容易的部分;当您需要手动指定每个处理器的数据量或其位置时,可以使用MPI_Gatherv函数。第一个是棘手的部分。

MPI让你指定给定数据类型的下限和上限 - 在给定一块内存的情况下,此类型的第一位数据将会去,以及它“结束”的位置,这里仅表示下一个可以开始。 (数据可以超出类型的上限,我认为这会使这些名称产生误导,但这就是事情的方式。)你可以指定它为你喜欢的任何东西,方便你;因为我们将处理[4][5]数组中的元素,所以让我们的类型为MPI_INT的范围。

int

(注意我们只需对收到的类型执行此操作;从发送类型开始,因为我们只发送其中一个,所以无关紧要。)

现在,我们将使用gatherv指定每个元素的起始位置 - 以这个新的resized类型的“size”为单位,它只是1个整数。因此,如果我们想要在 MPI_Type_create_resized(recvsubarray, 0, 1*sizeof(int), &resizedrevsubarray); MPI_Type_commit(&resizedrecvsubarray); 处进入大数组,那么从大数组开始的位移是5;如果我们希望它在位置[0][5]进入,那么位移是55.

最后,请注意聚集和分散集体都假设即使是“主人”也参与其中。如果即使主人拥有他们自己的全局数组,也是最容易实现的。

因此,以下内容对我有用:

[5][5]