5.5 针对性例子 BACKWARD FORWARD


5.5.1当前练习#1

例#1a:

 main(int argc,char **argv)
 {
  int me,size;
  ...
  MPI_Init(&argc,&argv);
  MPI_Comm_rank(MPI_COMM_WORLD,&me);
  MPI_Comm_size(MPI_COMM_WORLD,&size);

  (void)printf("Process %d size %d",me,size);
  ...
  MPI_Finalize();
 }

例#1a是一个对自身进行合法初始化但不做任何事的程序,程序中引用了"all" 通信 子并打印了一个消息。其自身也能合法终止。该例不意味着MPI本身支持类printf通信。

例#1b(假设size是偶数)

 main(int argc,char **argv)
 {
  int me,size;
  int SOME_TAG=0;
  ...
  MPI_Init(&argc,&argv);

  MPI_Comm_rank(MPI_COMM_WORLD,&me);/*本地*/
  MPI_Comm_size(MPI_COMM_WORLD,&size);/*本地*/

  if ((me%2)==0)
  {
   /*如果不是最高编号的进程则发送*/
   if((me+1)<size)
    MPI_Send(...,me+1,SOME_TAG,MPI_COMM_WORLD);
   }
   else
    MPI_Recv(...,me-1,SOME_TAG,MPI_COMM_WORLD);

   ...
   MPI_Finalize();
  }

例#1b有计划地阐述了"all"通信子中奇偶进程间的消息交换.

5.5.2 当前练习#2

 main(int argc,char **argv)
 {
  int me,count;
  void *data;
  ...

  MPI_Init(&argc,&argv);
  MPI_Comm_rank(MPI_COMM_WORLD,&me);

  if(me==0)
  {
   /*得到输入,建立缓冲区"data"*/
   ...
   }

   MPI_Bcast(data,count,MPI_BYTE,0,MPI_COMM_WORLD);

   ...

   MPI_Finalize();
  }

本例阐述了集合通信的使用.

