CPU作业之MPI实现快速排序

欢迎关注博客

项目介绍

  • 使用MPI实现多进程并行快速排序

MPI简介

  1. 什么是MPI

    OpenMP并行程序不同,MPI是一种基于信息传递的并行编程技术。消息传递接口是一种编程接口标准,而不是一种具体的编程语言。简而言之,MPI标准定义了一组具有可移植性的编程接口

    • Point-to-point communication
    • Collective operations
    • Process groups, Communication contexts, and Process topologies
    • Environmental management and inquiry
    • Process creation and management
    • Other functions
  2. MPI组成

    • 数据类型

      定义了精确的数据类型参数而不使用字节计数,以数据类型为单位指定消息的长度;对于CFortran,MPI均预定义了一组数据类型和一些附加的数据类型;可以发送 或接收连续的数据,还可以处理不连续的数据;允许发送和接收不同的数据类型

    • 通信域

      MPI预定义的通信域:mpi comm world(包含所有进程)、mpi comm self(只包含各个进程自己的进程组)

  3. 编程详解

    #include "mpi.h" //Header File
    int main (int argc, char *argv[]){
        MPI_Init (&argc, &argv); //Initialization
        MPI_Comm_size (COMM, &p);//Set the number of processes
        MPI_Comm_rank (COMM, &id);//id represents the current process number
        /*
       		Communicate & Compute;
       	*/
        MPI_Finalize( );
         return 0;
    }
    
  4. MPI 基本函数

    • Getting Communicator Information

      //Get the rank of the calling process in group
      int MPI_Comm_rank(MPI_Comm comm, int *rank);
      //Get the size in a communicator
      int MPI_Comm_size(MPI_Comm comm, int *size);
      
      

      例子:

      #include <stdio.h>
      #include "mpi.h"
      int main(int argc, char**argv){
        MPI_Init(&argc, &argv);
        printf("Hello world.\n");
        MPI_Finalize();
        return 0;
      }
      

      运行:

      $mpicc basic.c -o basic //编译
      $mpirun -np 3 ./basic > basic.txt  //设置进程数为3,linux重定向结果输出到basic.txt 
      

      输出:

      Hello world.
      Hello world.
      Hello world.
      
    • Point to Point communication

      //发送消息	Send a message
      MPI_Send( 
          void* data,	//starting address of the data to be sent
          int count,	//number of elements to be sent (not bytes)
          MPI_Datatype datatype,	//MPI datatype of each element
          int destination,	//rank of destination process
          int tag,	//message identifier (set by user)
          MPI_Comm comm	//MPI communicator of processors involved
      )
      
      //接受消息 Receive a message
      MPI_Recv(
          void* data,		//starting address of buffer to store message
          int count,		//number of elements to be received (not bytes)
          MPI_Datatype datatype,		//MPI datatype of each element
          int source,		//rank of source process
          int tag,		//message identifier (set by user)
          MPI_Comm comm,		//MPI communicator of processors involved
          MPI_Status* status		//structure of information about the message
      )
      
      

      例子:

      int main(int argc, char **argv){
        MPI_Comm comm = MPI_COMM_WORLD;
        MPI_Status status;  int size, rank;  char str[100];
        MPI_Init(&argc, &argv);
        MPI_Comm_size(comm, &size);  MPI_Comm_rank(comm, &rank);
        if (rank == 0)  {
           strcpy(str, "hello world");
           printf("Process 0 send 1 to process %s.\n", str);
           MPI_Send(str, strlen(str) + 1, MPI_CHAR, 1, 99, comm);
        }
        else if (rank == 1)  {
           MPI_Recv(str, 100, MPI_CHAR, 0, 99, comm, &status);
           printf("Process 1 receives messages %s.\n", str);
        }
        MPI_Finalize();
        return 0;
      }
      

      输出:

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nrk1govP-1627008119456)(C:\Users\86176\AppData\Roaming\Typora\typora-user-images\image-20210722171227625.png)]

    • Collective Communication

      Broadcast copies data from the memory of one processor to that of other processors —— One to all operation

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HDfXO1w7-1627008119459)(C:\Users\86176\AppData\Roaming\Typora\typora-user-images\image-20210722171458642.png)]

      int MPI_Bcast(
        void* buffer,		//starting address of buffer
         int count,		//number of entries in buffer
         MPI_Datatype datatype,		//data type of buffer
         int root,		//rank of broadcast root
         MPI_Comm comm		//communicator
      )
      
      

      例子:

      int main(int argc, char** argv){
         int arr[3], i, rank;
         MPI_Init(&argc, &argv);
         MPI_Comm_rank(MPI_COMM_WORLD, &rank);
         if (rank == 0){
           for (i = 0; i < 3; i++)
              arr[i] = i + 1;
         }
         MPI_Bcast(arr, 3, MPI_INT, 0, MPI_COMM_WORLD);
         printf("Process %d receives:", rank);
         for (i = 0; i < 3; i++)
            printf("%d ", arr[i]);
         putchar('\n');
         MPI_Finalize();
         return 0;
      }
      

      输出:

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kG1yOWLP-1627008119460)(C:\Users\86176\AppData\Roaming\Typora\typora-user-images\image-20210722171945635.png)]

      Gather copies data from each process to one process, where it is stored in rank order—— One to all operation

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dQEz0ejE-1627008119461)(C:\Users\86176\AppData\Roaming\Typora\typora-user-images\image-20210722172032364.png)]

