MPI并行化桶排序

MPI并行化桶排序

  • 桶排序(Bucket Sort)
  • 并行化桶排序算法
    • 简单的方法
    • 更好的方法
  • 代码实现

本文主要讲述了并行桶排序算法,以及如何使用MPI集合通信来实现。

桶排序(Bucket Sort)

桶排序假定待排序数据 [ a 0 , a 1 , ⋯   , a n − 1 ] [a_0,a_1,\cdots, a_{n-1}] [a0,a1,,an1] 的任何一个元素都满足 MIN ≤ a i ≤ MAX \text{MIN} \leq a_i \leq \text{MAX} MINaiMAX,其中MIN和MAX是常数,即所有的元素都落在相同的固定范围内。

桶排序把范围 [ MIN , MAX ] [\text{MIN}, \text{MAX}] [MIN,MAX] 划分为若干个子范围,每个范围对应一个 ,比如说可以把 [ 0 , 10 ] [0,10] [0,10] 划分为 [ 0 , 5 ] [0,5] [0,5] ( 5 , 10 ] (5,10] (5,10] 这两个子区间。

接下来遍历待排序的数据,根据当前元素的值,将其划分到对应的桶中去。划分完成后,对每个桶中的元素单独进行排序(使用其他排序算法完成这个排序),最终再把每个桶的数据合并,即可完成对所有数据的排序。

下面看一个例子,假设待排序数据为 [ 9 , 3 , 8 , 4 ] [9,3,8,4] [9,3,8,4],每个元素的都在 [ 0 , 10 ] [0,10] [0,10] 内。假设将 [ 0 , 10 ] [0,10] [0,10] 划分两个桶 [ 0 , 5 ] [0,5] [0,5] [ 6 , 10 ] [6,10] [6,10],分别为 B [ 0 ] B[0] B[0] B [ 1 ] B[1] B[1]

  1. 遍历待排序的数据,根据当前元素的值,将其划分到对应的 中去:这一步完成后, B [ 0 ] = { 3 , 4 } B[0]=\{3,4\} B[0]={3,4} B [ 1 ] = { 9 , 8 } B[1]=\{9,8\} B[1]={9,8}
  2. 对每个桶中的元素单独进行排序(使用其他排序算法完成这个排序):这一步完成后, B [ 0 ] = { 3 , 4 } B[0]=\{3,4\} B[0]={3,4} B [ 1 ] = { 8 , 9 } B[1]=\{8,9\} B[1]={8,9}
  3. 把每个桶的数据合并:依次合并 B [ 0 ] , B [ 1 ] B[0],B[1] B[0],B[1],最终得到排序后的数据 { 3 , 4 , 8 , 9 } \{3,4,8,9\} {3,4,8,9}

并行化桶排序算法

简单的方法

一个最简单的思路就是,每个进程对应一个桶,比如说一共有8个进程,那就把范围分为8个子范围(桶),每个进程负责一个桶,如下所图所示。

在这里插入图片描述

这个并行算法不足之处在于,如果待排序的数据不是均匀的分布在 [ MIN , MAX ] [\text{MIN},\text{MAX}] [MIN,MAX] 上,那么可能存在一些进程,它们负责的桶里面可能数据很少甚至根本没有,而部分进程负责桶里的数据又会过多,这会使得任务划分不均衡;此外最后合并的

更好的方法

现在描述另外一种并行化的思路,假设进程数为 n n n,并且将区间 [ MIN , MAX ] [\text{MIN}, \text{MAX}] [MIN,MAX] 划分为 n n n 个桶,分别为 B [ 0 ] , B [ 1 ] , ⋯   , B [ n − 1 ] B[0],B[1],\cdots,B[n-1] B[0],B[1],,B[n1]

  1. 首先将待排序数据平均划分到各个进程上
  2. 每个进程使用上面划分的 n n n 个桶,把它自己的数据划分到各个桶中
  3. 接下来进程间进行通信,每个进程都需要把自己的桶 Local_B [ i ] \text{Local\_B}[i] Local_B[i] 里的数据发送给进程 i i i,假设桶的编号依次为 0 , 1 , ⋯   , n − 1 0,1,\cdots,n-1 0,1,,n1,进程的编号依次为 0 , 1 , ⋯   , n − 1 0,1,\cdots,n-1 0,1,,n1。这一步完成后,全部待排序的数据已经成功地划分到各个桶中了,并且进程 i i i 拥有 Global_B[i] \text{Global\_B[i]} Global_B[i] 的数据,但是此时 Global_B[i] \text{Global\_B[i]} Global_B[i]中的数据并没有排好序
  4. 每个进程对他自己负责的桶进行排序,这一步需要使用其他排序算法完成
  5. 最后再将各个进程桶中的数据汇总即可

实现进程间的通信,需要MPI中相关函数的支持。第一步可以使用 MPI_Scatter() 实现;第三步可以使用 MPI_Alltoallv() 实现;第五步可以使用 MPI_Gatherv() 实现。MPI_Scatter()MPI_Gatherv() 原理较为简单,这里不再赘述,下面主要讲一下 MPI_Alltoall() 的通信过程,MPI_Alltoallv() 是类似的。

MPI_Alltoall() 主要是用于将每个进程的数据发送到所有的进程,比如说假设一共 n n n 个进程,每个进程都对自己的数组 Local_Data \text{Local\_Data} Local_Data 调用 MPI_Alltoall(),那么进程 i i i 会首先把自己的 Local_Data \text{Local\_Data} Local_Data 分成 n n n 份,然后分别发一份到每一个进程(把第 j j j 份发送给进程 j j j)。因此完成通信后,每个进程会收到 n n n 份来自其他进程的数据。

