17.2.292. MPI_Reduce

MPI_Reduce, MPI_Ireduce, MPI_Reduce_init - 对组内所有进程的值进行归约操作。

17.2.292.1. 语法

17.2.292.1.1. C语法

#include <mpi.h>

int MPI_Reduce(const void *sendbuf, void *recvbuf, int count,
               MPI_Datatype datatype, MPI_Op op, int root,
               MPI_Comm comm)

int MPI_Ireduce(const void *sendbuf, void *recvbuf, int count,
                MPI_Datatype datatype, MPI_Op op, int root,
                MPI_Comm comm, MPI_Request *request)


int MPI_Reduce_init(const void *sendbuf, void *recvbuf, int count,
                MPI_Datatype datatype, MPI_Op op, int root,
                MPI_Comm comm, MPI_Info info, MPI_Request *request)

17.2.292.1.2. Fortran语法

USE MPI
! or the older form: INCLUDE 'mpif.h'
MPI_REDUCE(SENDBUF, RECVBUF, COUNT, DATATYPE, OP, ROOT, COMM,
             IERROR)
     <type>  SENDBUF(*), RECVBUF(*)
     INTEGER COUNT, DATATYPE, OP, ROOT, COMM, IERROR

MPI_IREDUCE(SENDBUF, RECVBUF, COUNT, DATATYPE, OP, ROOT, COMM,
            REQUEST, IERROR)
     <type>  SENDBUF(*), RECVBUF(*)
     INTEGER COUNT, DATATYPE, OP, ROOT, COMM, REQUEST, IERROR

MPI_REDUCE_INIT(SENDBUF, RECVBUF, COUNT, DATATYPE, OP, ROOT, COMM,
            INFO, REQUEST, IERROR)
     <type>  SENDBUF(*), RECVBUF(*)
     INTEGER COUNT, DATATYPE, OP, ROOT, COMM, INFO, REQUEST, IERROR

17.2.292.1.3. Fortran 2008 语法

USE mpi_f08
MPI_Reduce(sendbuf, recvbuf, count, datatype, op, root, comm, ierror)
     TYPE(*), DIMENSION(..), INTENT(IN) :: sendbuf
     TYPE(*), DIMENSION(..) :: recvbuf
     INTEGER, INTENT(IN) :: count, root
     TYPE(MPI_Datatype), INTENT(IN) :: datatype
     TYPE(MPI_Op), INTENT(IN) :: op
     TYPE(MPI_Comm), INTENT(IN) :: comm
     INTEGER, OPTIONAL, INTENT(OUT) :: ierror

MPI_Ireduce(sendbuf, recvbuf, count, datatype, op, root, comm, request,
             ierror)
     TYPE(*), DIMENSION(..), INTENT(IN), ASYNCHRONOUS :: sendbuf
     TYPE(*), DIMENSION(..), ASYNCHRONOUS :: recvbuf
     INTEGER, INTENT(IN) :: count, root
     TYPE(MPI_Datatype), INTENT(IN) :: datatype
     TYPE(MPI_Op), INTENT(IN) :: op
     TYPE(MPI_Comm), INTENT(IN) :: comm
     TYPE(MPI_Request), INTENT(OUT) :: request
     INTEGER, OPTIONAL, INTENT(OUT) :: ierror


MPI_Reduce_init(sendbuf, recvbuf, count, datatype, op, root, comm, info, request,
             ierror)
     TYPE(*), DIMENSION(..), INTENT(IN), ASYNCHRONOUS :: sendbuf
     TYPE(*), DIMENSION(..), ASYNCHRONOUS :: recvbuf
     INTEGER, INTENT(IN) :: count, root
     TYPE(MPI_Datatype), INTENT(IN) :: datatype
     TYPE(MPI_Op), INTENT(IN) :: op
     TYPE(MPI_Comm), INTENT(IN) :: comm
     TYPE(MPI_Info), INTENT(IN) :: info
     TYPE(MPI_Request), INTENT(OUT) :: request
     INTEGER, OPTIONAL, INTENT(OUT) :: ierror

17.2.292.2. 输入参数

  • sendbuf: 发送缓冲区的地址(选项)。

  • count: 发送缓冲区中的元素数量(整数)。

  • datatype: 发送缓冲区元素的数据类型(句柄)。

  • op: 归约操作(句柄)。

  • root: 根进程的排名(整数)。

  • comm: 通信器(句柄)。

  • info: 信息(句柄,持久化)。

17.2.292.3. 输出参数

  • recvbuf: 接收缓冲区的地址(仅在根节点有效,可选参数)。

  • request: 请求(句柄,仅限非阻塞模式)。

  • ierror: 仅限Fortran:错误状态(整数)。

