📄 mpi49.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
<TITLE>第四章: 集合通信</TITLE>
<META NAME="GENERATOR" CONTENT="Mozilla/3.0Gold (X11; I; SunOS 5.4 sun4m) [Netscape]">
</HEAD>
<BODY BGCOLOR="#F0F8FF">
<TABLE WIDTH="100%" >
<TR>
<TD align=left><B><FONT SIZE=+1>4.9 全局归约操作(Global Reduction Operations)</FONT></B></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>
<P>
<HR WIDTH="100%"></P>
<P>本节的所有函数在组内所有成员范围内实现全局归约操作(比如求和,求极大值,逻辑与等).这个归约操作即可以是MPI定义的操作,也可以是用户自定义的操作.全局归约操作分成几种类型:如将归约结果返回给一个节点的归约操作、将结果返回给所有节点的全局归约操作和搜索(或称并行前置)操作.另外,归约-分散操作是将归约和分散操作的功能合并起来.</P>
<P><A NAME="section1"></A><B><FONT SIZE=+1>4.9.1 归约(Reduce)</FONT></B></P>
<PRE>MPI_REDUCE(sendbuf,recvbuf,count,datatype,op,root,comm)
IN sendbuf 发送消息缓冲区的起始地址(可变)
OUT recvbuf 接收消息缓冲区中的地址(可变,仅对于根进程)
IN count 发送消息缓冲区中的数据个数(整型)
IN datatype 发送消息缓冲区的元素类型(句柄)
IN op 归约操作符(句柄)
IN root 根进程序列号(整型)
IN comm 通信子(句柄)
int MPI_Reduce(void* sendbuf, void* recvbuf, int count,
MPI_Datatype datatype, MPI_Op op, int root,
MPI_Comm comm)
MPI_REDUCE(SENDBUF, RECVBUF, COUNT, DATATYPE, OP, ROOT, COMM, IERROR)
<type> SENDBUF(*), RECVBUF(*)
INTEGER COUNT, DATATYPE, OP, ROOT, COMM, IERROR</PRE>
<P>MPI_REDUCE将组内每个进程输入缓冲区中的数据按op操作组合起来,并将其结果返回到序列号为root的进程的输出缓冲区中.输入缓冲区由参数sendbuf、count和datatype定义;输出缓冲区由参数recvbuf、count和datatype定义;两者的元素数目和类型都相同.所有组成员都用同样的参数count、datatype、op、root和comm来调用此例程,因此所有进程都提供长度相同、元素类型相同的输入和输出缓冲区.每个进程可能提供一个元素或一系列元素,组合操作针对每个元素进行.例如,如果操作是
MPI_MAX,发送缓冲区中包含两个浮点数(count=2并且datatype=MPI_FLOAT),结果recvbuf(1)存放着所有sendbuf(1)中的最大值,recvbuf(2)存放着所有sendbuf(2)中的最大值.</P>
<P>4.9.2节中列出了MPI提供的定义操作,并且列出了每种操作所允许的数据类型.
同时用户也可以定义自己的作用于几种数据类型的操作,即可以是基本的也可以是派生的.这点将在4.9.4中进一步解释.</P>
<P>操作op始终被认为是可结合的,并且所有MPI定义的操作被认为是可交换的.用户自定义的操作被认为是可结合的,但可以不是可交换的.常规的对归约结果的估价顺序由组内各进程的序列号所决定,但在实现中可以借助于结合性的优点,或者利用结合性和交换性来改变对结果的估价顺序.但这可能改变有些对结合性和交换性没有特殊限制的操作结果,比如浮点加法.</P>
<P><B>对实现者的建议:</B>强烈推荐在实现MPI_REDUCE时,当用同样的参数、同样的顺序调用此函数时,必须得到相同的结果.注意这可能妨碍对处理机物理位置进行的优化<B>(对实现者的建议结尾)</B></P>
<P>在MPI_REDUCE中datatype的类型必须和op相兼容.MPI中定义的操作仅能作用于4.9.2节和4.9.3节中列出的数据类型.用户自定义的操作可以作用于通常的或派生的数据类型,,这种情况下归约操作中引用的这种数据类型的参数可能包含几个基本值.这将在4.9.4节中进一步说明.</P>
<P><A NAME="section2"></A><B><FONT SIZE=+1>4.9.2 MPI定义的归约操作</FONT></B></P>
<P>MPI中已经定义好的一些操作,它们是为函数MPI_REDUCE和一些其他的相关函数,如MPI_ALLREDUCE、MPI_REDUCE_SCATTER和MPI_SCAN而定义的.这些操作用来设定相应的op.</P>
<PRE> <B>名字</B> <B>含义
</B> MPI_MAX 最大值
MPI_MIN 最小值
MPI_SUM 求和
MPI_PROD 求积
MPI_LAND 逻辑与
MPI_BAND 按位与
MPI_LOR 逻辑或
MPI_BOR 按位或
MPI_LXOR 逻辑异或
MPI_BXOR 按位异或
MPI_MAXLOC 最大值且相应位置
MPI_MINLOC 最小值且相应位置</PRE>
<P>MPI_MINLOC和MPI_MAXLOC这两个操作将在4.9.3节中分别讨论.下面列出MPI中定
义的其他有关于op和datatype参数的操作以及它们之间允许的组合.首先列出基本数据类型组:</P>
<PRE> C语言中的整型 MPI_INT MPI_LONG MPI_SHORT
MPI_UNSIGNED_SHORT MPI_UNSIGNED
MPI_UNSIGNED_LONG
Fortran语言中的整型 MPI_INTEGER
浮点数 MPI_FLOAT MPI_DOUBLE MPI_REAL
MPI_DOUBLE_PRECISION MPI_LONG_DOUBLE
逻辑型 MPI_LOGICAL
复数型 MPI_COMPLEX
字节型 MPI_BYTE</PRE>
<P>对每种操作允许的数据类型如下:</P>
<PRE> 操作 允许的数据类型
MPI_MAX, MPI_MIN C整数,Fortran整数,浮点数
MPI_SUM, MPI_PROD C整数,Fortran整数,浮点数,复数
MPI_LAND, MPI_LOR, MPI_LXOR C整数,逻辑型
MPI_BAND, MPI_BOR, MPI_BXOR C整数,Fortran整数,字节型</PRE>
<P>例4.15 由一组进程分布式地计算两个向量的点积,然后将结果返回到0号节点.</P>
<PRE> SUBROUTINE PAR_BLAS1(m, a, b, c, comm)
REAL a(m), b(m) ! 数组的局部部分
REAL c ! 结果变量(在0号节点)
REAL sum
INTEGER m, comm, i, ierr
! 局部和
sum = 0.0
DO i = 1, m
sum = sum + a(i)*b(i)
END DO
! 全局和
CALL MPI_REDUCE(sum, c, 1, MPI_REAL, MPI_SUM, 0, comm, ierr)
RETURN</PRE>
<P>例4.16: 由一组进程分布式地计算一个向量和一个数组的乘积,然后将结果返回到0号节点.</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_REDUCE(sum, c, n, MPI_REAL, MPI_SUM, 0, comm, ierr)
! 将结果返回0号节点(并在其他节点作废料收集)
RETURN</PRE>
<P><A NAME="section3"></A><B><FONT SIZE=+1>4.9.3 MINLOC和MAXLOC</FONT></B></P>
<P>MPI_MINLOC操作符用于计算全局最小值和这个最小值的索引号,MPI_MAXLOC操作
符用于计算全局最大值和这个最大值的索引号,这两个函数的一个用途是计算一个全局最小值(最大值)和这个值所在的进程序列号.</P>
<P>MPI_MAXLOC被定义为:</P>
<UL>
<P><IMG SRC="formu1.gif" tppabs="http://arch.cs.pku.edu.cn/parallelprogramming/mpispec/formu1.gif" HEIGHT=69 WIDTH=219></P>
</UL>
<P>这里 <I>w</I>=max(<I>u</I>,<I>v</I>) 且</P>
<UL>
<P><IMG SRC="formu2.gif" tppabs="http://arch.cs.pku.edu.cn/parallelprogramming/mpispec/formu2.gif" HEIGHT=98 WIDTH=253></P>
</UL>
<P>MPI_MINLOC被定义为:</P>
<UL>
<P><IMG SRC="formu3.gif" tppabs="http://arch.cs.pku.edu.cn/parallelprogramming/mpispec/formu3.gif" HEIGHT=78 WIDTH=230></P>
</UL>
<P>这里 <I>w</I>=min(<I>u</I>,<I>v</I>) 且</P>
<UL>
<P><IMG SRC="formu4.gif" tppabs="http://arch.cs.pku.edu.cn/parallelprogramming/mpispec/formu4.gif" HEIGHT=116 WIDTH=264></P>
</UL>
<P>两个操作都是可结合、可交换的.如将MPI_MAXLOC应用于(u<SUB>0</SUB>,0),(u<SUB>1</SUB>,1),..,(u<SUB>n-1</SUB>,n-1)这个序列上进行归约,那么(u,r)将被返回,这里u
= max<SUB>i</SUB>u<SUB>i</SUB>且r是第一个全 局最大值所在的位置.这样如果组内的每个进程都提供一个值和它自身的序列号,那么op
= MPI_MAXLOC的归约操作将返回其中的最大值和具有最大值的第一个进程的序列号.同
样MPI_MINLOC可以被用于返回最小值和它所在的位置.更一般地讲,MPI_MINLOC可以计算字典序的最小值,这里元素是按照值对中的第一个组分进行排序,而其索引号则由值对中的第二个组分进行求解.</P>
<P>这种归约操作作用于一个值对上,包括值和其索引号.在Fortran和C语言中,值对都要有相应的类型,这种参数的混合类型对Fortran语言来说是比较困难的.在Fortran中的解决方案是:值对中值的类型是相同的,都采用MPI中所提供的类型,并且索引号也必须具有和值相同的类型;在C语言中,这些值对可以有不同的类型且索引号必须是整型.</P>
<P>为了在归约操作中使用MPI_MINLOC和MPI_MAXLOC,必须提供表示这个值对(值及其索引号)参数的类型.MPI定义了七个这样的类型,MPI_MAXLOC和MPI_MINLOC
可以采 用下列的数据类型:</P>
<PRE> Fortran语言中:
名字 描述
MPI_2REAL 实型值对
MPI_2DOUBLE_PRECISION 双精度变量值对
MPI_2INTEGER 整型值对
C语言中:
名字 描述
MPI_FLOAT_INT 浮点型和整型
MPI_DOUBLE_INT 双精度和整型
MPI_LONG_INT 长整型和整型
MPI_2INT 整型值对
MPI_SHORT_INT 短整型和整型
MPI_LONG_DOUBLE_INT 长双精度浮点型和整型</PRE>
<P>类型MPI_2REAL可以理解成按下列方式的定义(<A HREF="mpi312.htm" tppabs="http://arch.cs.pku.edu.cn/parallelprogramming/mpispec/mpi312.htm">见3.12节</A>)</P>
<PRE> MPI_TYPE_CONTIGUOUS(2, MPI_REAL, MPI_2REAL)</PRE>
<P>MPI_2INTEGER、MPI_2DOUBLE_PRECISION和MPI_2INT的定义方式和MPI_2REAL 相仿,MPI_FLOAT_INT类型和下列指令等价:</P>
<PRE> type[0] = MPI_FLOAT
type[1] = MPI_INT
disp[0] = 0
disp[1] = sizeof(float)
block[0] = 1
block[1] = 1
MPI_TYPE_STRUCT(2, block, disp, type, MPI_FLOAT_INT)</PRE>
<P>MPI_LONG_INT和MPI_DOUBLE_INT的定义方式和MPI_FLOAT_INT相仿.</P>
<P>例4.17: 每个进程都有一30个双精度数的数组(以C语言为例),计算30个位置上的值并返回包含最大值的进程序列号.</P>
<PRE> /* 每个进程都有一30个双精度数组ain[30] */
double ain[30],aout[30];
int ind[30];
struct {
double val;
int rank;
} in[30], out[30];
int i, myrank, root;
MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
for (i=0; i<30; ++i) {
in[i].val = ain[i];
in[i].rank = myrank;
}
MPI_Reduce(in, out, 30, MPI_DOUBLE_INT, MPI_MAXLOC, root, comm);
/* 此时结果归约到根进程 */
if (myrank == root) {
/* 读出其值 */
for (i=0; i<30; ++i) {
aout[i] = out[i].val;
ind[i] = out[i].rank;
}
}
</PRE>
<P>例4.18: 和上例相同,只是用Fortran语言书写.</P>
<PRE> ! 每个进程都有一30个双精度型的数组ain(30)
DOUBLE PRECISION ain(30), aout(30)
INTEGER ind(30);
DOUBLE PRECISION in(2,30), out(2,30)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -