5.7缓冲 BACKWARD FORWARD


MPI提供了一个缓冲工具,允许一个应用将任意片信息,即所谓的属性,附到通信子上.更详细的,缓冲工具允许一个可扩展库来做下列事:

构造诸如集合通信和应用拓扑这样的MPI函数时,往往需要某些形式的缓冲能力.作为MPI标准的一部分来定义一个与这些能力的接口是很有意义的,因为它允许类似于公共通信和应用拓扑这样的函数以可扩展代码的形式实现,而且也因为它通过允许用户书写的函数使用MPI调用序列使得MPI具有了更多的可扩充性.

对用户的建议:通信子MPI_COMM_SELF对于通过此属性缓冲机制传递进程本地属性是一个合适的选择.(对用户的建议结束)

5.7.1 功能

属性被附到通信子上.属性对于进程是本地的,它只专属于它所依附的通信子.MPI不能将属性从一个通信子传给另一个通信子,除非使用MPI_COMM_DUP对通信子进行复制(即使这样,应用也必须通过回调函数向被复制的属性给出专门的许可).

对实现者的建议:属性是一个标量值,在大小上等于或大于一个C语言的指针.属性总能拥有一 个MPI句柄.(对实现者的建议结束)

这里所定义的缓冲接口描述了被MPI模糊地存储在通信子内的属性.访问子函数包括下列内容:

对实现者的建议:当响应显式的应用要求时,缓冲和回调函数仅能被同步调用.这就避免了由于用户和系统空间之间的重复交叉所导致的问题.(此同步调用规则是MPI的一般特性)

关键字值的选择受MPI的控制.这就允许MPI优化其属性集的实现.它也避免了同一通信子上的不相关模块缓冲信息间的冲突.

一个仅包含回调工具的接口将允许通过可扩展代码的形式来实现整个缓冲工具.但是,使用最小的回调接口,为满足处理任意通信子的需要,就要有某些形式的表搜索.对比之下,这里定义的更完全的接口通过使用通信子中的指针允许对属性的快速访问(来找到属性表)而且能很灵活地选择关键字的值(以回收单个属性).同最小接口中的固有效率相对比,这里定义的更完全的接口被认为更高级.(对实现者的建议结束)

MPI提供了与缓冲相关的下列服务.它们都是本地进程.

MPI_KEYVAL_CREATE(copy_fn,delte_fn,keyval,extra_state)

int MPI_Keyval_create(MPI_Copyfunction *copy_fn,MPI_Delete_function *delete_fn ,int *keyval,void* extra_state)

MPI_KEYVAL_CREATE(COPY_FN,DELETE_FN,KEYVAL,EXTRA_STATE,IERROR)

产生一个新的属性关键字.关键字在进程内是本地唯一的,而且对用户不透明,虽然它们显式地以整数方式存储.一旦被分配,关键字的值可以在任何本地定义的通信子上与属性建立联系并访问它们.

当用MPI_COMM_DUP复制一个通信子时,函数copy_fn被唤醒.copy_fn应是 MPI_Copy_function类型,如下述定义:

 typedef int MPI_Copy_function(MPI_Comm *oldcomm,int *keyval, void *extra_state,void *attribute_val_in, void **attribute_val_out,int *flag)

此函数的Fortran说明如下所示:
FUNCTION COPY_FUNCTION(OLDCOMM,KEYVAL,EXTRA_STATE,ATTRIBUTE_VAL_IN, ATTRIBUTE_VAL_OUT,FLAG)

oldcomm中对应于每个关键字值的复制回调函数可以任意顺序被唤醒.用一个关键字值和其对应的属性可调用每个复制回调函数.如果它返回flag=0,则将删除复制通信子中的属性.否则 (flag=1),通过attribute_val_out置新的属性值.若成功,函数返回MPI_SUCCESS, 失败则返回一个错误码(在此情况下,MPI_COMM_DUP将失败).

copy_fn可从C或FORTRAN中作为MPI_NULL_FN来指定,在此情况下不发生对应于keyval的复制回 调;MPI_NULL_FN是一个除返回flag=0,不作任何事的函数.在C中,NULL函数指针同使用MPI_NULL_FN具有同样的效果.更方便的是,MPI_DUP_FN是一个可从C和FORTRAN中很容易获得的粗苹氐;它置flag=1,且在attribute_bal_out中返回attribute_val_in的值.

注意,MPI_COMM_DUP的C版本假定回调函数紧接着C原型,而相应的FORTRAN版本假定FORTRAN原型.

对用户的建议:一个有效的复制函数一方面能够将属性对应数据结构完全复制下来;另一方面, 可以通过一个引用计数机制仅进行对数据结构的另一次引用.其它类型的属性根本不能复制 (它们可能只专属于oldcomm).(对用户的建议结束)

下面定义了一个同copy_fn类似的回调删除函数.当通过MPI_COMM_FREE删除一个通信子或显式调用MPI_ATTR_DELETE时,就会唤醒delete_fn函数.delete_fn应象下面所定义的具有 MPI_Delete_function类型.

typedef int MPI_Delete_function(MPI_Comm *comm,int *keyval, void *attribute_val,void *extra_state)

此种函数的Fortran说明如下所示:

FUNCTION DELETE_FUNCTION(COMM,KEYVAL,ATTRIBUTE_VAL,EXTRA_STATE)

MPI_COMM_FREE和MPI_ATTR_DELETE调用此函数进行所需的去除属性的操作.可以用C中的空函数指针或者C或FORTRAN中的MPI_NULL_FN来说明它,在此情况下不发生keyval的删除回调.

特殊的关键字值MPI_KEYVAL_INVALID不会从MPI_KEYVAL_CREATE中返回.因此,可将其用于关键字值的静态初始化.

