并行归并排序在程序开始时,会将n/comm_comm个键值分配给每个进程,程序结束时,所有的键值会按顺序存储在进程0中。为了做到这点,它使用了树形结构通信模式。当进程接收到另一个进程的键值时,它将该键值合并进自己排序的键值列表中。编写一个程序实现归并排序。进程0应该读入n的值,将其广播给其余进程。每个进程需要使用随机数生成器来创建n/comm_sz的局部int型数据列表。每个进程先排序各自的局部列表,然后进程0收集并打印这些局部列表。然后,这些进程使用树形结构通信合并全局列表给进程0,并打印最终结果。

  

如图,一般情况下,应由进程读入数据,然后一层一层的传到每个进程,每个进程排序完成之后,由上一层的进程进行合并。我这次做的只是输入数n,由每个进程产生n/comm_sz个随机数。

这里有一个问题,如何确定一个进程的父节点和子节点?

  你最初可能认为让在处理树中的每个节点是一个单独的进程中。这样,你可以简单地从二叉堆借用一个想法任何父节点的左子节点的下标2* K+1,右子节点的下标是2* K+2,一个节点的父节点是(K-1 )/ 2,这在一个完全二叉树中确定了父子关系。因此,一个内部节点将数据分成两半,并发送给每个子进程进行处理。这样叶节点,只是做了排序,内部节点等待然后从两个子节点接收回数据,执行两半的合并,以及(对于所有内部节点但非根节点本身)将结果发送到父节点。

  但是这样带来一个问题,就是当叶节点进行排序时,父节点在等待子节点,没有理由让父节点空闲等待子节点进行排序,我们希望让父节点充当左子节点进行工作,如图:
  这样每个节点将数据分成两半,每个节点自己处理数据的左半,右子节点处理数据的右半。这样之后你就要确定每个节点的父节点和子节点,你可以根据树形通信结构来确定每个节点的父节点和子节点。
  我们要先确定每个节点在树形通信结构的高度,叶节点的高度为0,这样根节点0的高度是3。节点0需要与节点4进行通信,对于如何计算出节点0的右子节点是4,可以通过将1转化成二进制并左移节点0所在的高度3减1位,即(myRank|(1<<2)),myRank表示节点的编号,这里是0,这样就可以计算出高度为3的节点0的右子节点。下一步节点0需要与节点2通信,节点4需要与节点6通信,此时节点0和4的高度为2,因此需要计算他们的子节点时通过公式((myRank|(1<<1)),这样计算出节点0的右子节点是2,节点4的右子节点时6,一般化的公式是(myRank|(myHeight-1))。
  计算完每个节点的子节点后,还要计算每个节点的父节点,每个节点的父节点可以通过如下公式进行计算,myRank&~(1<<myHeight)。
  下面是一个小的demo来展示通信过程,他展示了高度为3的通信树节点之间的通信过程,

 #include <stdio.h>

 void communicate ( int myHeight, int myRank )
{ int parent = myRank & ~(<<myHeight); if ( myHeight > )
{ int nxt = myHeight - ;
int rtChild = myRank | ( << nxt ); printf ("%d sending data to %d\n", myRank, rtChild);
communicate ( nxt, myRank );
communicate ( nxt, rtChild );
printf ("%d getting data from %d\n", myRank, rtChild);
}
if ( parent != myRank )
printf ("%d transmitting to %d\n", myRank, parent);
} int main ( void )
{ int myHeight = , myRank = ; printf ("Building a height %d tree\n", myHeight);
communicate(myHeight, myRank);
return ;
}

下面是输出结果

Building a height 3 tree
0 sending data to 4
0 sending data to 2
0 sending data to 1
1 transmitting to 0
0 getting data from 1
2 sending data to 3
3 transmitting to 2
2 getting data from 3
2 transmitting to 0
0 getting data from 2
4 sending data to 6
4 sending data to 5
5 transmitting to 4
4 getting data from 5
6 sending data to 7
7 transmitting to 6
6 getting data from 7
6 transmitting to 4
4 getting data from 6
4 transmitting to 0
0 getting data from 4

了解了通信结构之后,下面是mpi_merge_sort.c文件,利用MPI写的归并排序,

 #include<stdlib.h>
#include<stdio.h>
#include<time.h>
#include<math.h>
#include<string.h>
#include<mpi.h>
//读取输入的要排序的数的个数,并计算每个进程需要排序的个数
void read_n(int* n,int my_rank,MPI_Comm comm,int comm_sz,int* size);
//根据节点在通信树中的最高度为数组分配空间
void allocate_arrays(int** left,int** right,int** merge,int size,int height,int* myHeight,int my_rank);
//在left数组中生成size个随机数
void randnum(int* left,int size,int my_rank);
//以下三个函数时用于在通信树的叶节点对生成在left中的size个随机数进行快速排序
void swap(int *a,int *b);
int partition(int left[],int lo,int hi);
void QuickSort(int left[], int lo, int hi);
//父节点将接收子节点的数据与自己节点的数据进行归并
void merge_sort(int* left,int* right,int* merge,int size);
//控制通信,确定从哪个子节点中接收数据或向哪个父节点发送数据
void communicate ( int Height, int my_rank,int size,int* right,int* left,int* merge,MPI_Comm comm); int main(int argc,char* argv[]){
int n,comm_sz,my_rank;
int* left;
int* right;
int* merge;
int size;
int myHeight=;
MPI_Comm comm;
int height=; MPI_Init(NULL,NULL);
comm=MPI_COMM_WORLD;
MPI_Comm_size(comm,&comm_sz);
MPI_Comm_rank(comm,&my_rank);
read_n(&n,my_rank,comm,comm_sz,&size);
int tt=comm_sz;
for(int i=;i<comm_sz;i++){
tt=tt/;
if(tt==)
break;
height++;
}
allocate_arrays(&left,&right,&merge,size,height,&myHeight,my_rank);
randnum(left,size,my_rank);
QuickSort(left,,size-); communicate(height,my_rank,size,right,left,merge,comm);
if(my_rank==){
if(height%!=)
for(int i=;i<n;i++)
printf("%d ",merge[i]);
else
for(int i=;i<n;i++)
printf("%d ",left[i]);
printf("\n");
}
MPI_Finalize();
free(left);
free(right);
free(merge); }
void read_n(int* n,int my_rank,MPI_Comm comm,int comm_sz,int* size){
if(my_rank==){
printf("please intput the number of number\n");
scanf("%d",n);
}
MPI_Bcast (n,,MPI_INT,,comm);
*size=*n/comm_sz;
}
void allocate_arrays(int** left,int** right,int** merge,int size,int height,int* myHeight,int my_rank){
for(int i=;i<height;i++){
int parent=my_rank&~(<<i);
if(parent!=my_rank)
break;
(*myHeight)++;
}
*left=malloc((<<(*myHeight))*size*sizeof(int)); *right=malloc((<<(*myHeight-))*size*sizeof(int));
*merge=malloc((<<(*myHeight))*size*sizeof(int)); }
void randnum(int* left,int size,int my_rank){
srand(my_rank);
for(int i=;i<size;i++)
left[i]=rand();
}
void swap(int *a,int *b)
{
int temp=*a;
*a=*b;
*b=temp;
}
int partition(int* left,int lo,int hi)
{
int key=left[hi];
int i=lo-;
for(int j=lo;j<hi;j++)
{
if(left[j]<=key)
{
i=i+;
swap(&left[i],&left[j]);
}
}
swap(&left[i+],&left[hi]);
return i+;
}
void QuickSort(int *left, int lo, int hi)
{
if (lo<hi)
{
int k = partition(left, lo, hi);
QuickSort(left, lo, k-);
QuickSort(left, k+, hi);
}
}
void merge_sort(int* left,int* right,int* merge,int size){ int lp=,rp=,mp=;
for(int i=;i<*size;i++){
if(left[lp]<=right[rp]){
merge[mp]=left[lp];
lp++;
mp++;
}else{
merge[mp]=right[rp];
rp++;
mp++;
}
if(lp==size||rp==size)
break;
}
if(lp==size){
memcpy(&merge[mp],&right[rp],(size-rp)*sizeof(int));
}else{
memcpy(&merge[mp],&left[lp],(size-lp)*sizeof(int));
}
}
void communicate ( int Height, int my_rank,int size,int* right,int* left,int* merge,MPI_Comm comm)
{
for(int i=;i<=Height;i++){
int parent=my_rank&~(<<i);
if(parent==my_rank){
int rtChild=my_rank|(<<i); MPI_Recv(right,size,MPI_INT,rtChild,,comm,MPI_STATUS_IGNORE); merge_sort(left,right,merge,size); int* temp;
temp=left;
left=merge;
merge=temp;
size*=; }else{
MPI_Send(left,size,MPI_INT,parent,,comm);
break; }
} }

使用 mpicc -g -Wall -std=c99 -o mpi_merge_sort mpi_merge_sort.c 进行编译

使用 mpirun -n ./mpi_merge_sort

注意输入的进程个数一定是2的整数次幂,且输入的要排序数的个数要能整除进程个数

下面是一次的运行结果:

please intput the number of number
12
190686788    483147985    844158168    846930886    846930886    1205554746    1505335290    1681692777    1681692777    1738766719    1804289383    1804289383

参考资料:http://penguin.ewu.edu/~trolfe/ParallelMerge/ParallelMerge.html

并行归并排序——MPI的更多相关文章

  1. 疯狂的Java算法——插入排序,归并排序以及并行归并排序

    从古至今的难题 在IT届有一道百算不厌其烦的题,俗称排序.不管是你参加BAT等高端笔试,亦或是藏匿于街头小巷的草根笔试,都会经常见到这样一道百年难得一解的问题. 今天LZ有幸与各位分享一下算法届的草根 ...

  2. 典型分布式系统分析:Bigtable

    本文是典型分布式系统分析的第三篇,分析的是Bigtable,一个结构化的分布式存储系统. Bigtable作为一个分布式存储系统,和其他分布式系统一样,需要保证可扩展.高可用与高性能.与此同时,Big ...

  3. [Distributed ML] Yi WANG's talk

    王益,分布式机器学习的践行者,他的足迹值得后来者学习. 膜拜策略: LinkedIn高级分析师王益:大数据时代的理想主义和现实主义(图灵访谈)[心路历程] 分布式机器学习的故事-王益[历史由来] 分布 ...

  4. C++程序中调用MPI并行的批处理命令

    问题来源:在使用MPI时,将程序并行实现了,运行时需要在dos窗口下输入批处理命令,以完成程序的执行. 如:mpiexec -localroot -n 6 d:/mpi/pro.exe 但每次这样挺麻 ...

  5. 【MPI学习7】MPI并行程序设计模式:MPI的进程组和通信域

    基于都志辉老师MPI编程书中的第15章内容. 通信域是MPI的重要概念:MPI的通信在通信域的控制和维护下进行 → 所有MPI通信任务都直接或间接用到通信域这一参数 → 对通信域的重组和划分可以方便实 ...

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

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

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

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

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

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

  9. 《并行程序设计导论》——MPI(Microsoft MPI)(1):Hello

    =================================版权声明================================= 版权声明:原创文章 禁止转载  请通过右侧公告中的“联系邮 ...

随机推荐

  1. luogu P1182 数列分段Section II

    题目描述 对于给定的一个长度为N的正整数数列A[i],现要将其分成M(M≤N)段,并要求每段连续,且每段和的最大值最小. 关于最大值最小: 例如一数列4 2 4 5 1要分成3段 将其如下分段: [4 ...

  2. SQL 统计某一列出现的总和

    现有数据如上图所示,要求统计出日期相同的Count总数,并且加一列统计前面日期Count的总和 SELECT SUM([Count]) AS DayTotal, SUM(SUM([Count])) o ...

  3. XTU | 人工智能入门复习总结

    写在前面 本文严禁转载,只限于学习交流. 课件分享在这里了. 还有人工智能标准化白皮书(2018版)也一并分享了. 绪论 人工智能的定义与发展 定义 一般解释:人工智能就是用 人工的方法在 **机器( ...

  4. 集合框架(06)Arrays

    Arrays Arrays:用于操作数组的工具类,里面都是静态方法 ---数组变集合 1.asList:将数组变成List集合 把数组变成list集合的好处?可以使用集合的思想和方法来操作数组中的元素 ...

  5. JAVA和.NET工作流相关项目收集

    .NET以自主实现为主, 暂未发现使用WWF框架开发的开源工作流,    java以BPM系为主 . .NET: RoadFlow : http://www.cqroad.cn/ 使用了百度编辑器扩展 ...

  6. mysql系列-安装及服务启动

    一.window下的安装 详细见官网 https://dev.mysql.com/doc/refman/5.7/en/windows-installation.html 以 MySQL 5.1 免安装 ...

  7. iOS开发15:自定义UITableViewCell

    上篇文章介绍了如何用UITableView显示表格,并讲了几种UITableViewCell的风格.不过有时候我们需要自己定义 UITableViewCell的风格,其实就是向行中添加子视图.添加子视 ...

  8. top显示的内存各项参数解析

    top观察进程数据时,会有如下几个内存参数(可以用f选择显示哪些参数): VIRT, RES, SHR, SWAP, CODE, DATA top实际上是从/proc/<pid>/stat ...

  9. ieda常用快捷键

    Ctrl+Shift + Enter,语句完成“!”,否定完成,输入表达式时按 “!”键Ctrl+E,最近的文件Ctrl+Shift+E,最近更改的文件Shift+Click,可以关闭文件Ctrl+[ ...

  10. wp8开发时模拟器无法联网解决方法

    关于模拟器无法联网的正常解决方案在网上有很多 这里讲的是我在做测试的时候模拟器无法上网的特殊情况 由于使用的是无线网络 可能有一些差别 过程如图: 启动模拟器 如果之前没有设置过模拟器的交换器则会出现 ...