MPI 并行奇偶交换排序 + 集合通信函数 Sendrecv() Sendvecv_replace()
▶ 《并行程序设计导论》第三章的例子程序
● 代码
#include <stdio.h>
#include <mpi.h>
#include <stdlib.h> const int nProcess = , localSize = * , globalSize = nProcess * localSize; int compare(const void *a, const void *b) { return *(int *)a - *(int *)b; }// 用于快排的回调函数 void merge(int *a, const int *b, const bool low)// 归并数组 a 和 b,由 low 决定归并小端还是大端
{
int temp[localSize], pa, pb, ptemp;
if (low)
{
for (pa = pb = ptemp = ; ptemp < localSize;)
{
if (a[pa] <= b[pb])
temp[ptemp++] = a[pa++];
else
temp[ptemp++] = b[pb++];
}
}
else
{
for (pa = pb = ptemp = localSize - ; ptemp >= ;)
{
if (a[pa] <= b[pb])
temp[ptemp--] = b[pb--];
else
temp[ptemp--] = a[pa--];
}
}
for (pa = ptemp = ; pa < localSize; a[pa++] = temp[ptemp++]);
return;
} int main(int argc, char* argv[])
{ int globalData[globalSize], globalDataForQSort[globalSize], localData[localSize], changeData[localSize];
int i, comSize, comRank, partner;
double timeQSort, timeProcess, timeSort, accerlateRatio; MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &comSize);
MPI_Comm_rank(MPI_COMM_WORLD, &comRank); srand();
if (comRank == )
{
printf("Lrngth: %d\n", globalSize);
for (i = ; i < globalSize; globalData[i] = globalDataForQSort[i] = rand(), i++); // CPU 单线程排序并计时
timeQSort = MPI_Wtime();
qsort(globalDataForQSort, globalSize, sizeof(int), &compare);
timeQSort = MPI_Wtime() - timeQSort;
printf("QSort time: %f ms\n", timeQSort * );
} // MPI 多进程排序
MPI_Barrier(MPI_COMM_WORLD);
timeProcess = MPI_Wtime();
MPI_Scatter(globalData, localSize, MPI_INT, localData, localSize, MPI_INT, , MPI_COMM_WORLD);// 分发数据
qsort(localData, localSize, sizeof(int), &compare); // 自行快排
for (i = ; i < nProcess; i++) // 每次循环完成一次交换,交换 nProcess 次完成排序
{
// 寻找 partner
if (i % )
partner = comRank + (comRank % ? + : -);
else
partner = comRank + (comRank % ? - : +);
if (partner == - || partner == comSize)
partner = MPI_PROC_NULL; // 与 partner 交换数据,使用函数 MPI_Sendrecv() 与后面注释中的发送、接收函数等价
MPI_Sendrecv(localData, localSize, MPI_INT, partner, , changeData, localSize, MPI_INT, partner, , MPI_COMM_WORLD, MPI_STATUS_IGNORE);
/*
if (comRank % 2) // 奇数号进程先接受再发送,偶数号进程先发送再接收
{
MPI_Recv(changeData, localSize, MPI_INT, partner, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
MPI_Send(localData, localSize, MPI_INT, partner, 0, MPI_COMM_WORLD);
}
else
{
MPI_Send(localData, localSize, MPI_INT, partner, 0, MPI_COMM_WORLD);
MPI_Recv(changeData, localSize, MPI_INT, partner, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
}
*/
if (partner != MPI_PROC_NULL) // 如果 partner 比本结点大,则本结点保留较小的部分
merge(localData, changeData, partner > comRank);
MPI_Barrier(MPI_COMM_WORLD); // 归并后进行同步,准备下一次交换
}
MPI_Gather(localData, localSize, MPI_INT, globalData, localSize, MPI_INT, , MPI_COMM_WORLD);// 聚集数据到主进程,以便检查结果
timeProcess = MPI_Wtime() - timeProcess;
MPI_Reduce((void *)&timeProcess, (void *)&timeSort, , MPI_DOUBLE, MPI_MAX, , MPI_COMM_WORLD); // 将排序结果与 CPU 单线程的结果相比较
if (comRank == )
{
printf("Sort time: %f ms, accerlate ratio = %f\n", timeSort * , timeQSort / timeSort);
for (i = ; i < globalSize; i++)
{
if (globalData[i] != globalDataForQSort[i])
break;
}
if (i == globalSize)
printf("Result Correct!");
else
printf("Error at i = %d, qsort[i] = %d, sort[i] = %d", i, globalDataForQSort[i], globalData[i]);
}
MPI_Finalize();
return ;
}
● 输出结果。中等规模的数组加速比较高,小规模情形非交换或归并的其他过程耗时较多,没有体现出并行的优势;大规模情形数据交换量较大,耗时较多
D:\Code\MPI\MPIProjectTemp\x64\Debug>mpiexec -n -l MPIProjectTemp.exe
[]Lrngth:
[]QSort time: 2.573407 ms
[]Sort time: 0.785797 ms, accerlate ratio = 3.274899
[]Result Correct!
D:\Code\MPI\MPIProjectTemp\x64\Debug>mpiexec -n -l MPIProjectTemp.exe
[]Lrngth:
[]QSort time: 101.129885 ms
[]Sort time: 20.440968 ms, accerlate ratio = 4.947412
[]Result Correct!
D:\Code\MPI\MPIProjectTemp\x64\Debug>mpiexec -n -l MPIProjectTemp.exe
[]Lrngth:
[]QSort time: 3051.212378 ms
[]Sort time: 687.252457 ms, accerlate ratio = 4.439726
[]Result Correct!
● 在 MPI 代码结尾处混入 getchar(); 会导致进程挂起,无法正常退出,报错代码是 0xc00000fd(栈溢出?),写的时候不要加上这条语句。
▶ 函数 MPI_Sendrecv() 和 MPI_Sendrecv_replace(),同时进行一次阻塞时信息发送和接收,完全由 MPI 进行调度,当发送数据和接收数据变量名不同时使用函数 MPI_Sendrecv(),名字相同时两者均可使用(在函数 MPI_Sendrecv() 中将参数 send_buf_p 与 recv_buf_p 设为相同即可)
● 函数声明
MPI_METHOD MPI_Sendrecv(
_In_opt_ const void* sendbuf, // 发送数据
_In_range_(>= , ) int sendcount, // 发送数据的个数
_In_ MPI_Datatype sendtype, // 发送数据类型
_In_range_(>= , MPI_PROC_NULL) int dest, // 发送目标进程号
_In_range_(>= , ) int sendtag, // 发送标签
_Out_opt_ void* recvbuf, // 接收数据
_In_range_(>= , ) int recvcount, // 接收数据个数
_In_ MPI_Datatype recvtype, // 接收数据类型
_In_range_(>= , MPI_ANY_SOURCE) int source, // 接收数据源
_In_range_(>= , MPI_ANY_TAG) int recvtag, // 接受标签
_In_ MPI_Comm comm, // 通信子
_Out_ MPI_Status* status // 状态结构指针
); MPI_METHOD MPI_Sendrecv_replace(
_Inout_opt_ void* buf, // 合并 sendbuf 和 recvbuf
_In_range_(>= , ) int count, // 合并 sendcount 和 recvcount
_In_ MPI_Datatype datatype, // 合并 sendtype 和 recvtype
_In_range_(>= , MPI_PROC_NULL) int dest,
_In_range_(>= , ) int sendtag,
_In_range_(>= , MPI_ANY_SOURCE) int source,
_In_range_(>= , MPI_ANY_TAG) int recvtag,
_In_ MPI_Comm comm,
_Out_ MPI_Status* status
);
● 函数 MPI_Sendrecv_replace() 的使用范例
{
const int nProcess = , localSize = , globalSize = localSize * nProcess;
int globalData[globalSize], localData[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 == )
for (i = ; i < globalSize; globalData[i] = i, i++); MPI_Scatter(globalData, localSize, MPI_INT, localData, localSize, MPI_INT, , MPI_COMM_WORLD);
for (i = ; i < localSize; i++) // 打印各进程数据
printf("%2d, ", localData[i]); MPI_Barrier(MPI_COMM_WORLD); // 同步后通信,将每个进程的数据环形发送给下一个进程
MPI_Sendrecv_replace(localData, localSize, MPI_INT, (comRank + ) % nProcess, , (comRank + nProcess - ) % nProcess, , MPI_COMM_WORLD, MPI_STATUS_IGNORE);
MPI_Barrier(MPI_COMM_WORLD); for (i = ; i < localSize; i++) // 再次打印各进程数据
printf("%2d, ", localData[i]); MPI_Finalize();
return ;
}
● 输出结果,发现数据被正确的轮换了,但是各进程的两次输出却是连在一起的,猜想各进程输出时被加载到了同一个缓冲区中,再按照进程排序合并,最后一次性向 stdout 输出,可以使用 fflush(stdout); 来清空缓冲区
D:\Code\MPI\MPIProjectTemp\x64\Debug>mpiexec -n -l MPIProjectTemp.exe
[] , , , , , , , , , , , , , , , ,
[] , , , , , , , , , , , , , , , ,
[], , , , , , , , , , , , , , , ,
[], , , , , , , , , , , , , , , ,
[], , , , , , , , , , , , , , , ,
[], , , , , , , , , , , , , , , ,
[], , , , , , , , , , , , , , , ,
[], , , , , , , , , , , , , , , ,
MPI 并行奇偶交换排序 + 集合通信函数 Sendrecv() Sendvecv_replace()的更多相关文章
- MPI 集合通信函数 MPI_Reduce(),MPI_Allreduce(),MPI_Bcast(),MPI_Scatter(),MPI_Gather(),MPI_Allgather(),MPI_Scan(),MPI_Reduce_Scatter()
▶ 八个常用的集合通信函数 ▶ 规约函数 MPI_Reduce(),将通信子内各进程的同一个变量参与规约计算,并向指定的进程输出计算结果 ● 函数原型 MPI_METHOD MPI_Reduce( _ ...
- 【MPI】并行奇偶交换排序
typedef long long __int64; #include "mpi.h" #include <cstdio> #include <algorithm ...
- MPI 集合通信函数 MPI_Scatterv(),MPI_Gatherv(),MPI_Allgatherv(),MPI_Alltoall(),MPI_Alltoallv(),MPI_Alltoallw()
▶ 函数 MPI_Scatterv() 和 MPI_Gatherv() .注意到函数 MPI_Scatter() 和 MPI_Gather() 只能向每个进程发送或接受相同个数的元素,如果希望各进程获 ...
- 【MPI学习4】MPI并行程序设计模式:非阻塞通信MPI程序设计
这一章讲了MPI非阻塞通信的原理和一些函数接口,最后再用非阻塞通信方式实现Jacobi迭代,记录学习中的一些知识. (1)阻塞通信与非阻塞通信 阻塞通信调用时,整个程序只能执行通信相关的内容,而无法执 ...
- MPI实现并行奇偶排序
奇偶排序 odd-even-sort, using MPI 代码在 https://github.com/thkkk/odd-even-sort 使用 MPI 实现奇偶排序算法, 并且 MPI 进程 ...
- 【MPI学习6】MPI并行程序设计模式:具有不连续数据发送的MPI程序设计
基于都志辉老师<MPI并行程序设计模式>第14章内容. 前面接触到的MPI发送的数据类型都是连续型的数据.非连续类型的数据,MPI也可以发送,但是需要预先处理,大概有两类方法: (1)用户 ...
- 【MPI学习2】MPI并行程序设计模式:对等模式 & 主从模式
这里的内容主要是都志辉老师<高性能计算之并行编程技术——MPI并行程序设计> 书上有一些代码是FORTAN的,我在学习的过程中,将其都转换成C的代码,便于统一记录. 这章内容分为两个部分: ...
- 利用共享内存实现比NCCL更快的集合通信
作者:曹彬 | 旷视 MegEngine 架构师 简介 从 2080Ti 这一代显卡开始,所有的民用游戏卡都取消了 P2P copy,导致训练速度显著的变慢.针对这种情况下的单机多卡训练,MegEng ...
- oracle之集合操作函数---minus、union、intersect
集合操作符专门用于合并多条select语句的结果,包括:UNION,UNION ALL,INTERSECT,MINUS.当使用集合操作函数时,需保证数据集的字段数据类型和数目一致. 使用集合操作符需要 ...
随机推荐
- apache配置文件详解与优化
apache配置文件详解与优化 一.总结 一句话总结:结合apache配置文件中的英文说明和配置详解一起看 1.apache模块配置用的什么标签? IfModule 例如: <IfModule ...
- linux系统之间共享文件(CentOS6)
Server IP: 192.168.2.128 nfs, rpcbind(portmap) installed Client IP: 192.168.2.254 nfs, rpcbind(portm ...
- Hosts 文件的作用
问题来源: 我修改了hosts文件访问公司的内网 但是出现错误找不到服务器或DNS错误 一个下午了都上不了公司的系统. Hosts是什么?Hosts是Window系统目录里的一个文件,它的作用可大 ...
- Anatoly and Cockroaches
Anatoly lives in the university dorm as many other students do. As you know, cockroaches are also li ...
- POJ2891 Strange Way to Express Integers
题意 Language:Default Strange Way to Express Integers Time Limit: 1000MS Memory Limit: 131072K Total S ...
- c/c++指针详解(一)
一:相关概念 1.指针数组:int *p[6] 是数组,是一个存放指针的数组,也就是里面存放的是地址. 2.数组指针:int (*p)[6] ...
- hibernate enum映射详解
hibernate enum映射详解 在这里介绍注解的形式,如果想要了解XML配置的方式,可以自行查找相关资料. 例如以下Entity @Entity @Table(name = "t_us ...
- PostgreSQL性能极限
目前已有很多PostgreSQL的系统在实际生产环境下管理着超过4TB的数据.一些PostgreSQL系统的极限值如下表所列: 极限值: 最大单个数据库大小 不限 最大数据单表大小 32 TB 单条记 ...
- 写写Django中DRF框架概述以及序列化器对象serializer的构造方法以及使用
写写Django中DRF框架概述以及序列化器对象serializer的构造方法以及使用 一.了解什么是DRF DRF: Django REST framework Django REST framew ...
- java局部变量和临时变量
局部变量:temp=1, 临时变量:return a+b 临时变量会有一点的性能优势 局部变量会比成员变量和静态成员变量有优势,改进的方法是吧成员变量和静态成员变量赋值在局部变量:https://bl ...