MPI可以实现一对多的集合通信,最常用的是广播:某个进程将数据广播到所有其他进程,最终的结果就是每个进程都有一份广播的数据。MPICH中的广播函数是MPI_Bcast(void* buffer,intcount,MPI_Datatype datatype,int root, MPI_Comm comm)。该函数在使用过程中非常容易出错,在此我们通过具体实例来说明其使用方法。
对广播最直观的观点是某个特定进程将数据一一广播到所有的进程,所以很多人在使用MPI_Bcast函数的时候,总是将其放入一个if语句中,由要发送数据的进程单独执行。但这是错误的!由某个特定进程发送数据到所有其他进行是低效的,复杂度为O(n),所以MPI_Bcast函数在实现的时候往往采用更加高效的手段进行广播。一个最常用也是非常高效的手段是规约树广播:收到广播数据的所有进程都参与到数据广播的过程中。首先只有一个进程有数据,然后它广播到某个进程,此时有两个进程有数据;然后这两个进程都参与到下一次的广播中,这时就会有4个进程有数据,……,以此类推,每次都会有2的次方个进程有数据。通过这种规约树的广播方法,广播的复杂度降为O(log n)。当然针对不同的数据大小,MPI_Bcast函数采取了不同的广播策略。
由上面的解释,我们可以明白在广播的过程中,所有的进程都参与广播,并不只有最初的进程进行发送(它只是一个起点)。所以我们在使用MPI_Bcast函数的时候一定要将其放到所有进程都能运行的位置。
有时我们要广播的数据长度并不知道,这时就需要采用动态分配的方式分配数据,然后再将其广播。这里很容易出问题,问题的根本就是MPI程序是分布式程序,即使我们在写程序时所有的进程都公用相同的变量,但是在不同的进程下它们的含义不同。这就会导致有些进程的指针变量指向一块已经分配内存的空间,但是有些进程的指令变量还是空指针。这时如果不注意,就会导致动态数组广播的失败。
在下面的实例中,master进程从用户读取一个长度,然后利用malloc分配空间并赋值,然后将该数组广播到其他slave进程。方法是首先广播数组的长度,然后让slave进程分配相应的内存,最后再广播该数组。
#include "mpi.h" #include <stdio.h> #include <stdlib.h> #define MASTER 0 int main(int argc,char *argv[]) { int myid, numprocs; float *sequence; int length; MPI_Init(&argc,&argv); MPI_Comm_size(MPI_COMM_WORLD,&numprocs); MPI_Comm_rank(MPI_COMM_WORLD,&myid); if(myid==MASTER) { scanf(“%d”,&length); sequence=(float*)malloc(sizeof(float)*length); for(int i=0;i<length;i++) { sequence[i]=i; } } /* step 1 * broadcast the length first */ MPI_Bcast(&length,1,MPI_INT,MASTER,MPI_COMM_WORLD); /* step 2 * allocate space on the slave processes */ if(myid!=MASTER) { sequence =(float*)malloc(sizeof(float)* length); } /* step 3 * broadcast the sequence to all the processes */ MPI_Bcast(sequence, length,MPI_FLOAT,MASTER,MPI_COMM_WORLD); printf("process %d get the sequnce, length %d\n",myid, length); MPI_Finalize(); return 0; }
通过上面三步,我们就完成了对动态数组的广播。上面的实例可以很容易扩展到结构体和其他需要动态分配内存的广播程序中。核心就是三步:1)广播长度;2)分配空间;3)广播动态数组。
上面是针对MPI广播中常见问题的解析,后续会对非基本数据类型的广播做介绍。