int MPI_Gather(
  const void* sendbuf,		//starting address of send buffer
  int sendcount,		//number of elements in send buffer
  MPI_Datatype sendtype,		//data type of send buffer elements
  void* recvbuf, 		//address of receive buffer (significant only at root)
  int recvcount,		//number of elements for any single receive (significant only at root)
  MPI_Datatype recvtype, 		//data type of recv buffer elements(significant only at root)
   int root,		//rank of receiving process
  MPI_Comm comm		//communicator
)

例子:

int main(int argc, char **argv){
   int rank, size, sbuf[3], *rbuf, i;
   MPI_Init(&argc, &argv);
   MPI_Comm_size(MPI_COMM_WORLD, &size);
   MPI_Comm_rank(MPI_COMM_WORLD, &rank);
   for (i = 0; i < 3; i++)      sbuf[i] = rank * 10 + i;
   if (rank == 0)     rbuf = (int*)malloc(sizeof(int) * 3 * size);
   MPI_Gather(sbuf, 3, MPI_INT, rbuf, 3, MPI_INT, 0, MPI_COMM_WORLD);
   if (rank == 0){
      printf("Process 0 receives:");
      for (i = 0; i < size * 3; i++)
        printf("%d ", rbuf[i]);
      putchar('\n');
   }
   MPI_Finalize();
   return 0;
}

输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d4CZEcPn-1627008119462)(C:\Users\86176\AppData\Roaming\Typora\typora-user-images\image-20210722172256838.png)]

MPI实现快排

设计思路

  • 利用多进程设计进行并行快速排序,需要log2(n)个进程
  • 其中0号进程进行初始分块,调用partition()函数将数组分成两部分
  • 将后一半调用MPI_Send()将数据发送到2^n-1线程;
  • 随后将进程分成对应的发送进程和接收进程两部分
  • 最后,对每个进程进行串行快速排序;
  • 随后从各进程收集结果,完成并行排序。

