MPI 集合通信函数 MPI_Reduce(),MPI_Allreduce(),MPI_Bcast(),MPI_Scatter(),MPI_Gather(),MPI_Allgather(),MPI_Scan(),MPI_Reduce_Scatter()

▶ 八个常用的集合通信函数

▶ 规约函数 MPI_Reduce(),将通信子内各进程的同一个变量参与规约计算,并向指定的进程输出计算结果

● 函数原型

MPI_METHOD MPI_Reduce(_In_range_(!= , recvbuf) _In_opt_ const void* sendbuf,  // 指向输入数据的指针_When_(root != MPI_PROC_NULL, _Out_opt_) void* recvbuf, // 指向输出数据的指针,即计算结果存放的地方_In_range_(>= , 0) int count,                           // 数据尺寸,可以进行多个标量或多个向量的规约_In_ MPI_Datatype datatype,                             // 数据类型_In_ MPI_Op op,                                         // 规约操作类型_mpi_coll_rank_(root) int root,                         // 目标进程号,存放计算结果的进程_In_ MPI_Comm comm                                      // 通信子
);

● 使用范例

{int size, rank, data, dataCollect;MPI_Init(NULL, NULL);MPI_Comm_size(MPI_COMM_WORLD, &size);MPI_Comm_rank(MPI_COMM_WORLD, &rank);data = rank;// 参与计算的数据MPI_Reduce((void *)&data, (void *)&dataCollect, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);// 所有的进程都要调用,而不是只在目标进程中调用MPI_Finalize();
}

● 操作类型,定义于 mpi.h

#define MPI_OP_NULL ((MPI_Op)0x18000000)#define MPI_MAX     ((MPI_Op)0x58000001)
#define MPI_MIN     ((MPI_Op)0x58000002)
#define MPI_SUM     ((MPI_Op)0x58000003)
#define MPI_PROD    ((MPI_Op)0x58000004)
#define MPI_LAND    ((MPI_Op)0x58000005)// 逻辑与
#define MPI_BAND    ((MPI_Op)0x58000006)// 按位与
#define MPI_LOR     ((MPI_Op)0x58000007)
#define MPI_BOR     ((MPI_Op)0x58000008)
#define MPI_LXOR    ((MPI_Op)0x58000009)
#define MPI_BXOR    ((MPI_Op)0x5800000a)
#define MPI_MINLOC  ((MPI_Op)0x5800000b)// 求最小值所在位置
#define MPI_MAXLOC  ((MPI_Op)0x5800000c)// 求最大值所在位置
#define MPI_REPLACE ((MPI_Op)0x5800000d)

▶ 规约并广播函数 MPI_Allreduce(),在计算规约的基础上,将计算结果分发到每一个进程中,相比于 MPI_Reduce(),只是少了一个 root 参数。除了简单的先规约再广播的方法,书中介绍了蝶形结构全局求和的方法。

● 函数原型

_Pre_satisfies_(recvbuf != MPI_IN_PLACE) MPI_METHOD MPI_Allreduce(_In_range_(!= , recvbuf) _In_opt_ const void* sendbuf,_Out_opt_ void* recvbuf,_In_range_(>= , 0) int count,_In_ MPI_Datatype datatype,_In_ MPI_Op op,_In_ MPI_Comm comm
);

● 使用范例

{int size, rank, data, dataCollect;MPI_Init(NULL, NULL);MPI_Comm_size(MPI_COMM_WORLD, &size);MPI_Comm_rank(MPI_COMM_WORLD, &rank);data = rank;MPI_Reduce((void *)&data, (void *)&dataCollect, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);// 所有的进程都要调用MPI_Finalize();
}

▶ 广播函数 MPI_Bcast(),将某个进程的某个变量的值广播到该通信子中所有进程的同名变量中

● 函数原型

MPI_METHOD MPI_Bcast(_Pre_opt_valid_ void* buffer,   // 指向输入 / 输出数据的指针_In_range_(>= , 0) int count,   // 数据尺寸_In_ MPI_Datatype datatype,     // 数据类型_mpi_coll_rank_(root) int root, // 广播源进程号_In_ MPI_Comm comm              // 通信子
);