MPI_KEYVAL_FREE(keyval)

int MPI_Keyval_free(int *keyval)

MPI_KEYVAL_FREE(KEYVAL,IERROR)

释放一个现存的属性关键字.此函数将keyval的值置为MPI_KEYVAL_INVALID.注意,释放一个正 在使用的属性关键字并不出错,因为直到当对此关键字的所有引用(在进程的其它通信子中)都释放时,才进行实际释放.这些引用需要由程序显式释放,或者通过调用MPI_ATTR_DELETE来释放一个属性实例,或者通过调用MPI_COMM_FREE来释放与被释放的通信子相联系的所有属性实例。

对实现者的建议:函数MPI_NULL_FN在C中无需用别名(void(*))来表示,虽然这样做很好.它可以是一个可描述的合法的可调用函数.对于FORTRAN,最方便的是让MPI_NULL_FN是一个合法的不作任何事的函数调用.(对实现者的建议结束)

MPI_ATTR_PUT(comm,keyval,attribute_val)

int MPI_Attr_put(MPI_Comm comm,int keyval,void* attribute_val)

MPI_ATTR_PUT(COMM,KEYVAL,ATTRIBUTE_VAL,IERROR)

此函数存储规定的属性值,该值被MPI_ATTR_GET用作进行后面的回收.如果该值总是存在,则输出似乎是先调用MPI_ATTR_DELETE来删除前面的值(并执行回调函数delete_fn),然后存储新的值。如果没有关键字具有值keyval,则调用出错;尤其在MPI_KEYVAL_INVALID是一个错误的关键字的值时.

MPI_ATTR_GET(comm,keyval,attribute_val,flag)

int MPI_Attr_get(MPI_Comm comm,int keyval,void **attribute_val,int *flag)

MPI_ATTR_GET(COMM,KEYVAL,ATTRIBUTE_VAL,FLAG,IERROR)

通过关键字回收属性值.如果没有具有值keyval的关键字,则调用出错.另一方面,如果关键字值存在,但没有对应于该关键字的属性附到comm上,调用也是正确的;在这种情况下,调用返回 flag=false.尤其MPI_KEYVAL_INVALID是一个出错的关键字值.

MPI_ATTR_DELETE(comm,keyval)

int MPI_Attr_delete(MPI_Comm comm,int keyval)

MPI_ATTR_DELETE(COMM,KEYVAL,IERROR)

从缓冲区中通过关键字删除属性.当keyval被创建时,此函数唤醒指定的删除函数delete_fn.

无论何时使用函数MPI_COMM_DUP复制一个通信子,都要唤醒所有的同时置属性值的回调复制函数.无论何时使用函数MPI_COMM_FREE删除一个通信子,都要唤醒所有的同时置属性值的回调删除函数.

5.7.2 属性例子

对用户的建议:本例显示怎样写一个集合通信操作,该操作使用缓冲区以便在第一个调用后更加有效.代码风格假定MPI的结果仅返回错误状态.

 /*对应于本模块成员的关键字*/
 static int gop_key=MPI_KEYVAL_INVALID;

 typedef struct
 {
  int ref_count;
  /*我们所需的其它成员*/
  } gop_stuff_type;

 Efficient_Collective_Op(comm, ...)
 MPI_Comm comm;
 {
  gop_stuff_type *gop_stuff;
  MPI_Group group;
  int foundflag;

  MPI_Comm_group(comm,&group);

  if(gop_key==MPI_KEYVAL_INVALID)/*基于第一次调用得到一个关键字*/
  {
   if ( ! MPI_Attr_keyval_create(gop_stuff_copier, gop_stuff_destructor, &gop_key, (void *)0));
  /*当指定关键字的拷贝及删除回调的动作时,得到关键字*/

   MPI_Abort("Insufficient keys available");

   }

  MPI_Attr_get(comm,gop_key,&gop_stuff,&foundflag);
  if(foundflag)
  {
   /*模块已在此之前在本组中被执行. 我们将使用此缓冲信息*/
   }
  else
  {
   /*这是一个我们从未在其中缓存任何东西的组. 现在我们将这样做*/

   /*首先,为我们所需的成员分配存储空间, 并初始化引用计数*/

   gop_stuff=(gop_stuff_type *)malloc(sizeof(gop_stuff_type));
   if(gop_stuff==NULL)/*发生存储溢出错误则失败*/

   gop_strff ->ref_count=1;

   /*第二,将我们所需的放入*gop_stuff中. 这部分不在这里显示*/

   /*第三,将gop_stuff作为属性值存储起来*/
   MPI_Attr_put(comm,gop_key,gop_stuff);
  }
  /*然后,在任何情况下,使用*gop_stuff的内容来作全局运算*/
 }

 /*当释放组时,MPI调用下列例程*/

 gop_stuff_destructor(comm,keyval,gop_stuff,extra)
 MPI_Comm comm;
 int keyval;
 gop_stuff_type *gop_stuff;
 void *extra;
 {
  if(keyval !gop_key) {/*失败--编程错误*/}

  /*释放本组即去掉了对gop_stuff的一次引用*/
  gop_stuff->ref_count -=1;

  /*若不再有引用,则释放存储空间*/
  if (gop_stuff->ref_count==0){
   free((void *)gop_stuff);
   }
  }

  /*当复制一个组时,MPI调用下面的函数*/
  gop_stuff_copier (comm,keyval,gop_stuff,extra)
  MPI_Comm comm;
  int keyval;
  gop_stuff_type *gop_stuff;
  void *extra;
  {
   if (keyval !=gop_key){/*失败--编程错误*/}

   /*新组向gop_stuff增加一次引用*/
   gop_stuff ->ref_count +=1;
  }


Copyright: NPACT BACKWARD FORWARD