CPU作业之MPI+OpenMp混合编程实现快速排序

欢迎关注博客

项目介绍

  • 使用MPIOpenMp混合模式实现并行快速排序

项目思路

  • 分出四个进程
  • 0号进程发送待排序数组
  • 1号和2号进程分别接收子数组,各自进行多线程的快速排序
  • 随后1号和2号进程排序后的数组发送到进程3
  • 进程3进行串行的归并排序
  • 将两个已排好的序列进行有序合并,完成混合并行快速排序

MPI和OpenMp介绍

  • 详情请看之前的博文

项目实现

自定义的库

  • 为了方便(MPIopenMPMPI混合)对于相同函数的调用,编写通用函数库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);
        }
    }
    
    • MPIOpenMp混合编程实现快速排序

      #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%
  • 对于openMPMPI混合编程,可以发现在数据规模小和巨大时,效率相对高,而在某一数据规模区间下,效率反而不高。
  • 综合来看,三者效率在不同的数据规模下,均可达到加速完成排序
  • 对于数据规模较小的情况下,三者效率相近
  • 对于数据规模中等的情况下,MPI效率远大于openMPopenMPMPI混合
  • 对于数据规模较大的情况下,openMPIMPI混合效率较其他两个有显著的优势
  • 3
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
OpenMPMPI 是两种不同的并行编程模型,可以在混合编程中一起使用。 在混合编程中,OpenMP 通常用于在单个节点上并行化程序的部分,而 MPI 用于在不同节点之间传递数据和进行通信。通过这种方式,可以利用多个节点和多个 CPU 核心的优势来加速程序的执行。 下面是一个简单的混合 OpenMPMPI 编程的示例: ```c #include <mpi.h> #include <omp.h> #include <stdio.h> int main(int argc, char *argv[]) { int rank, size, thread_id, num_threads; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); #pragma omp parallel private(thread_id, num_threads) { thread_id = omp_get_thread_num(); num_threads = omp_get_num_threads(); printf("Hello from thread %d of %d on process %d of %d\n", thread_id, num_threads, rank, size); } MPI_Finalize(); return 0; } ``` 在此示例中,我们使用 OpenMP 并行化 `printf` 语句,并使用 MPI 进行进程间通信。在每个进程上,我们使用 `omp_get_thread_num()` 和 `omp_get_num_threads()` 获取线程 ID 和线程总数,并将它们打印出来。 要编译此程序,您需要使用类似以下命令的编译器指令: ``` mpicc -fopenmp hybrid_mpi_openmp.c -o hybrid_mpi_openmp ``` 在运行程序时,您需要使用类似以下命令的命令: ``` mpirun -np 4 ./hybrid_mpi_openmp ``` 在此示例中,我们将使用 4 个进程运行程序。您可以将 `-np` 参数更改为所需的进程数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高明爱圣子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值