4.5 收集(Gather) BACKWARDFORWARD


MPI_GATHER(sendbuf, sendcount, sendtype, recvbuf, recvcount, recvtype, root , comm)
 IN sendbuf    发送消息缓冲区的起始地址(可变)
 IN sendcount  发送消息缓冲区中的数据个数(整型)
 IN sendtype   发送消息缓冲区中的数据类型(句柄) 
 OUT recvbuf   接收消息缓冲区的起始地址(可变,仅对于根进程) 
 IN recvcount  待接收的元素个数(整型,仅对于根进程)
 IN recvtype   接收元素的数据类型(句柄,仅对于根进程)
 IN root      接收进程的序列号(整型)
 IN comm      通信子(句柄)
int MPI_Gather(void* sendbuf, int sendcount, MPI_Datatype sendtype, 
               void* recvbuf, int recvcount, MPI_Datatype recvtype, 
               int root, MPI_Comm comm)
MPI_GATHER(SENDBUF, SENDCOUNT, SENDTYPE, RECVBUF, RECVCOUNT, RECVTYPE, 
           ROOT, COMM, IERROR)
  <type> SENDBUF(*), RECVBUF(*)
  INTEGER SENDCOUNT, SENDTYPE, RECVCOUNT, RECVTYPE, ROOT, COMM, IERROR

每个进程(包括根进程)将其发送缓冲区中的内容发送到根进程,根进程根据发送这些数据的进程的序列号将它们依次存放到自已的消息缓冲区中.其结果就象一个组中的n个进程(包括根进程在内)都执行了一个调用:

  MPI_Send(sendbuf, sendcount, sendtype, root, ...), 

同时根进程执行了n次调用:

  MPI_Recv(recvbuf+i*recvcount*extent(recvtype), recvcount, recvtype, i,...),

此处extent(recvtype)是调用函数MPI_Type_extent()所返回的类型,另外一种描述就象是组中的n个进程发送的n条消息按照它们的序列号连接起来,根进程通过调用MPI_RECV(recvbuf, recvcount*n, recvtype,...) 来将结果消息接收过来.

对于所有非根进程,接收消息缓冲区被忽略.

一般来说,sendtype和recvtype都允许是派生类型,并且第i个进程的sendcount 和sendtype的类型必须和根进程中的recvcount和recvtype相同,这就隐含了在每个进程和根进程之间,发送的数据量必须和接收的数据量相等,但发送方和接收方之间的不同数据类型映射仍然是允许的.

此函数中的所有参数对根进程来说都是很重要的,而对于其他进程只有sendbuf、 sendcount、sendtype、root和comm是必不可少的.参数root和comm在所有进程中都必须是一致的.

对数据个数和类型的描述不应当导致根进程消息缓冲区的任何部分被重写,这样的调用是错误的.

注意在根进程中recvcount所表示的是它从每个进程中接收的消息条数, 而不是它所接收的全部消息条数.

MPI_GATHERV(sendbuf, sendcount, sendtype, recvbuf, recvcounts, displs, 
            recvtype, root, comm)
  IN     sendbuf      发送消息缓冲区的起始地址(可变)
  IN     sendcount    发送消息缓冲区中的数据个数(整型)
  IN     sendtype     发送消息缓冲区中的数据类型(句柄)
  OUT    recvbuf      接收消息缓冲区的起始地址(可变,仅对于根进程)
  IN     recvcounts   整型数组(长度为组的大小), 其值为从每个进程接收
                      的数据个数(仅对于根进程)
  IN     displs       整数数组,每个入口i表示相对于recvbuf的位移,此位
                      移处存放着从进程i中接收的输入数据(仅对于根进程)
  IN     recvtype     接收消息缓冲区中数据类型(仅对于根进程)(句柄)
  IN     root         接收进程的序列号(句柄)
  IN     comm         通信子(句柄)
int MPI_Gatherv(void* sendbuf, int sendcount, MPI_Datatype sendtype, 
            void* recvbuf, int *recvcounts, int *displs, 
              MPI_Datatype recvtype, int root, MPI_Comm comm) 