● 使用范例

{int size, rank, data;MPI_Init(NULL, NULL);MPI_Comm_size(MPI_COMM_WORLD, &size);MPI_Comm_rank(MPI_COMM_WORLD, &rank);data = rank;MPI_Bcast((void *)&data, 1, MPI_INT, 0, MPI_COMM_WORLD);// 所有的进程都要调用,调用后所有 data 均被广播源进程的值覆盖MPI_Finalize();
}

▶ 散射函数 MPI_Scatter(),将向量数据分段发送到各进程中

● 函数原型和宏定义

_Pre_satisfies_(sendbuf != MPI_IN_PLACE) MPI_METHOD MPI_Scatter(_In_range_(!= , recvbuf) _In_opt_ const void* sendbuf,  // 指向需要分发的数据的指针_In_range_(>= , 0) int sendcount,                       // 分发到每一个进程的数据量,注意不是分发的数据总量_In_ MPI_Datatype sendtype,                             // 分发数据类型_When_(root != MPI_PROC_NULL, _Out_opt_) void* recvbuf, // 指向接收的数据的指针_In_range_(>= , 0) int recvcount,                       // 接受数据量,不小于上面分发到每一个进程的数据量_In_ MPI_Datatype recvtype,                             // 接收数据类型_mpi_coll_rank_(root) int root,                         // 分发数据源进程号_In_ MPI_Comm comm                                      // 通信子
);// 宏定义,mpi.h
#define MPI_IN_PLACE ((void*)(MPI_Aint)-1 // MPI_Aint 为 __int64 类型,表示地址

▶ 聚集函数 MPI_Gather(),将各进程中的向量数据分段聚集到一个进程的大向量中

● 函数原型

_Pre_satisfies_(recvbuf != MPI_IN_PLACE) MPI_METHOD MPI_Gather(_In_opt_ _When_(sendtype == recvtype, _In_range_(!= , recvbuf)) const void* sendbuf,// 指向需要聚集的数据的指针_In_range_(>= , 0) int sendcount,                                                   // 每个进程中进行聚集的数据量,不是聚集的数据总量_In_ MPI_Datatype sendtype,                                                         // 发送数据类型_When_(root != MPI_PROC_NULL, _Out_opt_) void* recvbuf,                             // 指向接收数据的指针_In_range_(>= , 0) int recvcount,                                                   // 从每个进程接收的接收数据量,不是聚集的数据总量_In_ MPI_Datatype recvtype,                                                         // 接收数据类型_mpi_coll_rank_(root) int root,                                                     // 聚集数据汇进程号_In_ MPI_Comm comm                                                                  // 通信子
);

● 函数 MPI_Scatter() 和 MPI_Gather() 的范例

{const int dataSize = 8 * 8;const int localSize = 8;int globalData[dataSize], localData[localSize], globalSum, i, comSize, comRank;MPI_Init(&argc, &argv);MPI_Comm_size(MPI_COMM_WORLD, &comSize);MPI_Comm_rank(MPI_COMM_WORLD, &comRank);if (comRank == 0)                                       // 初始化for (i = 0; i < dataSize; globalData[i] = i, i++);for (i = 0; i < localSize; localData[i++] = 0);MPI_Scatter((void *)&globalData, localSize, MPI_INT, (void *)&localData, localSize, MPI_INT, 0, MPI_COMM_WORLD);    // 分发数据for (i = 0; i < localSize; localData[i++]++);MPI_Barrier(MPI_COMM_WORLD);                                                                                        // 进程同步MPI_Gather((void *)&localData, localSize, MPI_INT, (void *)&globalData, localSize, MPI_INT, 0, MPI_COMM_WORLD);     // 聚集数据for (i = globalSum = 0; i < dataSize; globalSum += globalData[i++]);if (comRank == 0)printf("\nSize = %d, Rank = %d, result = %d\n", comSize, comRank, globalSum);MPI_Finalize();return 0;// 输出结果:Size = 8, Rank = 0, result = 2080,表示 0 + 1 + 2 + …… + 63
}

▶ 全局聚集函数 MPI_Allgather(),将各进程的向量数据聚集为一个大向量,并分发到每个进程中,相当于各进程同步该大向量的各部分分量。相比于 MPI_Gather(),只是少了一个 root 参数。

● 函数原型

_Pre_satisfies_(recvbuf != MPI_IN_PLACE) MPI_METHOD MPI_Allgather(_In_opt_ _When_(sendtype == recvtype, _In_range_(!= , recvbuf)) const void* sendbuf,_In_range_(>= , 0) int sendcount,_In_ MPI_Datatype sendtype,_Out_opt_ void* recvbuf,_In_range_(>= , 0) int recvcount,_In_ MPI_Datatype recvtype,_In_ MPI_Comm comm
);

● 函数 MPI_Scatter() 和 MPI_Allgather() 的范例,相当于从上面的范例中修改了一部分

{const int dataSize = 8 * 8;const int localSize = 8;int globalData[dataSize], localData[localSize], globalSum, i, comSize, comRank;MPI_Init(&argc, &argv);MPI_Comm_size(MPI_COMM_WORLD, &comSize);MPI_Comm_rank(MPI_COMM_WORLD, &comRank);for (i = 0; i < dataSize; globalData[i] = i, i++);// 改动for (i = 0; i < localSize; localData[i++] = 0);MPI_Scatter((void *)&globalData, localSize, MPI_INT, (void *)&localData, localSize, MPI_INT, 0, MPI_COMM_WORLD);    // 分发数据for (i = 0; i < localSize; localData[i++]++);MPI_Barrier(MPI_COMM_WORLD);MPI_Allgather((void *)&localData, localSize, MPI_INT, (void *)&globalData, localSize, MPI_INT, MPI_COMM_WORLD);     // 聚集数据,改动for (i = globalSum = 0; i < dataSize; globalSum += globalData[i++]);printf("\nSize = %d, rank = %d, result = %d\n", comSize, comRank, globalSum);// 改动MPI_Finalize();return 0;// 输出结果,八个进程乱序输出 2080
}

▶ 前缀和函数 MPI_Scan(),将通信子内各进程的同一个变量参与前缀规约计算,并将得到的结果发送回每个进程,使用与函数 MPI_Reduce() 相同的操作类型

● 函数原型

_Pre_satisfies_(recvbuf != MPI_IN_PLACE) MPI_METHOD MPI_Scan(_In_opt_ _In_range_(!= , recvbuf) const void* sendbuf,  // 指向参与规约数据的指针_Out_opt_ void* recvbuf,                                // 指向接收规约结果的指针_In_range_(>= , 0) int count,                           // 每个进程中参与规约的数据量_In_ MPI_Datatype datatype,                             // 数据类型_In_ MPI_Op op,                                         // 规约操作类型_In_ MPI_Comm comm                                      // 通信子
);

● 范例代码

int main(int argc, char **argv)
{const int nProcess = 8, localSize = 8, globalSize = localSize * nProcess;int globalData[globalSize], localData[localSize], sumData[localSize];int comRank, comSize, i;MPI_Init(&argc, &argv);MPI_Comm_rank(MPI_COMM_WORLD, &comRank);MPI_Comm_size(MPI_COMM_WORLD, &comSize);if (comRank == 0)for (i = 0; i < globalSize; globalData[i] = i, i++);MPI_Scatter(globalData, localSize, MPI_INT, localData, localSize, MPI_INT, 0, MPI_COMM_WORLD);for (i = 0; i < localSize; i++)printf("%2d, ", localData[i]);MPI_Scan(localData, sumData, localSize, MPI_INT, MPI_SUM, MPI_COMM_WORLD);for (i = 0; i < localSize; i++)printf("%2d, ", sumData[i]);MPI_Finalize();return 0;
}

● 输出结果,分别展示了 localSize 取 1 和 8 的结果,每个进程的输出中,前一半(分别为 1 个和 8 个元素)为个进程的原始数据,后一半为进行完前缀求和后的结果。注意到 localSize 取 8 时,程序将各进程保存向量的每一个元素分别进行前缀和,但同一进程中各元素之间不相互影响。