5.5.3(近似)当前练习#3

 main(int argc,char **argv)
 {
  int me,count,count2;
  void *send_buf,*recv_buf,*send_buf2,*recv_buf2;
  MPI_Group MPI_GROUP_WORLD,grprem;
  MPI_Comm commslave;
  static int rank[]={0};
  ...
  MPI_Init(&argc,&argv);
  MPI_Comm_group(MPI_COMM_WORLD,&MPI_GROUP_WORLD);
  MPI_Comm_rank(MPI_COMM_WORLD,&me);/*本地/

  MPI_Group_excl(MPI_GROUP_WORLD,1,ranks,&grprem);/*本地*/   MPI_Comm_create(MPI_COMM_WORLD,grprem,&commslave);

  if((me!=0)
  {
   /*在从机上计算*/
   ...
   MPI_Reduce(send_buf,recv_buff,count,MPI_INT,MPI_SUM,1,commslave);
   ...
   }
   /*0则立即作此归约操作,其它则稍后再做*/
   MPI_Reduce(send_buf2,recv_buff2,count2, MPI_INT,MPI_SUM,0,MPI_COMM_WORLD);

   MPI_Comm_free(&commslave);
   MPI_Group_free(&MPI_GROUP_WORLD);
   MPI_Group_free(&grprem);
   MPI_Finalize();
   }

本例阐述了如何创建一个组,该组包含了除"all"组中第0个进程之外的所有进程,同时阐述了如何为该新组(commslave)形成一个通信子。此新通信子在一个集合调用中被使用,而且所有进程在上下文MPI_COMM_WORLD中都执行一个集合调用。本例还阐述了两个通信子(本质上具有不同的上下文)如何保护通信。亦即,MPI_COMM_WORLD中的通信与commslave中的通信隔离开,反之亦然。

概要地讲,“组安全性”是通过通信子获得的,这是因为在任何进程上通信子内的不同上下文被强制为唯一的。

5.5.4 例#4

下面的例子目的是阐述点对点与集合通信之间的安全性。MPI保证了单独一个通信子能够安全地进行点对点与集合通信。

 #define TAG_ARBITRARY 12345
 #define SOME_COUNT 50

 main(int argc,char **argv)
 {
  int me;
  MPI_Request request[2];
  MPI_Status status[2];
  MPI_Group MPI_GROUP_WORLD,subgroup;
  int ranks[]={2,4,6,8};
  MPI_Comm the_comm;
  ...
  MPI_Init(&argc,&argv);
  MPI_Comm_group(MPI_COMM_WORLD,&MPI_GROUP_WORLD);

  MPI_Group_incl(MPI_GROUP_WORLD,4,ranks,&subgroup);/*本地*/
  MPI_Group_rank(subgroup,&me);/*本地*/

  MPI_Comm_create(MPI_COMM_WORLD,subgroup,&the_comm);

  if (me!=UNDEFINED)
  {
   MPI_Irecv(buff1,count,MPI_DOUBLE,MPI_ANY_SOURCE,TAG_ARBITRARY,the_comm, request);       MPI_Isend(buff2,count,MPI_DOUBLE,(me+1)%4,TAG_ARBITRARY, the_comm,request+1);
   }

   for(i=0;i<SOME_COUNT;i++)
   MPI_Reduce(...,the_comm);
   MPI_Waitall(2,request,status);

   MPI_Comm_free(&the_comm);
   MPI_Group_free(&MPI_GROUP_WORLD);
   MPI_Group_free(&subgroup);
   MPI_Finalize();
  }

5.5.5 库例#1

主程序:

 main(int argc,char **argv)
 {
  int done=0;
  user_lib_t *libh_a,*libh_b;
  void *dataset1,*dataset2;
  ...
  MPI_Init(&argc,&argv);
  ...
  init_user_lib(MPI_COMM_WORLD,&libh_a);
  init_user_lib(MPI_COMM_WORLD,&libh_b);
  ...
  user_start_op(libh_a,dataset1);
  user_start_op(libh_b,dataset2);
  ...
  while(!done)
  {
   /*起作用*/
   ...
   MPI_Reduce(...,MPI_COMM_WORLD);
   ...
   /*如果done,则见下面*/
   ...
   }
   user_end_op(libh_a);
   user_end_op(libh_b);

   uninit_user_lib(libh_a);
   uninit_user_lib(libh_b);
   MPI_Finalize();
  }

用户库初始化代码:
 void init_user_lib(MPI_Comm *comm,user_lib_t **handle)
 {
  user_lib_t *save;

  user_lib_initsave(&save);/*本地*/
  MPI_Comm_dup(comm,&(save->comm));

  /*其它初始化*/
  ...

  *handle=save;
 }

用户启动代码:
 void user_start_op(user_lib_t *handle,void *data)
 {
  MPI_Irecv(...,handle->comm,&(handle->irecv_handle));
  MPI_Isend(...,handle->comm,&(handle->irecv_handle));
 }

用户通信清除代码:

 void user_end_op(usr_lib_t *handle)
 {
  MPI_Status *status;
  MPI_Wait(handle->isend_handle,status);
  MPI_Wait(handle->irecv_handle,status);
  }

用户对象清除代码:

 void uninit_user_lib(user_lib_t *handle)
 {
  MPI_Comm_free(&(handle->comm));
  free(handle);
  }

5.5.6 库例#2

主程序:
 main(int argc **argv)
 {
  int ma, mb;
  MPI_Group MPI_GROUP_WORLD,group_a, group_b;
  MPI_Comm comm_a,commb;

  static int list_a[] = {0,1};
#if defined(EXAMPLE_2B) |defined(EXAMPLE_2C)
  static int list_b[] = {0, 2, 3};

#else/* EXAMPLE_2A */
  static int list_b[] = {0, 2};
#endif
  int size_list_a = sizeof(list_a)/sizeof(int);
  int size_list_b = sizeof(list_b)/sizeof(int);

  ...
  MPI_Init(&argc, &argv);
  MPI_Comm_group(MPI_COMM_WORLD, &MPI_GROUP_WORLD);

  MPI_Group_incl(MPI_GROUP_WORLD,size_list_a,&group_a);
  MPI_group_incl(MPI_GROUP_WORLD,size-list_b,&group_b);

  MPI_Comm_create(MPI_COMM_WORLD,group_a, &comm_a);
  MPI_Comm_create(MPI_COMM_WORLD,group_b, &comm_b);

  MPI_Comm_rank(comm_a, &ma);
  MPI_Comm_rant(comm_b, &mb);

  if(ma != MPI_UNDEFINED)
   lib_call(comm_a);

  if(mb != MPI_UNDEFINED)
  {
   lib_call(comm_b);
   lib_call(comm_b);
   }

  MPI_Comm_free(&comm_a);
  MPI_Comm_free(&comm_b);
  MPI_Group_free(&group_a);
  MPI_Group_free(&group_b);
  MPI_Group_free(&MPI_GROUP_WORLD);
  MPI_Finalize();
  }

库:

 void lib_call(MPI_Comm comm)
 {
  int me, done = 0;
  MPI_Comm_rank(comm,&me);
  if(me == 0)
   while(!done)
   {
    MPI_Recv(..., MPI_ANY_SOURCE, MPI_ANY_TAG, comm);
    ...
    }
   else
   {
   /*起作用*/
   MPI_Send(...,0, ARBITRARY_TAG, comm);
   ...
   }
#ifdef EXAMPLE_2C
   /* 包含(resp, exclude)用于safety (resp, no safety): */
   MPI_Barrier(comm);
#endif
  }

根据程序是否在lib_b中包含序列号等级3,以及是否在lib_call中进行同步,可将上面的例子实际 上当做三个例子.本例所阐述的是,不管上下文如何,使用同样上下文对lib_call进行的后续调用相互间无须是安全的(口语上,成为“后标志”). 如果加上MPI_Barrier, 就要考虑安全性.已经证明,即使使用上下文, 库也要仔细书写.当不包括序列号3时,就无须通过同步来从后标志中获取安全性.

假如MPI提供基本的保证,则类似于"reduce"和"allreduce"这样的算法就会有足够强大的选 择特性来保证其固有的正确性(无后标志).对使用相同的根和不同的根的典型树广播算法的多重调用也是这样. 这里我们依赖于MPI的两个保证: 同一上下文内的进程间的成对顺序,及资源的可选择性--删除任一个特征就等于去掉了不要求后标志这一保证.

试图作非确定性广播或包括通配符操作的其它调用一般不具备诸如 "reduce","allreduce",及"broadcast"这种确定性实现的好的特性.这种算法必须利用单调上升的标志(在一个通信子范围内)来保持正确性.

前面的所有调用都假定是用点对点操作来实现集合调用.MPI实现既可用也可不用点对点操作来实现集合调用.这些算法用于阐述正确性和安全性问题,而不管MPI如何实现其集合调用. 见5.8节.


Copyright: NPACT BACKWARD FORWARD