自定义的库

  • 为了方便(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);
        }
    }
    
  • MPI实现快速排序

    #include <stdio.h>
    #include "mpi.h"
    #include <time.h>
    #include <stdlib.h>
    #include "quickSort.h"
    #define n 10000000
    
    double whi_sta,whi_end;
    
    //并行排序
    void para_quickSort(int *data,int sta,int end,int whi_m,int id,int now_id){
        //printf("1\n");
        int whi_r,whi_j,whi_i;
        int MyLength = -1;
        int *tmp;
        MPI_Status status;
        //可剩处理器为0,进行串行快速排序排序
        if(whi_m == 0){
            whi_sta = MPI_Wtime();
            if(now_id == id){
                quickSort(data,sta,end);  
            }
            return ;
        }
        //由当前处理器进行分块
        if(now_id == id){
            whi_r = partition(data,sta,end);
            MyLength = end - whi_r;
            MPI_Send(&MyLength,1,MPI_INT,id+exp2(whi_m-1),now_id,MPI_COMM_WORLD);
            if(MyLength != 0){
    	MPI_Send(data+whi_r+1,MyLength,MPI_INT,id+exp2(whi_m1),now_id,MPI_COMM_WORLD);
            }
        }
        if(now_id == id+exp2(whi_m-1)){
            MPI_Recv(&MyLength,1,MPI_INT,id,id,MPI_COMM_WORLD,&status);
     
            if(MyLength != 0){
                tmp = (int*)malloc(sizeof(int)*MyLength);
    
                MPI_Recv(tmp,MyLength,MPI_INT,id,id,MPI_COMM_WORLD,&status);
            }
        }
        whi_j = whi_r-1-sta;
        MPI_Bcast(&whi_j,1,MPI_INT,id,MPI_COMM_WORLD);
        if(whi_j > 0){
            para_quickSort(data,sta,whi_r-1,whi_m-1,id,now_id);
        }
        whi_j = MyLength;
        MPI_Bcast(&whi_j,1,MPI_INT,id,MPI_COMM_WORLD);
        if(whi_j > 0){
            para_quickSort(tmp,0,MyLength-1,whi_m-1,id+exp2(whi_m-1),now_id);
        }
        if(now_id == id+exp2(whi_m-1)&&MyLength != 0){
            MPI_Send(tmp,MyLength,MPI_INT,id,id+exp2(whi_m-1),MPI_COMM_WORLD);
        }
        if((now_id == id) && (MyLength != 0))
            MPI_Recv(data+whi_r+1,MyLength,MPI_INT,id+exp2(whi_m-1),id+exp2(whi_m-1),MPI_COMM_WORLD,&status);
    }
    
    int main(int argc,char *argv[])
    {
        int *data1,*data2;
        int now_id,sum_id;
        int whi_m,whi_r;
        int whi_i,whi_j;
    
        MPI_Status status;
        //启动mpi
        MPI_Init(&argc,&argv);
        //确定自己的进程标志符now_id
        MPI_Comm_rank(MPI_COMM_WORLD,&now_id);
        //组内进程数是sum_id
        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);
            data2 = (int*)malloc(sizeof(int)*whi_n);
            rands(data1,n);
            rands(data2,n);
            //打印初始数组
            //printf("---------\n");
           // printf("The original array data1: \n");
            //print(data1,n);
            //printf("The original array data2: \n");
           // print(data2,n);
            //printf("---------\n");
        }
        whi_m = log2(sum_id);
    MPI_Bcast(&whi_n,1,MPI_INT,0,MPI_COMM_WORLD);
    
        //并行排序
        para_quickSort(data1,0,whi_n-1,whi_m,0,now_id);
        whi_end = MPI_Wtime();
        if(now_id == 0){
            double sta_time2 = MPI_Wtime();
            quickSort(data2,0,n-1);
            double end_time2 = MPI_Wtime();
            //printf("----------\n");
           // printf("The final array data1 : \n");
            //print(data1,n);
            //printf("----------\n");
            printf("串行时间 = %f s\n",end_time2-sta_time2);
            printf("并行时间 = %f s\n",whi_end-whi_sta);
        }
        MPI_Finalize();
        return 0;
    }
    
  • 性能分析

    实际测试

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BCzCxaOi-1627008119463)(C:\Users\86176\AppData\Roaming\Typora\typora-user-images\image-20210723101736425.png)]

    CPU串行快排随数据规模N增大而呈指数递增,而MPI并行快排随数据规模N增大而呈线性递增。

  • 6
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 使用 MPI 实现快速排序的一种方法是使用分治策略。首先,将数组划分成若干个子数组,然后使用 MPI 进程分别对各自的子数组进行快速排序。然后,在所有进程完成排序之后,使用 MPI 函数进行数据交换,将所有子数组中的元素进行归并。最后,对归并后的数组进行快速排序即可完成整个数组的排序。 下面是一个使用 MPI 实现快速排序的简单示例: ``` #include <stdio.h> #include <stdlib.h> #include <mpi.h> #define N 1000000 int cmp(const void *a, const void *b) { return (*(int *)a - *(int *)b); } int main(int argc, char *argv[]) { int rank, size; int i, pivot, temp; int *data, *sub_data; MPI_Status status; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); data = (int *)malloc(N * sizeof(int)); sub_data = (int *)malloc((N / size) * sizeof(int)); if (rank == 0) { for (i = 0; i < N; i++) data[i] = rand(); } MPI_Scatter(data, N / size, MPI_INT, sub_data, N / size, MPI_INT, 0, MPI_COMM_WORLD); qsort(sub_data, N / size, sizeof(int), cmp); if (rank != 0) { MPI_Send(sub_data, N / size, MPI_INT, 0, 0, MPI_COMM_WORLD); } else { for (i = 1; i < size; i++) { MPI_Recv(data, N / size, MPI_INT, i, 0, MPI_COMM_WORLD, &status); qsort(data, N / size, sizeof(int), cmp); } qsort(sub ### 回答2: 快速排序是一种常用的排序算法,在并行计算中可以利用MPI实现更高效的排序。 快速排序的基本思想是通过选定一个基准元素,将待排序序列切分成两个子序列,其中一个序列所有元素都小于基准元素,另一个序列所有元素都大于基准元素。然后对两个子序列分别进行快速排序,最终将两个有序序列合并为一。 在MPI中,我们可以利用分布式内存模型,将待排序序列分布到不同的进程上,每个进程负责排序一部分数据。首先,我们选择一个进程作为主进程,负责生成随机序列,并将序列拆分并发送给其他进程。然后,每个进程对接收到的数据进行快速排序。排序结束后,他们将排好序的数据发送给主进程。 在主进程接收到所有排序结果后,可以利用归并操作将多个有序序列合并为一个有序序列,最终得到完整的有序序列。 在实际实现中,需要注意处理边界情况,比如仅有一个进程的情况,此时直接对整个序列进行排序即可。另外,在切分序列并选择基准元素时,需要使用一些算法来提高快速排序的效率,比如选择中位数作为基准元素。 总而言之,通过MPI实现快速排序可以利用多个进程同时处理不同部分的数据,并利用分布式计算资源加速排序过程,从而实现更快速的排序。 ### 回答3: 使用MPI(Message Passing Interface)实现快速排序需要将排序任务划分为多个子任务,并通过消息传递机制协同各个进程进行排序。 首先,选择一个排序算法作为快速排序的基本算法。快速排序的基本思想是通过选择一个基准元素,将待排序序列划分为两个子序列,左侧序列小于等于基准元素,右侧序列大于基准元素,然后分别对左右子序列进行递归排序。 在MPI实现中,可以采用Master-Worker模型。首先,Master进程负责读入待排序序列,并将序列拆分成若干个均匀的子序列,将每个子序列分发给Worker进程。Master进程通过发送消息给Worker进程来分配子序列和指示排序操作。 每个Worker进程接收子序列后,使用快速排序对其进行排序。排序过程中,Worker进程将子序列根据基准元素的大小分为两个子序列,并将左右子序列分别发送给其他Worker进程进行排序。这样不断地划分和排序,直到子序列长度为1或为空,则排序完成。 排序完成后,每个Worker进程将自己的排序好的子序列发送回Master进程。Master进程接收到Worker进程返回的消息后,按照顺序将这些子序列合并起来,得到最终的有序序列。 最后,Master进程将有序序列输出,即得到了使用MPI实现快速排序结果。 需要注意的是,MPI实现快速排序的过程中需要合理地划分子序列,并进行消息传递和接收。同时,应确保MPI进程之间的通信是同步的,以保证排序的正确性和一致性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

高明爱圣子

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

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

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

打赏作者

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

抵扣说明:

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

余额充值