D:\Code\MPI\MPIProjectTemp\x64\Debug>mpiexec -n 8 -l MPIProjectTemp.exe
[1]  1,   1,
[6]  6,  21,
[5]  5,  15,
[4]  4,  10,
[3]  3,   6,
[0]  0,   0,
[2]  2,   3,
[7]  7,  28,
D:\Code\MPI\MPIProjectTemp\x64\Debug>mpiexec -n 8 -l MPIProjectTemp.exe
[4] 32,  33,  34,  35,  36,  37,  38,  39,  80,  85,  90,  95, 100, 105, 110, 115,
[1]  8,   9,  10,  11,  12,  13,  14,  15,   8,  10,  12,  14,  16,  18,  20,  22,
[0]  0,   1,   2,   3,   4,   5,   6,   7,   0,   1,   2,   3,   4,   5,   6,   7,
[7] 56,  57,  58,  59,  60,  61,  62,  63, 224, 232, 240, 248, 256, 264, 272, 280,
[3] 24,  25,  26,  27,  28,  29,  30,  31,  48,  52,  56,  60,  64,  68,  72,  76,
[6] 48,  49,  50,  51,  52,  53,  54,  55, 168, 175, 182, 189, 196, 203, 210, 217,
[5] 40,  41,  42,  43,  44,  45,  46,  47, 120, 126, 132, 138, 144, 150, 156, 162,
[2] 16,  17,  18,  19,  20,  21,  22,  23,  24,  27,  30,  33,  36,  39,  42,  45,

▶ 规约分发函数 MPI_Reduce_Scatter(),将数据进行规约计算,结果分段分发到各进程中

● 函数原型

_Pre_satisfies_(recvbuf != MPI_IN_PLACE) MPI_METHOD MPI_Reduce_scatter(_In_opt_ _In_range_(!= , recvbuf) const void* sendbuf,  // 指向输入数据的指针_Out_opt_ void* recvbuf,                                // 指向接收数据的指针_In_ const int recvcounts[],                            // 各进程接收规约结果的元素个数_In_ MPI_Datatype datatype,                             // 数据类型_In_ MPI_Op op,                                         // 规约操作类型_In_ MPI_Comm comm                                      // 通信子
);

● 使用范例

{const int nProcess = 8, localSize = 8, globalSize = nProcess * localSize, countValue = 1;int globalData[globalSize], localData[localSize], count[localSize], localSum[countValue], i, comSize, comRank;MPI_Init(&argc, &argv);MPI_Comm_size(MPI_COMM_WORLD, &comSize);MPI_Comm_rank(MPI_COMM_WORLD, &comRank);if (comRank == 0)for (i = 0; i < globalSize; globalData[i] = i, i++);MPI_Scatter(globalData, localSize, MPI_INT, localData, localSize, MPI_INT, 0, MPI_COMM_WORLD);for (i = 0; i < localSize; count[i++] = 1);for (i = 0; i < localSize; i++)printf("%3d, ", localData[i]);MPI_Reduce_scatter(localData, localSum, count, MPI_INT, MPI_SUM, MPI_COMM_WORLD);           for (i = 0; i < countValue; i++)printf("%3d, ", localSum[i]);MPI_Finalize();return 0;
}

● 输出结果,这里取定 localSize 为 8,输出结果的前 8 个元素为分发到各进程中参与规约计算的原始数据,后面元素为规约计算结果。程序将各进程保存向量的每一个元素分别进行前缀和,但同一进程中各元素之间不相互影响,通过修改 countValue(即参数 count 各元素的值),可以将规约计算的结果分发到各进程中

■ countValue == 1(count == { 1, 1, 1, 1, 1, 1, 1, 1 })情况,每个进程分得一个结果(注意与上面的函数 MPI_Scan() 作对比)

■ countValue == 2(count == { 2, 2, 2, 2, 2, 2, 2, 2 })情况,前 4 个进程每个进程分得 2 个结果,后 4 的进程访问越界,得到无意义的值

