基于mpi的奇偶排序_MPI并行编程系列三:并行正则采样排序PSRS

快速排序算法的效率相对较高,并行算法在理想的情况下时间复杂度可达到o(n),但并行快速排序算法有一个严重的问题:会造成严重的负载不平衡,最差情况下算法的复杂度可达o(n^2)。本篇我们介绍一种基于均匀划分的负载平衡的并行排序算法------并行正则采样排序(Parallel Sorting by Regular Sampling)。

一、算法的基本思想

假设待排序的元素n个,处理器p个。

首先将这n个元素均匀的分成p部分,每部分包含n/p个元素。每个处理器负责其中的一部分,并对其进行局部排序。为确定局部有序序列在整个序列中的位置,每个处理器从各自的局部有序序列中选取几个代表元素,将这些代表元素进行排序后选出p-1个主元。每个处理器根据这p-1个主元将自己的局部有序序列分成p段。然后通过全局交换的方式,将p段有序序列分发给对应的处理器,使第i个处理器都拥有各个处理器的第i段,共p段有序序列。每个处理器对着p段有序序列进行排序。最后,将各个处理器的有序段依次汇合起来,就是全局有序序列了。

二、算法描述

根据算法的基本思想,我们对算法的描述如下:

输入:n个待排序的序列

输出:分布在各个处理器上,得到全局有序的数据序列

1)无序序列的划分及局部排序

根据数据快的划分方法(请看系列一),将无序序列划分成p部分,每个处理器对其中的一部分进行串行快速排序,这样每个处理器就会拥有一个局部有序序列。

2)选取代表元素

每个处理器从局部有序序列中选取第w,2w,...,(p-1)w共p-1个代表元素。其中w = n/p^2。

3)确定主元

每个处理器都将自己选取好的代表元素发送给处理器p0。p0对这p段有序序列做多路归并排序,再从这排序后的序列中选取第p-1,2(p-1), ...,(p-1)(p-1)共p-1个元素作为主元。

4)分发主元

p0将这p-1个主元分发给各个处理器。

5)局部有序序列划分

每个处理器在接收到主元后,根据主元将自己的局部有序序列划分成p段。

6)p段有序序列的分发

每个处理器将自己的第i段发送给第i个处理器,是处理器i都拥有所有处理器的第i段。

7)多路排序

每个处理器将上一步得到的p段有序序列做多路归并。

经过这7步后,一次将每个处理器的数据取出,这些数据是有序的。

三、算法分析

1)负载均衡分析:

因为这个算法是一个负载平衡的算法,者从第1)步中就可以看出来,但却不是完美的,因为在第6)步的划分很可能会引起负载的不平衡。

2)时间复杂度分析

PSRS算法适合处理大批量的数据(呵呵,数据量不大,何必并行乎)。当n>p^3时,算法的时间复杂度可达n/p*logn。具体每一步的时间复杂度的分析在这里就不一一描述了,因为每一步的排序都是普通的串行排序算法。

四、算法实现

因为算法比较复杂,代码较长,本文仅仅列出主代码,代码如下:

1: void psrs_mpi(int *argc, char ***argv){

2:

3: int process_id;

4: int process_size;

5:

6: int *init_array; //初始数组

7: int init_array_length; //初始数组长度

8:

9: int *local_sample; //每个进程选取的代表元素数组

10: int local_sample_length; //代表元素数组长度

11:

12: int *sample; //代表元素集合(0号进程使用)

13: int *sorted_sample; //排序后的代表元素的集合

14: int sample_length; //代表预算的长度

15:

16: int *primary_sample; //主元

17:

18: int *resp_array; //偏移数组,主要用户指定个进程数组的各分段的长度

19:

20: int *section_resp_array; //偏移数组,用于指定进程从其他进程获得的数组的长度

21:

22: int *section_array; //从各个进程中获得分段数组的集合

23: int *sorted_section_array;

24: int section_array_length; //总长

25:

26: int section_index;

27:

28: int i, j ; //循环变量

29:

30: MPI_Request handle;

31: MPI_Status status;

32:

33: mpi_start(argc, argv, &process_size, &process_id, MPI_COMM_WORLD);

34: resp_array = (int *)my_mpi_malloc(process_id, sizeof(int) * process_size);

35:

36: //为每个进程构建一个数组

37: //并对改进型的数组进行串行快速排序

38: init_array_length = ARRAY_LENGTH;

39: init_array = (int *)my_mpi_malloc(process_id, sizeof(int) * init_array_length);

40: array_builder_seed(init_array, init_array_length, process_id);

41:

42: quick_sort(init_array, 0, init_array_length -1);

43:

44: //每个处理器从排序号的序列中选取process_size-1个元素

45: //并发送到0号进程中

46: local_sample_length = process_size - 1;

47: local_sample = array_sample(init_array, local_sample_length, init_array_length/process_size, process_id);

48:

49: if(process_id)

50: MPI_Send(local_sample, local_sample_length, MPI_INT, 0, SAMPLE_DATA, MPI_COMM_WORLD);

51:

52:

53: //0号进程接收各处理器发送过来的代表元素,并将这些元素做多路归并排序

54: if(!process_id){

55: sample = (int *)my_mpi_malloc(0, sizeof(int) * process_size * local_sample_length);

56: sorted_sample = (int *)my_mpi_malloc(0, sizeof(int) * process_size * local_sample_length);

57: array_copy(sample, local_sample, local_sample_length);

58:

59: for(i = 1; i < process_size; i++)

60: MPI_Irecv(sample + local_sample_length * i, local_sample_length, MPI_INT, i, SAMPLE_DATA,

61: MPI_COMM_WORLD, &handle);

62:

63: MPI_Wait(&handle, &status);

64:

65: for(i = 0; i < process_size; i++)

66: resp_array[i] = local_sample_length;

67:

68: mul_merger(sample, sorted_sample, resp_array, process_size);

69:

70: //从排序好的代表元素中选取process_size-1个主元,并将这些主元广播道其他的处理器中

71: primary_sample = array_sample(sorted_sample, process_size -1, process_size -1, process_id);

72: }

73: if(process_id)

74: primary_sample = (int *)my_mpi_malloc(process_id, sizeof(int) * process_size -1);

75:

76: MPI_Bcast(primary_sample, process_size-1, MPI_INT, 0, MPI_COMM_WORLD);

77:

78: //将处理器上的数据根据主元分成process_size 端

79: get_array_sepator_resp(init_array, primary_sample, resp_array, init_array_length, process_size);

80: if(process_id == ID){

81: printf("process %d resp array is:" ,process_id);

82: array_int_print(process_size, resp_array);

83: }

84:

85: //每个处理器将自己的第i段发送给第i个处理器

86: section_resp_array = (int *)my_mpi_malloc(process_id, sizeof(int) * process_size);

87: section_resp_array[process_id] = resp_array[process_id];

88:

89: //每个进程将要发送的数据的个数发送给哥哥处理器

90: for(i = 0; i < process_size; i++){

91: if(i == process_id){

92: for(j = 0; j < process_size; j++)

93: if(i != j)

94: MPI_Send(&(resp_array[j]), 1, MPI_INT, j, SECTION_INDEX ,

95: MPI_COMM_WORLD);

96: }

97: else

98: MPI_Recv(&(section_resp_array[i]), 1, MPI_INT, i, SECTION_INDEX,

99: MPI_COMM_WORLD, &status);

100: }

101:

102: MPI_Barrier(MPI_COMM_WORLD);

103:

104: section_array_length = get_array_element_total(section_resp_array, 0, process_size - 1);

105: section_array = (int *)my_mpi_malloc(process_id, sizeof(int) * section_array_length);

106: sorted_section_array = (int *)my_mpi_malloc(process_id, sizeof(int) * section_array_length);

107: section_index = 0;

108:

109: for(i = 0; i < process_size; i++){

110: if(i == process_id){

111: for(j = 0; j < process_size; j++){

112: if(j)

113: section_index = get_array_element_total(resp_array, 0 , j-1);

114: if(i == j)

115: array_int_copy(section_array, init_array, section_index, section_index+resp_array[j]);

116: if(i != j){

117: if(j)

118: section_index = get_array_element_total(resp_array, 0 , j-1);

119: MPI_Send(&(init_array[section_index]), resp_array[j], MPI_INT,

120: j, SECTION_DATA, MPI_COMM_WORLD);

121: }

122: }

123: }

124: else{

125: if(i)

126: section_index = get_array_element_total(section_resp_array, 0, i-1);

127: MPI_Recv(&(section_array[section_index]), section_resp_array[i], MPI_INT,

128: i, SECTION_DATA, MPI_COMM_WORLD, &status);

129: }

130: }

131: MPI_Barrier(MPI_COMM_WORLD);

132:

133: //进行多路归并排序

134: mul_merger(section_array, sorted_section_array, section_resp_array, process_size);

135:

136: array_int_print(section_array_length, sorted_section_array);

137:

138: //释放内存

139: free(resp_array);

140: free(init_array);

141: free(local_sample);

142: free(primary_sample);

143: free(section_array);

144: free(sorted_section_array);

145: free(section_resp_array);

146:

147: if(!process_id){

148: free(sample);

149: free(sorted_sample);

150: }

151:

152: MPI_Finalize();

153: }

五、MPI函数分析

在上述算法中,用到了MPI的非阻塞通信函数:MPI_IRecv,其对应的是MPI_Isend。这连个函数用于进程间的非阻塞通信,使通信和运算能够同时进行。有这两

个非阻塞通信函数,就不能不提MPI_Wait函数,该函数的作用是阻塞进程执行,直到想对应的所有进程操作都执行的这个地方为止。一般是这个三函数一起使用。

下篇,我们将介绍kmp字符串匹配算法及其并行化。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值