实际上,MPI_Alltoall() 也可以这么理解, “all-to-all” routine actually transfers rows of an array to columns. 下图是一个示例来帮助理解。

在这里插入图片描述

可以看到,通信前数组 A A A 按照行均匀地划分到各个进程,通信后,数组 A A A按照列均匀地划分到各个进程

代码实现

#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
#include <sys/time.h>
#include <time.h>

// #define PRINT_RES

// return the difference between end and start in microseconds
int timeDiff(struct timeval start, struct timeval end) {
	return (end.tv_sec-start.tv_sec)*1000000 + (end.tv_usec-start.tv_usec);
}

// The compar for qsort
int cmpfunc(const void *a, const void *b){
   return (*(int*)a - *(int*)b);
}

void print_arr(int* arr, int len){
    int i;
    for(i = 0; i < len; ++i){
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main(int argc, char *argv[]){
    int my_rank, comm_sz;
    int n, i, local_n;
    struct timeval start_time, end_time;
    long local_runtime, total_runtime;
    int *total_array, *local_array, num_buckets;
    int *local_small_buckets, *local_large_bucket;
    int *len_local_small_buckets, len_local_large_bucket;
    int *sdispls, *recvcounts, *rdispls;

    MPI_Init(&argc, &argv);
	MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
	MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);

    sdispls = malloc(sizeof(int) * comm_sz);
    recvcounts = malloc(sizeof(int) * comm_sz);
    rdispls = malloc(sizeof(int) * comm_sz);

    num_buckets = comm_sz; // The number of buckets is equal to the number of processes
    // get the size of array from the command
    if (argc < 2) {
        n = 10000;
    } else {
        n = atoi(argv[1]);
    }
    local_n = n / comm_sz;
    if(local_n <= 0){
        local_n = 1;
    }
    n = local_n * comm_sz;
    if(my_rank == 0){
        srand(0);
        total_array = (int*) malloc(sizeof(int) * n);
        local_array = (int*) malloc(sizeof(int) * local_n);
        for (i = 0; i < n; ++i){
            total_array[i] = rand();
        }
    }else{
        local_array = (int*) malloc(sizeof(int) * local_n);
    }

    gettimeofday(&start_time, NULL);

    // The master needs to partition total_array and send each partition to the correct slave process
    MPI_Scatter(total_array, local_n, MPI_INT, local_array, local_n, MPI_INT, 0, MPI_COMM_WORLD);

    // Small bucket distribution
    local_small_buckets = malloc(sizeof(int*) * local_n * num_buckets);
    len_local_small_buckets = calloc(num_buckets, sizeof(int));
    
    int tmp = RAND_MAX / num_buckets + 1;
    for (i = 0; i < local_n; ++i){
        int idx = local_array[i] / tmp;
        local_small_buckets[local_n * idx + len_local_small_buckets[idx]++] = local_array[i];
    }

    // Small buckets then emptied into p final buckets for sorting
    local_large_bucket = malloc(sizeof(int) * n);
    len_local_large_bucket = 0;
    for (i = 0; i < comm_sz; ++i){
        sdispls[i] = local_n * i;
    }
    MPI_Alltoall(len_local_small_buckets, 1, MPI_INT, recvcounts, 1, MPI_INT, MPI_COMM_WORLD);
    rdispls[0] = 0;
    len_local_large_bucket = recvcounts[0];
    for (i = 1; i < comm_sz; ++i){
        rdispls[i] = rdispls[i-1] + recvcounts[i-1];
        len_local_large_bucket += recvcounts[i];
    }
    MPI_Alltoallv(local_small_buckets, len_local_small_buckets, sdispls, MPI_INT, local_large_bucket, recvcounts, rdispls, MPI_INT, MPI_COMM_WORLD);

    // Sort the local_large_bucket
    qsort(local_large_bucket, len_local_large_bucket, sizeof(int), cmpfunc);

    gettimeofday(&end_time, NULL);
    local_runtime = timeDiff(start_time, end_time);
    MPI_Reduce(&local_runtime, &total_runtime, 1, MPI_LONG, MPI_MAX, 0, MPI_COMM_WORLD);

    // Gather all the data to the main process
    MPI_Gather(&len_local_large_bucket, 1, MPI_INT, recvcounts, 1, MPI_INT, 0, MPI_COMM_WORLD);
    if(my_rank == 0){
        rdispls[0] = 0;
        for (i = 1; i < comm_sz; ++i){
            rdispls[i] = rdispls[i-1] + recvcounts[i-1];
        }
    }
    MPI_Gatherv(local_large_bucket, len_local_large_bucket, MPI_INT, total_array, recvcounts, rdispls, MPI_INT, 0, MPI_COMM_WORLD);

    if (my_rank == 0){
        int is_sorted = 1;
        for(i = 1; i < n; ++i){
            if (total_array[i] < total_array[i-1]){
                is_sorted = 0;
                break;
            }
        }
        if(is_sorted){
            printf("The Data has been sorted.\n");
        }else{
            printf("Data Not Sorted in index %d with %d > %d\n", i, total_array[i-1], total_array[i]);
        }
#ifdef PRINT_RES
        print_arr(total_array, n);
#endif
        printf("Total runtime (in microseconds): %ld\n", total_runtime);
        free(total_array);
    }
    free(local_array);
    free(len_local_small_buckets);
    free(local_small_buckets);
    free(local_large_bucket);
    free(rdispls);
    free(recvcounts);
    free(sdispls);
    MPI_Finalize();
    return 0;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值