■ count == { 2, 0, 2, 0, 2, 0, 2, 0 } 情况,偶数号进程每个进程分得 2 个结果,奇数号进程分得 0 个结果,表现为无意义的值

■ 思考,这列每个 localData 长度为 8,所以规约计算的结果为一个长度为 8 的向量,可以在不同进程中进行分发(注意数据尺寸大小 localSize 与运行程序的进程数 nProcess 没有任何关系,只是在范例中恰好相等),而函数 MPI_Scan() 则相当于在此基础上保留了所有中间结果(部分前缀结果),所以其输出为一个长为 localSize,宽度为 nProcess 的矩阵,并且自动按照进程号均分。

D:\Code\MPI\MPIProjectTemp\x64\Debug>mpiexec -n 8 -l MPIProjectTemp.exe // countValue = 1
[6] 48,  49,  50,  51,  52,  53,  54,  55, 272,
[0]  0,   1,   2,   3,   4,   5,   6,   7, 224,
[2] 16,  17,  18,  19,  20,  21,  22,  23, 240,
[4] 32,  33,  34,  35,  36,  37,  38,  39, 256,
[3] 24,  25,  26,  27,  28,  29,  30,  31, 248,
[1]  8,   9,  10,  11,  12,  13,  14,  15, 232,
[5] 40,  41,  42,  43,  44,  45,  46,  47, 264,
[7] 56,  57,  58,  59,  60,  61,  62,  63, 280,D:\Code\MPI\MPIProjectTemp\x64\Debug>mpiexec -n 8 -l MPIProjectTemp.exe // countValue = 2
[0]  0,   1,   2,   3,   4,   5,   6,   7, 224, 232,
[6] 48,  49,  50,  51,  52,  53,  54,  55, 1717986912, 1717986912,
[1]  8,   9,  10,  11,  12,  13,  14,  15, 240, 248,
[3] 24,  25,  26,  27,  28,  29,  30,  31, 272, 280,
[4] 32,  33,  34,  35,  36,  37,  38,  39, 1717986912, 1717986912,
[5] 40,  41,  42,  43,  44,  45,  46,  47, 1717986912, 1717986912,
[2] 16,  17,  18,  19,  20,  21,  22,  23, 256, 264,
[7] 56,  57,  58,  59,  60,  61,  62,  63, 1717986912, 1717986912,D:\Code\MPI\MPIProjectTemp\x64\Debug>mpiexec -n 8 -l MPIProjectTemp.exe // countValue = 2,count[i] = (i + 1) % 2 * 2
[4] 32,  33,  34,  35,  36,  37,  38,  39, 256, 264,
[2] 16,  17,  18,  19,  20,  21,  22,  23, 240, 248,
[3] 24,  25,  26,  27,  28,  29,  30,  31, -858993460, -858993460,
[1]  8,   9,  10,  11,  12,  13,  14,  15, -858993460, -858993460,
[7] 56,  57,  58,  59,  60,  61,  62,  63, -858993460, -858993460,
[0]  0,   1,   2,   3,   4,   5,   6,   7, 224, 232,
[5] 40,  41,  42,  43,  44,  45,  46,  47, -858993460, -858993460,
[6] 48,  49,  50,  51,  52,  53,  54,  55, 272, 280,

MPI 集合通信函数 MPI_Reduce(),MPI_Allreduce(),MPI_Bcast(),MPI_Scatter(),MPI_Gather(),MPI_Allgather(),MPI_S相关推荐

  1. 我的并行计算之路(三)MPI集合通信之Scatter和Gather

    集合通信对比点对点通信来说,确实要方便许多,基本上不需要考虑各个进程该怎么发送,该怎么接收的问题,只要是使用一个通信子,函数接口自己就完成了.从进程的角度考虑,集合通信在分配任务量的时候更加平均,使各 ...

  2. 【MPI高性能计算】用集合通信改进梯形求和积分

    简述 [MPI高性能计算]梯形面积积分计算 里面有梯形积分公式的具体写法 这里采用MPI_Reduce的集合通信的方式来计算 API int MPI_Reduce(const void *sendbu ...

  3. MPI数据通信常用函数

    MPI数据通信常用函数: int MPI_Init(int *argc, char **argv[]); 功能:用于并行环境初始化,其后面的代码到MPI_Finalize()函数之前的代码在每个进程中 ...

  4. 用MPI_Send和MPI_Recv实现简单集群通信函数

    本片博客总结自我<并行计算>MPI实验 在这里我用阻塞式消息传递函数MPI_Send.MPI_Recv和非阻塞式消息发送函数MPI_Isend实现了三个简单的集群通信函数: MPI_Bca ...

  5. NCCL通信函数库相关资料整理

    NCCL 内部想参考NCCL库开发一套针对性的函数库.通过官方文档.源码.网上博客,整理了一些有关资料.图片都来源于网络,比较直观的介绍了NVIDIA GPU互联互通的底层硬件架构,和基于硬件链接开发 ...

  6. Scala函数式编程(三) scala集合和函数

    前情提要: scala函数式编程(二) scala基础语法介绍 scala函数式编程(二) scala基础语法介绍 前面已经稍微介绍了scala的常用语法以及面向对象的一些简要知识,这次是补充上一章的 ...

  7. socket通信函数的深入分析

    平时我们可能会用 socket 通信做个作业交给老师,一般情况下,都是拷贝一段代码,ip 地址改下,端口号改下也就可以了,即使是会写 socket 通信程序,甚至是使用 socket 做一个文件传输的 ...

  8. 求两个集合是否有交集 c语言_高中数学:集合与函数概念知识点总结

    我们为你准备了以下12个优质公众号,让你在这里一网打尽高中高考.教育教学哪些事儿,敬请点对应名称关注. 高中生学习库 高中全球通 人教部编课本 高考志愿 高中文科 高中理科 高中语文 高中数学 高中英 ...

  9. kotlin 添加第一个 集合_Flutter开发必学Dart语法篇之集合操作符函数与源码分析...

    简述: 在上一篇文章中,我们全面地分析了常用集合的使用以及集合部分源码的分析.那么这一节讲点更实用的内容,绝对可以提高你的Flutter开发效率的函数,那就是集合中常用的操作符函数.这次说的内容的比较 ...

最新文章

  1. 持续集成之戏说Check-in Dance
  2. Revit二次开发之“选择某一楼层的墙”
  3. 的注册表怎么才能删干净_白蚁怎么才能消灭干净?
  4. 【算法】一个简单的ISODATA原理
  5. 分子生物学-共价键基础
  6. 如何将 ipynb 发布到 blog 中(html, markdown格式)
  7. dm368ipnc 重写架构中的swosd 实现中文osd
  8. 关于2017届学长制作分享软件share(失物招领)的使用体验和需改进的内容
  9. DB2操作指南及命令大全word版
  10. 开挂的印度裔00后:7岁“出道”教编程,12岁成为IBM荣誉顾问
  11. kubernetes不同的命名空间下的容器能通信吗_在Kubernetes环境中,容器间如何进行网络通信?...
  12. linxu 启动过程分析
  13. mysql多表结果合并查询
  14. c语言中文网pdf免费下载,C语言中文网VIP教程11.9.pdf
  15. 软件专业大学生常用的刷题、找答案、找资源的网站
  16. 4、JVM垃圾回收机制
  17. 我身边那些资深程序员,他们是怎么突破年薪百万的?
  18. 深圳安全研讨会11月15日举行
  19. to_char在oracle 中函数使用方法
  20. 反向传播与它的直观理解

热门文章

  1. 大四上学期的分数和分析感悟
  2. 选择最大值的那条记录 sql
  3. U3D 动态创建Prefab的多个实例
  4. Flutter 填坑之 表单数据哪里去了?
  5. python3高阶函数:map(),reduce(),filter()的区别
  6. [SpringMVC]定义多个前缀映射的问题
  7. 初识Oracle的XMLType
  8. NSUserDefaults 简介,使用 NSUserDefaults 存储自定义对象
  9. Android 自定义Button按钮显示样式(正常、按下、获取焦点)
  10. MySQL数据类型:UNSIGNED注意事项