17.2.292.4. 描述

全局归约函数(MPI_ReduceMPI_Op_createMPI_Op_freeMPI_AllreduceMPI_Reduce_scatter、MPI_Scan)在组内所有成员之间执行全局归约操作(如求和、最大值、逻辑与等)。归约操作可以是预定义操作列表中的一种,也可以是用户自定义操作。全局归约函数具有多种形式:将归约结果返回给单个节点的reduce操作、将结果返回给所有节点的all-reduce操作,以及扫描(并行前缀)操作。此外,reduce-scatter操作结合了归约和散射操作的功能。

MPI_Reduce 通过操作op将组内每个进程输入缓冲区中的元素进行组合,并将组合结果返回到根进程的输出缓冲区中。输入缓冲区由参数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))。

17.2.292.5. 原地选项的使用

当通信器为内部通信器时,可以执行原地归约操作(输出缓冲区同时用作输入缓冲区)。将变量MPI_IN_PLACE作为根进程sendbuf的值。在这种情况下,根进程会从接收缓冲区获取输入数据,该数据随后将被输出数据替换。

请注意,MPI_IN_PLACE是一种特殊类型的值;其使用限制与MPI_BOTTOM相同。

由于原地(in-place)选项将接收缓冲区转换为发送-接收缓冲区,包含INTENT的Fortran绑定必须将其标记为INOUT,而非OUT。

17.2.292.6. 当通信器为跨通信器时

当通信器是跨通信器时,第一组中的根进程会收集第二组中所有进程的数据,然后执行op操作。第一组定义了根进程。该进程使用MPI_ROOT作为其root参数的值。其余进程使用MPI_PROC_NULL作为其root参数的值。第二组中的所有进程都使用第一组中该根进程的秩作为其root参数的值。在第二组中只有发送缓冲区参数是有效的,而在第一组的根进程中只有接收缓冲区参数是有效的。

17.2.292.7. 预定义的归约操作

MPI提供的预定义操作集如下所示(预定义归约操作)。该部分还列举了每个操作可应用的数据类型。此外,用户可以定义自己的操作,这些操作可以被重载以处理多种数据类型,包括基本类型和派生类型。关于用户自定义操作的详细说明,请参阅相关手册页(参见MPI_Op_create和MPI_Op_free)。

操作op始终被假定为具有结合性。所有预定义的操作也被假定为具有交换性。用户可以定义被假定为具有结合性但不具有交换性的操作。归约操作的"规范"执行顺序由组中进程的等级决定。然而,实现可以利用结合性,或结合性和交换性来改变执行顺序。对于非严格结合和交换的操作(如浮点加法),这可能会改变归约的结果。

预定义运算符仅适用于下面列出的MPI类型(预定义归约操作,以及下方的MINLOC和MAXLOC部分)。用户自定义运算符可以操作通用的派生数据类型。在这种情况下,归约操作应用的每个参数都是由此类数据类型描述的一个元素,可能包含多个基本值。MPI标准第4.9.4节"用户自定义操作"对此有进一步说明。

以下是为MPI_Reduce及相关函数MPI_AllreduceMPI_Reduce_scatterMPI_Scan提供的预定义操作。通过在op参数中指定以下操作来调用它们:

Name                Meaning
---------           --------------------
MPI_MAX             maximum
MPI_MIN             minimum
MPI_SUM             sum
MPI_PROD            product
MPI_LAND            logical and
MPI_BAND            bit-wise and
MPI_LOR             logical or
MPI_BOR             bit-wise or
MPI_LXOR            logical xor
MPI_BXOR            bit-wise xor
MPI_MAXLOC          max value and location
MPI_MINLOC          min value and location

MPI_MINLOC和MPI_MAXLOC这两个操作将在下文中单独讨论(MINLOC和MAXLOC)。对于其他预定义操作,我们下面列举了允许的op和datatype参数组合。首先,按照以下方式定义MPI基本数据类型组:

C integer:            MPI_INT, MPI_LONG, MPI_SHORT,
                      MPI_UNSIGNED_SHORT, MPI_UNSIGNED,
                      MPI_UNSIGNED_LONG
Fortran integer:      MPI_INTEGER
Floating-point:       MPI_FLOAT, MPI_DOUBLE, MPI_REAL,
                      MPI_DOUBLE_PRECISION, MPI_LONG_DOUBLE
Logical:              MPI_LOGICAL
Complex:              MPI_COMPLEX
Byte:                 MPI_BYTE

现在,每个选项的有效数据类型如下所示。

Op                              Allowed Types
----------------         ---------------------------
MPI_MAX, MPI_MIN                C integer, Fortran integer,
                                        floating-point

MPI_SUM, MPI_PROD               C integer, Fortran integer,
                                        floating-point, complex

MPI_LAND, MPI_LOR,              C integer, logical
MPI_LXOR

MPI_BAND, MPI_BOR,              C integer, Fortran integer, byte
MPI_BXOR

示例1: 一个计算两个向量点积的例程,这两个向量分布在一组进程中,并在进程零处返回结果。

SUBROUTINE PAR_BLAS1(m, a, b, c, comm)
REAL a(m), b(m)       ! local slice of array
REAL c                ! result (at process zero)
REAL sum
INTEGER m, comm, i, ierr

! local sum
sum = 0.0
DO i = 1, m
   sum = sum + a(i)*b(i)
END DO

! global sum
CALL MPI_REDUCE(sum, c, 1, MPI_REAL, MPI_SUM, 0, comm, ierr)
RETURN

示例2: 一个计算向量与数组乘积的例程,该向量和数组分布在一组进程中,并在进程零处返回结果。

SUBROUTINE PAR_BLAS2(m, n, a, b, c, comm)
REAL a(m), b(m,n)    ! local slice of array
REAL c(n)            ! result
REAL sum(n)
INTEGER n, comm, i, j, ierr

! local sum
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

! global sum
CALL MPI_REDUCE(sum, c, n, MPI_REAL, MPI_SUM, 0, comm, ierr)

! return result at process zero (and garbage at the other nodes)
RETURN

17.2.292.8. MINLOC 和 MAXLOC

操作符MPI_MINLOC用于计算全局最小值及其对应的索引。类似地,MPI_MAXLOC用于计算全局最大值及其索引。这些操作的一个应用场景是计算全局最小(最大)值以及包含该值的进程的排名。

定义MPI_MAXLOC的操作是

         ( u )    (  v )      ( w )
         (   )  o (    )   =  (   )
         ( i )    (  j )      ( k )

where

    w = max(u, v)

and

         ( i            if u > v
         (
   k   = ( min(i, j)    if u = v
         (
         (  j           if u < v)

MPI_MINLOC的定义方式类似:

         ( u )    (  v )      ( w )
         (   )  o (    )   =  (   )
         ( i )    (  j )      ( k )

where

    w = min(u, v)

and

         ( i            if u < v
         (
   k   = ( min(i, j)    if u = v
         (
         (  j           if u > v)

这两种操作都具有结合性和交换性。需要注意的是,如果对一系列(u(0), 0), (u(1), 1), ..., (u(n-1), n-1)这样的数值对应用MPI_MAXLOC进行归约操作,返回的结果将是(u, r),其中u= max(i) u(i),而r是该序列中第一个全局最大值的索引。因此,如果每个进程提供一个值及其在组内的排名,那么使用op = MPI_MAXLOC的归约操作将返回最大值以及第一个具有该值的进程的排名。类似地,MPI_MINLOC可用于返回最小值及其索引。更一般地说,MPI_MINLOC计算的是字典序最小值,其中元素根据每对数值的第一个分量排序,当第一个分量相同时则根据第二个分量决定顺序。

归约操作被定义为对由值和索引组成的参数对进行操作。对于Fortran和C语言,都提供了描述这种参数对的类型。这种参数可能具有混合类型的特性在Fortran中会带来问题。针对Fortran,通过让MPI提供的类型由与值相同类型的对组成,并将索引强制转换为相同类型,从而规避了这个问题。而在C语言中,MPI提供的参数对类型具有不同的类型,且索引是一个整型。

为了在归约操作中使用MPI_MINLOC和MPI_MAXLOC,必须提供一个表示值-索引对的数据类型参数。OpenMPI提供了九种这样的预定义数据类型。MPI_MAXLOC和MPI_MINLOC操作可与以下每种数据类型配合使用:

Fortran:
Name                     Description
MPI_2REAL                pair of REALs
MPI_2DOUBLE_PRECISION    pair of DOUBLE-PRECISION variables
MPI_2INTEGER             pair of INTEGERs

C:
Name                     Description
MPI_FLOAT_INT            float and int
MPI_DOUBLE_INT           double and int
MPI_LONG_INT             long and int
MPI_2INT                 pair of ints
MPI_SHORT_INT            short and int
MPI_LONG_DOUBLE_INT      long double and int

数据类型MPI_2REAL等同于:

MPI_TYPE_CONTIGUOUS(2, MPI_REAL, MPI_2REAL)

类似的声明适用于MPI_2INTEGER、MPI_2DOUBLE_PRECISION和MPI_2INT。

MPI_FLOAT_INT 数据类型相当于通过以下指令序列定义的。

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)

类似的声明也适用于MPI_LONG_INT和MPI_DOUBLE_INT。

示例3: 在C语言中,每个进程都有一个包含30个双精度浮点数的数组。针对这30个位置中的每一个,计算包含最大值的进程的值和排名。

...
/* each process has an array of 30 double: 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 );
/* At this point, the answer resides on process root
 */
if (myrank == root) {
    /* read ranks out
     */
    for (i=0; i<30; ++i) {
        aout[i] = out[i].val;
        ind[i] = out[i].rank;
    }
}

示例4: 相同的示例,使用Fortran语言。

...
! each process has an array of 30 double: ain(30)

DOUBLE PRECISION :: ain(30), aout(30)
INTEGER :: ind(30)
DOUBLE PRECISION :: in(2,30), out(2,30)
INTEGER :: i, myrank, root, ierr

call MPI_COMM_RANK(MPI_COMM_WORLD, myrank)
DO I=1, 30
    in(1,i) = ain(i)
    in(2,i) = myrank    ! myrank is coerced to a double
END DO

call MPI_REDUCE( in, out, 30, MPI_2DOUBLE_PRECISION, MPI_MAXLOC, root, &
                                                          comm, ierr )
! At this point, the answer resides on process root

IF (myrank == root) THEN
    ! read ranks out
    DO I= 1, 30
        aout(i) = out(1,i)
        ind(i) = out(2,i)  ! rank is coerced back to an integer
    END DO
END IF

示例5:每个进程都有一个非空的值数组。找出全局最小值、持有该值的进程的排名以及该值在该进程中的索引。

#define  LEN   1000

float val[LEN];        /* local array of values */
int count;             /* local number of values */
int myrank, minrank, minindex;
float minval;

struct {
    float value;
    int   index;
} in, out;

/* local minloc */
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;
    }

/* global minloc */
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 );
    /* At this point, the answer resides on process root
     */
if (myrank == root) {
    /* read answer out
     */
    minval = out.value;
    minrank = out.index / LEN;
    minindex = out.index % LEN;

所有MPI对象(例如MPI_Datatype、MPI_Comm)在Fortran中都是INTEGER类型。

17.2.292.9. 关于集合操作的注意事项

归约函数(MPI_Op)不会返回错误值。因此,如果这些函数检测到错误,它们只能选择调用MPI_Abort或静默跳过问题。因此,如果您将错误处理程序从MPI_ERRORS_ARE_FATAL更改为其他选项,例如MPI_ERRORS_RETURN,则可能不会指示任何错误。

原因在于确保所有集合例程返回相同错误值时存在的性能问题。

17.2.292.10. 错误

几乎所有MPI例程都会返回一个错误值;C语言例程通过函数返回值返回,Fortran例程则通过最后一个参数返回。

在返回错误值之前,会调用与通信对象(如通信器、窗口、文件)关联的当前MPI错误处理程序。如果MPI调用未关联任何通信对象,则该调用被视为附加到MPI_COMM_SELF,并将调用关联的MPI错误处理程序。当MPI_COMM_SELF未初始化时(即在MPI_Init/MPI_Init_thread之前、MPI_Finalize之后,或仅使用会话模型时),错误会触发初始错误处理程序。初始错误处理程序可通过在使用世界模型时调用MPI_Comm_set_errhandler来修改MPI_COMM_SELF,或通过mpiexec的mpi_initial_errhandler命令行参数,或MPI_Comm_spawn/MPI_Comm_spawn_multiple的info键来设置。如果未设置其他适当的错误处理程序,则MPI I/O函数将调用MPI_ERRORS_RETURN错误处理程序,而其他所有MPI函数将调用MPI_ERRORS_ABORT错误处理程序。

Open MPI 包含三个可使用的预定义错误处理器:

  • MPI_ERRORS_ARE_FATAL 导致程序中止所有连接的MPI进程。

  • MPI_ERRORS_ABORT 一个可在通信器、窗口、文件或会话上调用的错误处理程序。当在通信器上调用时,其行为类似于在该通信器上调用MPI_Abort。如果在窗口或文件上调用,则行为类似于在包含对应窗口或文件中进程组的通信器上调用MPI_Abort。如果在会话上调用,则仅中止本地进程。

  • MPI_ERRORS_RETURN 向应用程序返回一个错误代码。

MPI应用程序也可以通过调用以下方式实现自己的错误处理程序:

请注意,MPI不保证MPI程序在出现错误后能够继续运行。

查看MPI手册页获取完整的MPI错误代码列表。

有关更多信息,请参阅MPI-3.1标准中的错误处理部分。