MPI_GATHERV(SENDBUF, SENDCOUNT, SENDTYPE, RECVBUF, RECVCOUNTS,DISPLS,
         RECVTYPE, ROOT, COMM, IERROR) 
  <type> SENDBUF(*), RECVBUF(*) 
  INTEGER SENDCOUNT, SENDTYPE, RECVCOUNTS(*), DISPLS(*), RECVTYPE,
        ROOT, COMM, IERROR 

MPI_GATHERV扩展了MPI_GATHER的功能, 它可以从不同的进程接收不同数量的数 据,因为此时recvcounts是一个数组,除此之外,它还提供了更大的灵活性,比如它提供了一个新的参数displs,用户可以将接收的数据存放到根进程消息缓冲区的任意处.

此函数调用的结果就象组中的每个进程(包括根进程在内)都向根进程发送一条消息:

    MPI_Send(sendbuf, sendcount, sendtype, root,...), 

同时根进程执行n次接收动作,

    MPI_Recv(recvbuf+disp[i]*extent(recvtype),recvcounts[i],
             recvtype,i, ...), 

消息按其源进程中的序列号依次存放在根进程的消息接收缓冲区中,也就是说,第j个进程发送给根进程的数据将存放到根进程消息接收缓冲区recvbuf的第j部分,第j部分从recvbuf的偏移量为displs[j]处开始.

对于所有非根进程,接收消息缓冲区被忽略.

图4.2: 根进程从组中的每个其他进程收集100个整型数据

第i个进程的sendcount和sendtype的类型必须和根进程中的recvcounts[i]和recvtype相同,这意谓着在每个进程和根进程之间,发关的数据量必须和接收的数据量相等.但发送方和接收方之间的不同数据类型映射仍然是允许的,如例4.6所示.

此函数中的所有参数对根进程来说都是很重要的,而对于其他进程只有sendbuf、 sendcount、sendtype、root和comm是必不可少的.参数root和comm在所有进程中都必须是一致的.

对数据个数和类型的描述不应当导致根进程消息缓冲区的任何部分被重写,这样的调用是错误的.

4.5.1 应用MPI_GATHER和MPI_GATHERV的例子

例4.2: 从组中的每个进程收集100个整型数送给根进程.见图4.2.

    MPI_Comm comm;
    int gsize,sendarray[100];
    int root,*rbuf;
    ......
    MPI_Comm_size(comm,&gsize);
    rbuf=(int *)malloc(gsize*100*sizeof(int));
    MPI_Gather(sendarray,100,MPI_INT,rbuf,100,MPI_INT,root,comm);

例4.3: 例4.2的变更,仅对根进程分配消息接收缓冲区.

    MPI_Comm comm;
    int gsize, sendarray[100];
    int root, myrank, *rbuf;
    ......
    MPI_Comm_rank(comm, myrank);
    if (myrank == root) {
       MPI_Comm_size(comm, &gsize);
       rbuf=(int *)malloc(gsize*100*sizeof(int));
       }
    MPI_Gather(sendarray, 100, MPI_INT, rbuf, 100, MPI_INT,
               root, comm);

例4.4: 功能同例4.3,但使用了派生数据类型.注意此时数据类型已经不是整个集合中gsize*100个整型数了,因为在收集动作中提供了每个进程和根进程之间的类型匹配.

    MPI_Comm comm;
    int gsize, sendarray[100];
    int root, *rbuf;
    MPI_Datatype rtype;
    ......
    MPI_Comm_size(comm, &gsize);
    MPI_Type_contiguous(100, MPI_INT, &rtype);
    MPI_Type_commit(&rtype);
    rbuf = (int *)malloc(gsize*100*sizeof(int));
    MPI_Gather(sendarray, 100, MPI_INT, rbuf, 1, rtype,
               root,comm);

例4.5 每个进程向根进程发送100个整型数,但在接收端设置每个集合(100个数据)的步长,用MPI_GATHERV函数和displs参数来实现,假设步长≥100.见图4.3.

    MPI_Comm comm;
    int gsize, sendarray[100];
    int root, *rbuf, stride;
    int *displs, i, *rcounts;
    ......
    MPI_Comm_size(comm, &gsize);
    rbuf = (int *)malloc(gsize*stride*sizeof(int));
    displs = (int *)malloc(gsize*sizeof(int));
    rcounts = (int *)malloc(gsize*sizeof(int));
    for (i=0; i<gsize; ++i) {
        displs[i] = i*stride;
        rcounts[i] = 100;
    }
    MPI_Gatherv(sendarray, 100, MPI_INT, rbuf, rcounts, displs, MPI_INT,
                root, comm);

注意:如果步长<100时程序将出错.

图4.3: 根进程从组中的每个其他进程收集100个整型数据,每个数据集之间按一定步长分开存放

图4.4: 根进程从100*150的数组中收集第0列,每个数据集之间按一定步长分开存放

例4.6: 在接收方同例4.5,但发送的是每个100*150数组的第0列中的100个数据(以C语言为例)见图4.4.

    MPI_Comm comm;
    int gsize, sendarray[100][150];
    int root, *rbuf, stride;
    MPI_Datatype stype;
    int *displs, i, *rcounts;
    ......
    MPI_Comm_size(comm, &gsize);
    rbuf = (int *)malloc(gsize*stride*sizeof(int));
    displs = (int *)malloc(gsize*sizeof(int));
    rcounts = (int *)malloc(gsize*sizeof(int));
    for (i=0; i<gsize; ++i)  {
         displs[i] = i*stride;
         rcounts[i] = 100;
    }
    /* 为数组中的第一列数据生成相应的数据类型 */
    MPI_Type_vector(100, 1, 150, MPI_INT, &stype);
    MPI_Type_commit(&stype);
    MPI_Gatherv(sendarray, 1, stype, rbuf, rcounts, displs, MPI_INT,
                root, comm);

例4.7:进程i将100*150的整数数组中第i列的100-i个整数发送给根进程(以C语言为例)接收端和上述两例一样也设定步长.见图4.5

    MPI_Comm comm;
    int gsize, sendarray[100][150], *sptr;
    int root, *rbuf, stride, myrank;
    MPI_Datatype stype;
    int *displs, i, *rcounts;
    ......
    MPI_Comm_size(comm, &gsize);
    MPI_Comm_rank(comm, &myrank);
    rbuf = (int *)malloc(gsize*stride*sizeof(int));
    displs = (int *)malloc(gsize*sizeof(int));
    rcounts = (int *)malloc(gsize*sizeof(int));
    for (i=0; i<gsize; ++i)  {
        displs[i] = i*stride;
        rcounts[i] = 100-i;   /* 和上例不同处 */
    }
    /* 为要发送列的数据生成相应的数据类型 */
    MPI_Type_vector(100-myrank, 1, 150, MPI_INT, &stype);
    MPI_Type_commit(&stype);
    /* sptr是"myrank"列的起始地址 */
    sptr = &sendarray[0][myrank];
    MPI_Gatherv(sptr, 1, stype, rbuf, rcounts, displs, MPI_INT, 
                root, comm);

注意:每个进程所接收的数据个数不同.

图4.5: 根进程从100*150的数组中收集第i列的100-i个整型数据,每个数据集之间按一定步长分开存放

例4.8:和例4.7一样,但发送端不同.为在发送端得到正确的步长,首先生成相应的步长,这样我们就可以读C数组中的某一列了,这同3.12.7中的例3.32相类似.

    MPI_Comm comm;
    int gsize,sendarray[100][150],*sptr;
    int root,*rbuf,stride,myrank,disp[2],blocklen[2];
    MPI_Datatype stype,type[2];
    int *displs,i,*rcounts;
    ......
    MPI_Comm_size(comm, &gsize);
    MPI_Comm_rank(comm, &myrank);
    rubf = (int *)malloc(gsize*stride*sizeof(int));
    displs = (int *)malloc(gsize*sizeof(int));
    rcounts = (int *)malloc(gsize*sizeof(int));
    for (i=0; i<gsize; ++i)  {
        displs[i] = i*stride;
        rcounts[i] = 100-i;
    }
    /* 在整行范围内为一个整数生成数据类型 */
    disp[0] = 0;         disp[1] = 150*sizeof(int);
    type[0] = MPI_INT;   type[1] = MPI_UB;
    blocklen[0] = 1;     blocklen[1] = 1;
    MPI_Type_struct(2, blocklen, disp, type, &stype);
    MPI_Type_commit(&stype);
    sptr = &sendarray[0][myrank];
    MPI_Gatherv(sptr, 100-myrank, stype, rbuf, rcounts, displs, 
                MPI_INT, root, comm);

例4.9: 在发送端同例4.7,但在接收端各数据块之间的步长是随之变化的.见图4. 6.

    MPI_Comm comm;
    int gsize,sendarray[100][150],*sptr;
    int root,*rbuf,*stride,myrank,bufsize;
    MPI_Datatype stype;
    int *displs,i,*rcounts,offset;
    ......
    MPI_Comm_size(comm, &gsize);
    MPI_Comm_rank(comm, &myrank);

    stride = (int *)malloc(gsize*sizeof(int));
    ......
    /* 对stride[i]赋初值,i从0到gsize-1 */
    /* 首先设置displs和rcounts向量 */
    displs = (int *)malloc(gsize*sizeof(int));
    rcounts = (int *)malloc(gsize*sizeof(int));
    offset = 0;
    for (i=0; i<gsize; ++i)  {
        displs[i] = offset;
        offset += stride[i];
        rcounts[i] = 100-i;
    }
    /* rbuf缓冲区的大小很容易得到 */
    bufsize = displs[gsize-1]+rcounts[gsize-1];
    rbuf = (int *)malloc(bufsize*sizeof(int));
    /* 为将要发送的列生成相应的数据类型 */
    MPI_Type_vector(100-myrank, 1, 150, MPI_INT, &stype);
    MPI_Type_commit(&stype);
    sptr = &sendarray[0][myrank];
    MPI_Gatherv(sptr, 1, stype, rbuf, rcounts, displs, MPI_INT,
                root, comm);

图4.6: 根进程从100*150的数组中收集第i列的100-i个整型数据,每个数据集之间按步长stride[i]分开存放

例4.10: 进程i从100*150数组的第i列开始发送num个整数(以C语言为例).这里比较困难的是根进程不知道变化的num的确切值,所以必须先收集各个num值,这些数据依次存放在接收端.

    MPI_Comm comm;
    int gsize,sendarray[100][150],*sptr;
    int root,*rbuf,stride,myrank,disp[2],blocklen[2];
    MPI_Datatype stype,types[2];
    int *displs,i,*rcounts,num;
    ......
    MPI_Comm_size(comm, &gsize);
    MPI_Comm_rank(comm, &myrank);

    /* 首先根进程收集各个num的值 */

    rcounts = (int *)malloc(gsize*sizeof(int));
    MPI_Gather(&num, 1, MPI_INT, rcounts, 1, MPI_INT, root, comm);

    /* 现在根进程已经得到正确的rcounts,这样我们可以设置displs[] 以便将这些
       数据依次存放在接收端 */

    displs = (int *)malloc(gsize*sizeof(int));
    displs[0] = 0;
    for (i=1; i<gsize; ++i)  {
        displs[i] = displs[i-1]+rcounts[i-1];
    }

    /* 生成接收消息缓冲区 */

    rbuf = (int *)malloc(gsize*(displs[gsize-1]+rcounts[gsize-1])
           *sizeof(int));

    /* 在整行范围内为一个整数生成数据类型 */

    disp[0] = 0;           disp[1] = 150*sizeof(int);
    type[0] = MPI_INT;     type[1] = MPI_UB;
    blcklen[0] =1;         blcklen[1] = 1;
    MPI_Type_struct(2, blocklen, disp, type, &stype);
    MPI_Type_commit(&stype);
    sptr = &sendarray[0][myrank];
    MPI_Gatherv(sptr, num, stype, rbuf, rcounts, displs, MPI_INT,
                root, comm);


Copyright: NPACT BACKWARDFORWARD