归约

  MPI_REDUCE( sendbuf, recvbuf, count, datatype, op, root, comm )
  IN sendbuf 发送缓冲区起始地址(待归约数据)
  OUT recvbuf 接收缓冲区起始地址(root中存放最后结果的地址)
  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预定义的归约操作类型如下:

归约操作
含义
允许的数据类型
C
MPI_MAX
最大值
整型,浮点型
MPI_MIN
最小值
整型,浮点型
MPI_SUM
整型,浮点型
MPI_PROD
整型,浮点型
MPI_LAND
逻辑与
整型
MPI_BAND
按位与
整型,字节型
MPI_LOR
逻辑或
整型
MPI_BOR
按位或
整型,字节型
MPI_LXOR
逻辑异或
整型
MPI_BXOR
按位异或
整型,字节型
MPI_MAXLOC
第一个最大值
专用类型
MPI_MINLOC
第一个最小值
专用类型

  其中的整型包括:
  MPI_INT MPI_UNSIGNED
  MPI_SHORT MPI_UNSIGNED_SHORT
  MPI_LONG MPI_UNSIGNED_LONG
  
  浮点型包括
  MPI_FLOAT
  MPI_DOUBLE
  MPI_LONG_DOUBLE

  字节型为MPI_BYTE
  专用类型是专门为这两个操作定义的,请参照MPI规范中对应的说明。

  顺便提一句,除了这些预定义归约操作类型外,MPI允许用户自定义归约操作类型,对应的操作为MPI_OP_CREATE和MPI_OP_FREE。用户需要保证这个归约操作满足结合律,但不要求可交换性。

  一个经典的例子。求π值。
  这种方法所依赖的原理如下式:
  

  所以π值可以转化为求函数在[0,1]区间与x轴所夹图形的面积。可以用下面的公式来逼近这个面积:
  
  可以证明,通过增大N,可以以任意精度逼近π值。

  程序如下(原例子由MPICH-1.2.3的例子cpi.c给出, /* */中为原文给出的注释
  //后面的是本文中所加得注释,它们可以被安全的删除):

  //pi.c
  //使用mpicc -o pi pi.c进行编译
  //使用mpirun -np 4 pi来运行(四个处理器)
  #include "mpi.h"
  #include <stdio.h>
  #include <math.h>

  // 计算出a点处函数f(x)=的值
  double f(double a)
  {
   return (4.0 / (1.0 + a*a));
  }

  int main(int argc,char **argv)
  {
   int done = 0, n, myid, numprocs, i;
   //25位的PI的参考值
   double PI25DT = 3.141592653589793238462643;
   double mypi, pi, h, sum, x;
   double startwtime = 0.0, endwtime;
   int namelen;
   char processor_name[MPI_MAX_PROCESSOR_NAME];

   MPI_Init(&argc,&argv);
   MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
   MPI_Comm_rank(MPI_COMM_WORLD,&myid);
   MPI_Get_processor_name(processor_name,&namelen);

   fprintf(stdout,"Process %d of %d on %s\n",
    myid, numprocs, processor_name);

   n = 0;
   while (!done)
   {
    if (myid == 0)
    {
  /*
     printf("Enter the number of intervals: (0 quits) ");
     scanf("%d",&n);
  */
    //设定N值,注意,用上面的两个语句,可以由用户给出N值
    if (n==0) n=10000; else n=0;
    //进程0开始计时
    startwtime = MPI_Wtime();
    }
    //由进程0广播N值
    MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);
    if (n == 0)
     done = 1;
    else
    {
     //h是区间的步长
     h = 1.0 / (double) n;
     sum = 0.0;
   /* A slightly better approach starts from large i and works back */
    //注意每个处理器都通过myid来确定自己的工作。
    for (i = myid + 1; i <= n; i += numprocs)
    {
     //x是每个区间的中点的x坐标
     x = h * ((double)i - 0.5);
     //sum现在包含的是每个处理器上的局部和
     sum += f(x);
    }
    //得到了局部的面积,为了减少不必要的计算,从上面的循环中提了出来。
    mypi = h * sum;

    //由局部的面积得到总的面积
    MPI_Reduce(&mypi, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);

    //由进程0打印结果,误差和计算时间。
    if (myid == 0)
    {
     printf("pi is approximately %.16f, Error is %.16f\n",
        pi, fabs(pi - PI25DT));
     endwtime = MPI_Wtime();
     printf("wall clock time = %f\n", endwtime-startwtime);
     fflush( stdout );
    }
   }
  }
  MPI_Finalize();
  return 0;
 }