📄 mpi49.htm
字号:
INTEGER i, myrank, root, ierr;
MPI_COMM_RANK(MPI_COMM_WORLD, myrank);
DO I=1, 30
in(1,i) = ain(i)
in(2,i) = myrank ! myrank必须是一个双精度型
END DO
MPI_REDUCE(in, out, 30, MPI_2DOUBLE_PRECISION, MPI_MAXLOC, root,
comm, ierr);
! 此时结果归约到根进程
IF (myrank .EQ. root) THEN
! 读出其值
DO I = 1, 30
aout(i) = out(1,i)
ind(i) = out(2,i) ! rank被强制转换回整型
END DO
END IF</PRE>
<P>例4.19: 每个进程有一个非空数组,找出全局最小值、拥有此最小值的进程序列号及在此进程的位置.</P>
<PRE> #define LEN 1000
float val[LEN]; /* 局部数组 */
int count; /* 局部值 */
int myrank, minrank, minindex;
float minval;
struct {
float value;
int index;
} in, out;
/* 局部最小值及其索引号 */
in.value = val[0];
in.index = 0;
for (i=1; i < count; i++)
if (in.value > val[i]) {
in.value = val[i];
in.index = i;
}
/* 全局最小值及其索引号 */
MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
in.index = myrank*LEN + in.index;
MPI_Reduce(in, out, 1, MPI_FLOAT_INT, MPI_MINLOC, root, comm);
/* 此时结果返回到根进程 */
if (myrank == root) {
/* 读出其值 */
minval = out.value;
minrank = out.index/LEN;
minindex = out.index%LEN;
}
</PRE>
<P><B>原则:</B> 对MPI_MINLOC和MPI_MAXLOC的定义提供了这样一种好处:它可以不经过任
何特殊处理完成两个操作,其过程和其他归约操作很相象.程序员可以按其愿望对MPI_MAXLOC和MPI_MINLOC
进行定义.其缺限是值和索引号必须交错放置,并且在 Fortran语言中值和索引号的类型必须一致.<B>(原则结尾)</B></P>
<P><A NAME="section4"></A><B><FONT SIZE=+1>4.9.4 用户自定义的操作</FONT></B></P>
<PRE>MPI_OP_CREATE(function, commute, op)
IN function 用户自定义的函数(函数)
IN commute 可交换则为true,否则为false
OUT op 操作(句柄)
int MPI_Op_create(MPI_User_function *function,int commute,MPI_Op *op)
MPI_OP_CREATE(FUNCTION, COMMUTE, OP, IERROR)
EXTERNAL FUNCTION
LOGICAL COMMUTE
INTEGER OP, IERROR</PRE>
<P>MPI_OP_CREATE将用户自定义的操作和op绑定在一起,可以用于函数MPI_REDUCE
、MPI_ALLREDUCE、MPI_REDUCE_SCATTER和MPI_SCAN中.用户自定义的操作被认为是可以结合的.如果commute=true,则此操作是可交换且可结合的;如果commute=false,则此操作的顺序是固定地按进程序列号升序方式进行,即从序列号为0的进程开始.</P>
<P>function是用户自定义的函数,必须具备四个参数: invec, inoutvec, len和datatype.</P>
<P>在ANSI C中这个函数的原型是:</P>
<PRE> typedef void MPI_User_function(void *invec, void *inoutvec,
int *len, MPI_Datatype *datatype);</PRE>
<P>在Fortran中对用户自定义的函数描述如下:</P>
<PRE> FUNCTION USER_FUNCTION(INVEC(*), INOUTVEC(*), LEN, TYPE)
<type> INVEC(LEN), INOUTVEC(LEN)
INTEGER LEN, TYPE</PRE>
<P>参数datatype用于控制传送给MPI_REDUCE的数据类型.用户的归约函数应当写成下列方式:当函数被激活时,让u[0],...,u[len-1]是通信缓冲区中len个由参数invec、len和datatype描述的元素;让v[0],...,v[
len- 1]是通信缓冲区中len个由参数inoutvec、len和datatype描述的元素;当函数返回时,让w[0],...,w[len-1]是通信缓冲区中len个由参数inoutvec、len和datatype描述的元素;此时w[i]
= u[i]·v[i] ,i从0到len-1,这里·是function所定义的归约操作. </P>
<P>从非正式的角度来看,我们可以认为invec和inoutvec是函数中长度为len的数组,归约的结果重写了inoutvec的值.每次调用此函数都导致了对这len个元素逐个进行相应的操作,例如:函数将invec[i]·inoutvec[i]的结果返回到inoutvec[i]中,i从0
到count-1,这里·是由此函数执行的归约操作.</P>
<P><B>原则:</B> 参数len可以使MPI_REDUCE不去调用输入缓冲区中的每个元素,也就是说,系统可以有选择地对输入进行处理.在C语言中,为了与Fortran语言兼容,此参数以引用的方式传送.</P>
<P>通过内部对数据类型参数datatype的值与已知的、全局句柄进行比较,就可能将一个用户自定义的操作作用于几种不同的数据类型.<B>(原则结尾)</B></P>
<P>通常的数据类型可以传给用户自定义的参数,然而互不相邻的数据类型可能会导致低效率.</P>
<P>在用户自定义的函数中不能调用MPI中的通信函数.当函数出错时可能会调用MPI_ABORT.</P>
<P><B>对用户的建议:</B>假设用户建立了一个可重载的自定义归约函数库:在每次调用时,根据操作数的类型,参数datatype用于选择正确的执行路径.用户自定义的归约操作不能对传送给它的数据类型进行"译码"操作,也不能自动识别数据类型句柄和它所代表的数据类型之间的对应关系.这种对应关系应当在建立数据类型时完成.在使用这个库时,必须先执行库初始化操作,初始化时将定义库中要使用的数据类型,并将其句柄存放到可以被用户代码和程序库代码能访问到的全局、静态变量中.</P>
<P>Fortran版本的MPI_REDUCE将用Fortran语言中调用方式来调用一个用户自定义的归约函数,并传给它一个Fortran中的数据类型;C版本的MPI_REDUCE以C语言的方式和C语言中这种数据类型句柄来调用此函数.应用混合型语言的用户须定义相应的归约函数<B>(对用户的建议结尾)</B></P>
<P><B>对实现者的建议:</B>下面给出MPI_REDUCE本质的但非高效的实现过程.</P>
<PRE> if (rank > 0) {
RECV(tempbuf, count, datatype, rank-1,...)
User_reduce(tempbuf, sendbuf, count, datatype)
}
if (rank < groupsize-1) {
SEND(sendbuf, count, datatype, rank+1,...)
}
/* 结果位于进程groupsize-1上,现在将其发送到根进程 */
if (rank == groupsize-1) {
SEND(sendbuf, count, datatype, root, ...)
}
if (rank == root) {
RECV(recvbuf, count, datatype, groupsize-1,...)
}</PRE>
<P>归约操作顺序地、依次地从进程0计算到进程groupsize-1.这样选择顺序的原因是为了照顾用户自定义的User_reduce函数中有些操作的顺序是不可交换的.更有效的实现方法是采用可结合性的特点或应用对数树形归约法.对于在MPI_OP_CREATE
中 commute为true的情况,还可以利用可交换性的特点,也就是说可以对缓冲区中的一部分数据进行归约操作,这样通信和计算就可以流水执行,即可以传送的数据块的长度len可以小于count.</P>
<P>MPI中定义好的操作可以作为用户自定义操作的一个库,但为获得更好的性能,MPI_REDUCE应根据具体情况特殊处理<B>(对实现者的建议结尾)</B></P>
<PRE>MPI_OP_FREE(op)
IN op 操作(句柄)
int MPI_op_free(MPI_Op *op)
MPI_OP_FREE(OP, IERROR)
INTEGER OP, IERROR</PRE>
<P>如要将用户自定义的归约操作撤消,将op设置成MPI_OP_NULL.</P>
<P>应用用户自定义的归约操作的例子</P>
<P>例4.20 计算一个复数数组的积(用C语言编程)</P>
<PRE> typedef struct {
double real,imag;
} Complex;
/* 用户自定义的函数 */
void myProd(Complex *in, Complex *inout, int *len, MPI_Datatype *dptr)
{
int i;
Complex c;
for (i=0; i < *len; ++i) {
c.real = inout->real*in->real - inout->imag*in->imag;
c.imag = inout->real*in->imag + inout->imag*in->real;
*inout = c;
in++; inout++;
}
}
/* 然后调用它 */
/* 每个进程都有一个100个元素的复数数组 */
Complex a[100], answer[100];
MPI_Op myOp;
MPI_Datatype ctype;
/* 告之MPI复数结构是如何定义的 */
MPI_Type_contiguous(2, MPI_DOUBLE, &ctype);
MPI_Type_commit(&ctype);
/* 生成用户定义的复数乘积操作 */
MPI_Op_create(myProd, True, &myOp);
MPI_Reduce(a, answer, 100, ctype, myOp, root, comm);
/* 这时结果(为100个复数)就已经存放在根进程 */</PRE>
<P><A NAME="section5"></A><B><FONT SIZE=+1>4.9.5 全局归约(All-Reduce)</FONT></B></P>
<P>MPI中还包括对每个归约操作的变形,即将结果返回到组内的所有进程.MPI要求组内所有参与的进程都归约同一个结果.</P>
<PRE>MPI_ALLREDUCE(sendbuf, recvbuf, count, datatype, op, comm)
IN sendbuf 发送消息缓冲区的起始地址(可变)
OUT recvbuf 接收消息缓冲区的起始地址(可变)
IN count 发送消息缓冲区中的数据个数(整型)
IN datatype 发送消息缓冲区中的数据类型(句柄)
IN op 操作(句柄)
IN comm 通信子(句柄)
int MPI_Allreduce(void* sendbuf, void* recvbuf, int count,
MPI_Datatype datatype, MPI_Op op, MPI_Comm comm)
MPI_ALLREDUCE(SENDBUF, RECVBUF, COUNT, DATATYPE, OP, COMM, IERROR)
<type> SENDBUF(*), RECVBUF(*)
INTEGER COUNT, DATATYPE, OP, COMM, IERROR</PRE>
<P>除了将结果返回给组内的所有成员外,其他同MPI_REDUCE.</P>
<P><B>对实现者的建议: </B>全局归约操作(all-reduce)可以由归约操作reduce和广播操作(broadcast)来实现,但直接实现可以获得更好的性能.<B>(对实现者的建议结尾)</B></P>
<P>例4.21: 在一组分布式的进程上计算一个向量和一个数组的乘积,并将结果返回到所有节点上(见例4.16).</P>
<PRE> SUBROUTINE PAR_BLAS2(m, n, a, b, c, comm)
REAL a(m), b(m,n) ! 局部数组
REAL c(n) ! 结果
REAL sum(n)
INTEGER n, comm, i, j, ierr
! 局部和
DO j = 1 , n
sum(j) = 0.0
DO i = 1, m
sum(j) = sum(j) + a(i)*b(i,j)
END DO
END DO
! 全局和
CALL MPI_ALLREDUCE(sum, c, n, MPI_REAL, MPI_SUM, 0, comm, ierr)
! 将结果返回给所有节点
RETURN</PRE>
<P>
<HR WIDTH="100%"></P>
<TABLE WIDTH="100%" >
<TR>
<TD align=left>Copyright: NPACT </TD>
<TD align=right><A HREF="mpi48.htm" tppabs="http://arch.cs.pku.edu.cn/parallelprogramming/mpispec/mpi48.htm"><IMG SRC="backward.gif" tppabs="http://arch.cs.pku.edu.cn/image/backward.gif" ALT="BACKWARD" HEIGHT=32 WIDTH=32></A><A HREF="mpi410.htm" tppabs="http://arch.cs.pku.edu.cn/parallelprogramming/mpispec/mpi410.htm"><IMG SRC="forward.gif" tppabs="http://arch.cs.pku.edu.cn/image/forward.gif" ALT="FORWARD" HEIGHT=32 WIDTH=32></A>
</TD>
</TR>
</TABLE>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -