▶ 《并行程序设计导论》第三章的例子程序

● 代码

 #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()的更多相关文章

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

    ▶ 八个常用的集合通信函数 ▶ 规约函数 MPI_Reduce(),将通信子内各进程的同一个变量参与规约计算,并向指定的进程输出计算结果 ● 函数原型 MPI_METHOD MPI_Reduce( _ ...

  2. 【MPI】并行奇偶交换排序

    typedef long long __int64; #include "mpi.h" #include <cstdio> #include <algorithm ...

  3. MPI 集合通信函数 MPI_Scatterv(),MPI_Gatherv(),MPI_Allgatherv(),MPI_Alltoall(),MPI_Alltoallv(),MPI_Alltoallw()

    ▶ 函数 MPI_Scatterv() 和 MPI_Gatherv() .注意到函数 MPI_Scatter() 和 MPI_Gather() 只能向每个进程发送或接受相同个数的元素,如果希望各进程获 ...

  4. 【MPI学习4】MPI并行程序设计模式:非阻塞通信MPI程序设计

    这一章讲了MPI非阻塞通信的原理和一些函数接口,最后再用非阻塞通信方式实现Jacobi迭代,记录学习中的一些知识. (1)阻塞通信与非阻塞通信 阻塞通信调用时,整个程序只能执行通信相关的内容,而无法执 ...

  5. MPI实现并行奇偶排序

    奇偶排序 odd-even-sort, using MPI 代码在 https://github.com/thkkk/odd-even-sort 使用 MPI 实现奇偶排序算法, 并且 MPI 进程 ...

  6. 【MPI学习6】MPI并行程序设计模式:具有不连续数据发送的MPI程序设计

    基于都志辉老师<MPI并行程序设计模式>第14章内容. 前面接触到的MPI发送的数据类型都是连续型的数据.非连续类型的数据,MPI也可以发送,但是需要预先处理,大概有两类方法: (1)用户 ...

  7. 【MPI学习2】MPI并行程序设计模式:对等模式 & 主从模式

    这里的内容主要是都志辉老师<高性能计算之并行编程技术——MPI并行程序设计> 书上有一些代码是FORTAN的,我在学习的过程中,将其都转换成C的代码,便于统一记录. 这章内容分为两个部分: ...

  8. 利用共享内存实现比NCCL更快的集合通信

    作者:曹彬 | 旷视 MegEngine 架构师 简介 从 2080Ti 这一代显卡开始,所有的民用游戏卡都取消了 P2P copy,导致训练速度显著的变慢.针对这种情况下的单机多卡训练,MegEng ...

  9. oracle之集合操作函数---minus、union、intersect

    集合操作符专门用于合并多条select语句的结果,包括:UNION,UNION ALL,INTERSECT,MINUS.当使用集合操作函数时,需保证数据集的字段数据类型和数目一致. 使用集合操作符需要 ...

随机推荐

  1. Spring Boot安装

    Spring引导依赖项groupId使用org.springframework.boot. 通常Maven POM文件将继承自spring-boot-starter-parent项目,并将依赖性声明为 ...

  2. 使用80percent开发rails程序:gem的了解。(kaminari)

    学习目的: 对一些主要的gem进行学习了解基本功能: 作者的一些答复:(链接) 关于安全配置: 对于配置文件, 安全仅有一点: 不要提交任何敏感信息到服务端. 所以 rails-template 是添 ...

  3. Android之第三方平台实现多平台分享操作

    开发中常常遇到分享操作,当用到多种分享时,如:QQ,微信,微博,短信等,可以借助第三方平台来完成,此博客主要借助mob平台来完成相关操作,当然也可以借助其他平台,如友盟等. 先来看看效果图: 如图看出 ...

  4. bzoj2330: [SCOI2011]糖果 差分约束系统

    幼儿园里有N个小朋友,lxhgww老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果.但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候 ...

  5. AppCrawler自动化遍历使用详解(版本2.1.0 )

    AppCrawle是自动遍历的app爬虫工具,最大的特点是灵活性,实现:对整个APP的所有可点击元素进行遍历点击.   优点: 1.支持android和iOS, 支持真机和模拟器 2.可通过配置来设定 ...

  6. javascript中new Date()会存在偏差一小时的bug

    事件回顾: 因为我们的产品会有与时间转换这部分,并且流量主要集中在小程序. emmm~  获取用户出生的年/月/日/时  我们和后台协商的是换算用户选择后的时间为  年/月/日/时/分/秒  所以我们 ...

  7. ipvsadm命令介绍

    为了更好的让大家理解这份命令手册,将手册里面用到的几个术语先简单的介绍一下: 1,virtual-service-address:是指虚拟服务器的ip 地址 2,real-service-addres ...

  8. PyQt4 HardwareManager

    # PyQt4 HardwareManager # 声明: # 本软件主要是由于朋友说想要一个产品缺陷记录软件,主要用于记录产品缺陷, # 通过产品序列号进行插入.查询,本来想用VC++ 6.0做,但 ...

  9. MyBatis_Study_004(动态代理)

    源码:https://github.com/carryLess/mbtsstd-004 0.readme 基于前几篇:dao的实现类基本煤气到什么作用 仅仅是通过SQLSession的相应API定位到 ...

  10. JavaScript异步编程__“回调地狱”的一些解决方案

    异步编程在JavaScript中非常重要.过多的异步编程也带了回调嵌套的问题,本文会提供一些解决“回调地狱”的方法. setTimeout(function () { console.log('延时触 ...