项目介绍
- 使用
MPI
和OpenMp
混合模式实现并行快速排序
项目思路
- 分出四个进程
- 0号进程发送待排序数组
- 1号和2号进程分别接收子数组,各自进行多线程的快速排序
- 随后1号和2号进程排序后的数组发送到进程3
- 进程3进行串行的归并排序
- 将两个已排好的序列进行有序合并,完成混合并行快速排序
MPI和OpenMp介绍
- 详情请看之前的博文
项目实现
自定义的库
-
为了方便(
MPI
,openMP
和MPI
混合)对于相同函数的调用,编写通用函数库quickSort.h
原码:
#include "omp.h" //随机创建数组 void rands(int *data,int sum); //交换函数 void sw(int *a, int *b); //求2的n次幂 int exp2(int wht_num); //求log2(n) int log2(int wht_num); //合并两个有序的数组 void mergeList(int *c,int *a, int sta1, int end1, int *b, int sta2, int end2); //串行快速排序 int partition(int *a, int sta, int end); void quickSort(int *a, int sta, int end); //openMP(8)并行快速排序 void quickSort_parallel(int* array, int lenArray, int numThreads); void quickSort_parallel_internal(int* array, int left, int right, int cutoff); void rands(int *data, int sum){ int i; for (i = 0; i < sum; i++){ data[i] = rand() % 100000000; } } void sw(int *a, int *b){ int t = *a; *a = *b; *b = t; } int exp2(int wht_num){ int wht_i; wht_i=1; while(wht_num>0){ wht_num--; wht_i=wht_i*2; } return wht_i; } int log2(int wht_num){ int wht_i, wht_j; wht_i=1; wht_j=2; while(wht_j<wht_num){ wht_j=wht_j*2; wht_i++; } if(wht_j>wht_num) wht_i--; return wht_i; } int partition(int *a, int sta, int end){ int i = sta, j = end + 1; int x = a[sta]; while (1){ while (a[++i] < x && i < end); while (a[--j] > x); if (i >= j) break; sw(&a[i], &a[j]); } a[sta] = a[j]; a[j] = x; return j; } //并行openMP排序 void quickSort_parallel(int *a, int lenArray, int numThreads){ int cutoff = 1000; #pragma omp parallel num_threads(numThreads) //指定线程数的数量 { #pragma omp single //串行执行 { quickSort_parallel_internal(a, 0, lenArray - 1, cutoff); } } } void quickSort_parallel_internal(int *a, int left, int right, int cutoff){ int i = left, j = right; int tmp; int pivot = a[(left + right) / 2]; //进行数组分割,分成两部分(符合左小右大) while (i <= j) { while (a[i] < pivot) i++; while (a[j] > pivot) j--; if (i <= j){ tmp = a[i]; a[i] = a[j]; a[j] = tmp; i++; j--; } } //int j = partition(a,left,right); if (((right - left) < cutoff)){ if (left < j){ quickSort_parallel_internal(a, left, j, cutoff); } if (i < right){ quickSort_parallel_internal(a, i, right, cutoff); } } else{ #pragma omp task /* task是“动态”定义任务的,在运行过程中, 只需要使用task就会定义一个任务, 任务就会在一个线程上去执行,那么其它的任务就可以并行的执行。 可能某一个任务执行了一半的时候,或者甚至要执行完的时候, 程序可以去创建第二个任务,任务在一个线程上去执行,一个动态的过程 */ //对两部分再进行并行的线程排序 { quickSort_parallel_internal(a, left, j, cutoff); } #pragma omp task { quickSort_parallel_internal(a, i, right, cutoff); } } } //合并两个已排序的数组 void mergeList(int *c,int *a, int sta1, int end1, int *b, int sta2, int end2){ int a_index = sta1; // 遍历数组a的下标 int b_index = sta2; // 遍历数组b的下标 int i = 0; // 记录当前存储位置 //int *c; //c = (int *)malloc(sizeof(int) * (end1 - sta1 + 1 + end2 - sta2 + 1)); while (a_index < end1 && b_index < end2){ if (a[a_index] <= b[b_index]){ c[i] = a[a_index]; a_index++; } else{ c[i] = b[b_index]; b_index++; } i++; } while (a_index < end1){ c[i] = a[a_index]; i++; a_index++; } while (b_index < end2){ c[i] = b[b_index]; i++; b_index++; } } //串行快速排序 void quickSort(int *a, int sta, int end){ if (sta < end){ //printf("3\n"); int mid = partition(a, sta, end); quickSort(a, sta, mid - 1); quickSort(a, mid + 1, end); } }
-
MPI
和OpenMp
混合编程实现快速排序#include <stdio.h> #include <mpi.h> #include <omp.h> #include <time.h> #include <stdlib.h> #include "quickSort.h" //数组长度 const int n = 10000000; //串行和并行时间变量 double whi_sta,whi_end; double sta_time2,end_time2; int num = 0; //线程openmp对子数组进行并行快排 void QuickSort(int *a,int sta,int end){ quickSort_parallel(a,end-sta+1,8); } //并行排序 void para_quickSort(int *data,int sta,int end,int whi_m,int id,int now_id){ int whi_r,whi_j,whi_i; int MyLength = -1; int *tmp,*tmp1,*tmp2,*tmp3,*tmp4,*tmp5,*tmp6; MPI_Status status; //将进程0的数组分两半,一半到进程1,一半到进程2 if(now_id == 0){ whi_sta = MPI_Wtime(); whi_r = partition(data,sta,end); MyLength = end - whi_r+1; //将要发散的数组长度发散到指定进程 MPI_Send(&whi_r,1,MPI_INT,1,0,MPI_COMM_WORLD); MPI_Send(&MyLength,1,MPI_INT,2,0,MPI_COMM_WORLD); MPI_Send(&whi_r,1,MPI_INT,3,4,MPI_COMM_WORLD); MPI_Send(&MyLength,1,MPI_INT,3,5,MPI_COMM_WORLD); //将要发散的数组元素发散到指定进程 MPI_Send(data,whi_r,MPI_INT,1,0,MPI_COMM_WORLD); MPI_Send(data+whi_r+1,MyLength,MPI_INT,2,0,MPI_COMM_WORLD); } if(now_id == 2){ //接收来自进程0的数组长度 MPI_Recv(&MyLength,1,MPI_INT,0,0,MPI_COMM_WORLD,&status); //接收来自进程0的数组元素 tmp1 = (int*)malloc(sizeof(int)*MyLength); MPI_Recv(tmp1,MyLength,MPI_INT,0,0,MPI_COMM_WORLD,&status); //对其进行多线程的快速排序 QuickSort(tmp1,0,MyLength-1); //将排好序的子数组发送到进程3 MPI_Send(tmp1,MyLength,MPI_INT,3,2,MPI_COMM_WORLD); } if(now_id == 1){ MPI_Recv(&whi_r,1,MPI_INT,0,0,MPI_COMM_WORLD,&status); tmp2 = (int*)malloc(sizeof(int)*whi_r); MPI_Recv(tmp2,whi_r,MPI_INT,0,0,MPI_COMM_WORLD,&status); QuickSort(tmp2,0,whi_r-1); MPI_Send(tmp2,whi_r,MPI_INT,3,1,MPI_COMM_WORLD); } if(now_id == 3){ //接收来自进程0的划分的子数组的长度 MPI_Recv(&MyLength,1,MPI_INT,0,5,MPI_COMM_WORLD,&status); MPI_Recv(&whi_r,1,MPI_INT,0,4,MPI_COMM_WORLD,&status); //接收来自进程1和2的排好序的子数组元素 tmp3 = (int*)malloc(sizeof(int)*whi_r); tmp4 = (int*)malloc(sizeof(int)*MyLength); tmp5 = (int*)malloc(sizeof(int)*(MyLength+whi_r)+1); MPI_Recv(tmp3,whi_r,MPI_INT,1,1,MPI_COMM_WORLD,&status); MPI_Recv(tmp4,MyLength,MPI_INT,2,2,MPI_COMM_WORLD,&status); //合并两个子数组 mergeList(tmp5,tmp3,tmp4,whi_r,MyLength); // printf("----------\n"); // printf("The final array data1: \n"); // //print(tmp5,whi_r+MyLength); // printf("%d\n",whi_r+MyLength); // printf("----------\n\n"); } } int main(int argc,char *argv[]) { int *data1,*data2; int now_id,sum_id; int whi_m,whi_r; //启动mpi MPI_Init(&argc,&argv); /*确定自己的进程标志符MyID*/ MPI_Comm_rank(MPI_COMM_WORLD,&now_id); /*组内进程数是SumID*/ MPI_Comm_size(MPI_COMM_WORLD,&sum_id); double whi_n; if(now_id == 0){ whi_n = n; data1 = (int*)malloc(sizeof(int)*whi_n+1); data2 = (int*)malloc(sizeof(int)*whi_n+1); rands(data1,n); rands(data2,n); } whi_m = sum_id; MPI_Bcast(&whi_n,1,MPI_INT,0,MPI_COMM_WORLD); /* openmp+mpi混合并行排序, 四个进程, 进程0广播data1数组分开的两部分到进程1和2 进程1和2采用多线程进行快速排序 线程1和2将各自排好序的数组Send到进程3 进程3采用归并排序,合并两子数组 最后覆盖掉进程0 得到排好序的数组 */ para_quickSort(data1,0,whi_n-1,whi_m,0,now_id); whi_end = MPI_Wtime(); //串行快排 if(now_id == 0){ sta_time2 = MPI_Wtime(); quickSort(data2,0,n-1); end_time2 = MPI_Wtime(); printf("串行时间 = %f s\n",end_time2-sta_time2); printf("并行时间 = %f s\n\n",whi_end-whi_sta); // printf("----------\n"); // printf("The final array data1: \n"); // print(data1,n); // printf("----------\n"); // printf("----------\n"); // printf("The final array data2: \n"); // print(data2,n); // printf("----------\n"); } MPI_Finalize(); }
-
性能分析
三种方式的比较
- 三种
CPU
并行处理各具特色,首先我们看到三种方式效率均大于0%
- 对于
openMP
,在规定线程块为8
下,效率随数据规模增大程度不明显,比较稳定在200%~500%
- 对于
MPI
,在规定进程为8
下,效率随数据规模先增大后减小,效率在测试数据规模为50万时甚至可以达到10000%
- 对于
openMP
和MPI
混合编程,可以发现在数据规模小和巨大时,效率相对高,而在某一数据规模区间下,效率反而不高。 - 综合来看,三者效率在不同的数据规模下,均可达到加速完成排序
- 对于数据规模较小的情况下,三者效率相近
- 对于数据规模中等的情况下,
MPI
效率远大于openMP
,openMP
和MPI
混合 - 对于数据规模较大的情况下,
openMPI
和MPI
混合效率较其他两个有显著的优势