基于mpi的奇偶排序_MPI 并行奇偶交换排序 + 集合通信函数 Sendrecv() Sendvecv_replace()...

本文通过一个基于MPI的奇偶排序示例,展示了如何利用MPI的Scatter、Gather、Sendrecv及Sendrecv_replace等函数实现并行排序。实验结果显示,中等到大规模数据的并行排序速度明显提升,验证了并行计算的优势。此外,文章还探讨了MPI函数的使用细节,包括栈溢出问题以及数据在进程间的正确传递。
摘要由CSDN通过智能技术生成

▶ 《并行程序设计导论》第三章的例子程序

● 代码

1 #include

2 #include

3 #include

4

5 const int nProcess = 8, localSize = 1024 * 1024, globalSize = nProcess *localSize;6

7 int compare(const void *a, const void *b) { return *(int *)a - *(int *)b; }//用于快排的回调函数

8

9 void merge(int *a, const int *b, const bool low)//归并数组 a 和 b,由 low 决定归并小端还是大端

10 {11 inttemp[localSize], pa, pb, ptemp;12 if(low)13 {14 for (pa = pb = ptemp = 0; ptemp

19 temp[ptemp++] = b[pb++];20 }21 }22 else

23 {24 for (pa = pb = ptemp = localSize - 1; ptemp >= 0;)25 {26 if (a[pa] <=b[pb])27 temp[ptemp--] = b[pb--];28 else

29 temp[ptemp--] = a[pa--];30 }31 }32 for (pa = ptemp = 0; pa < localSize; a[pa++] = temp[ptemp++]);33 return;34 }35

36 int main(int argc, char*argv[])37 {38

39 intglobalData[globalSize], globalDataForQSort[globalSize], localData[localSize], changeData[localSize];40 inti, comSize, comRank, partner;41 doubletimeQSort, timeProcess, timeSort, accerlateRatio;42

43 MPI_Init(&argc, &argv);44 MPI_Comm_size(MPI_COMM_WORLD, &comSize);45 MPI_Comm_rank(MPI_COMM_WORLD, &comRank);46

47 srand(2);48 if (comRank == 0)49 {50 printf("Lrngth: %d\n", globalSize);51 for (i = 0; i < globalSize; globalData[i] = globalDataForQSort[i] = rand(), i++);52

53 //CPU 单线程排序并计时

54 timeQSort =MPI_Wtime();55 qsort(globalDataForQSort, globalSize, sizeof(int), &compare);56 timeQSort = MPI_Wtime() -timeQSort;57 printf("QSort time: %f ms\n", timeQSort * 1000);58 }59

60 //MPI 多进程排序

61 MPI_Barrier(MPI_COMM_WORLD);62 timeProcess =MPI_Wtime();63 MPI_Scatter(globalData, localSize, MPI_INT, localData, localSize, MPI_INT, 0, MPI_COMM_WORLD);//分发数据

64 qsort(localData, localSize, sizeof(int), &compare); //自行快排

65 for (i = 0; i < nProcess; i++) //每次循环完成一次交换,交换 nProcess 次完成排序

66 {67 //寻找 partner

68 if (i % 2)69 partner = comRank + (comRank % 2 ? +1 : -1);70 else

71 partner = comRank + (comRank % 2 ? -1 : +1);72 if (partner == -1 || partner ==comSize)73 partner =MPI_PROC_NULL;74

75 //与 partner 交换数据,使用函数 MPI_Sendrecv() 与后面注释中的发送、接收函数等价

76 MPI_Sendrecv(localData, localSize, MPI_INT, partner, 0, changeData, localSize, MPI_INT, partner, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);77 /*

78 if (comRank % 2) // 奇数号进程先接受再发送,偶数号进程先发送再接收79 {80 MPI_Recv(changeData, localSize, MPI_INT, partner, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);81 MPI_Send(localData, localSize, MPI_INT, partner, 0, MPI_COMM_WORLD);82 }83 else84 {85 MPI_Send(localData, localSize, MPI_INT, partner, 0, MPI_COMM_WORLD);86 MPI_Recv(changeData, localSize, MPI_INT, partner, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);87 }88 */

89 if (partner != MPI_PROC_NULL) //如果 partner 比本结点大,则本结点保留较小的部分

90 merge(localData, changeData, partner >comRank);91 MPI_Barrier(MPI_COMM_WORLD); //归并后进行同步,准备下一次交换

92 }93 MPI_Gather(localData, localSize, MPI_INT, globalData, localSize, MPI_INT, 0, MPI_COMM_WORLD);//聚集数据到主进程,以便检查结果

94 timeProcess = MPI_Wtime() -timeProcess;95 MPI_Reduce((void *)&timeProcess, (void *)&timeSort, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);96

97 //将排序结果与 CPU 单线程的结果相比较

98 if (comRank == 0)99 {100 printf("Sort time: %f ms, accerlate ratio = %f\n", timeSort * 1000, timeQSort /timeSort);101 for (i = 0; i < globalSize; i++)102 {103 if (globalData[i] !=globalDataForQSort[i])104 break;105 }106 if (i ==globalSize)107 printf("Result Correct!");108 else

109 printf("Error at i = %d, qsort[i] = %d, sort[i] = %d", i, globalDataForQSort[i], globalData[i]);110 }111 MPI_Finalize();112 return 0;113 }

● 输出结果。中等规模的数组加速比较高,小规模情形非交换或归并的其他过程耗时较多,没有体现出并行的优势;大规模情形数据交换量较大,耗时较多

D:\Code\MPI\MPIProjectTemp\x64\Debug>mpiexec -n 8 -l MPIProjectTemp.exe

[0]Lrngth: 8192[0]QSort time: 2.573407ms

[0]Sort time: 0.785797 ms, accerlate ratio = 3.274899[0]Result Correct!D:\Code\MPI\MPIProjectTemp\x64\Debug>mpiexec -n 8 -l MPIProjectTemp.exe

[0]Lrngth: 262144[0]QSort time: 101.129885ms

[0]Sort time: 20.440968 ms, accerlate ratio = 4.947412[0]Result Correct!D:\Code\MPI\MPIProjectTemp\x64\Debug>mpiexec -n 8 -l MPIProjectTemp.exe

[0]Lrngth: 8388608[0]QSort time: 3051.212378ms

[0]Sort time: 687.252457 ms, accerlate ratio = 4.439726[0]Result Correct!

● 在 MPI 代码结尾处混入 getchar(); 会导致进程挂起,无法正常退出,报错代码是 0xc00000fd(栈溢出?),写的时候不要加上这条语句。

▶ 函数 MPI_Sendrecv() 和 MPI_Sendrecv_replace(),同时进行一次阻塞时信息发送和接收,完全由 MPI 进行调度,当发送数据和接收数据变量名不同时使用函数 MPI_Sendrecv(),名字相同时两者均可使用(在函数 MPI_Sendrecv() 中将参数 send_buf_p 与 recv_buf_p 设为相同即可)

● 函数声明

1 MPI_METHOD MPI_Sendrecv(2 _In_opt_ const void* sendbuf, //发送数据

3 _In_range_(>= , 0) int sendcount, //发送数据的个数

4 _In_ MPI_Datatype sendtype, //发送数据类型

5 _In_range_(>= , MPI_PROC_NULL) int dest, //发送目标进程号

6 _In_range_(>= , 0) int sendtag, //发送标签

7 _Out_opt_ void* recvbuf, //接收数据

8 _In_range_(>= , 0) int recvcount, //接收数据个数

9 _In_ MPI_Datatype recvtype, //接收数据类型

10 _In_range_(>= , MPI_ANY_SOURCE) int source, //接收数据源

11 _In_range_(>= , MPI_ANY_TAG) int recvtag, //接受标签

12 _In_ MPI_Comm comm, //通信子

13 _Out_ MPI_Status* status //状态结构指针

14 );15

16 MPI_METHOD MPI_Sendrecv_replace(17 _Inout_opt_ void* buf, //合并 sendbuf 和 recvbuf

18 _In_range_(>= , 0) int count, //合并 sendcount 和 recvcount

19 _In_ MPI_Datatype datatype, //合并 sendtype 和 recvtype

20 _In_range_(>= , MPI_PROC_NULL) intdest,21 _In_range_(>= , 0) intsendtag,22 _In_range_(>= , MPI_ANY_SOURCE) intsource,23 _In_range_(>= , MPI_ANY_TAG) intrecvtag,24 _In_ MPI_Comm comm,25 _Out_ MPI_Status*status26 );

● 函数 MPI_Sendrecv_replace() 的使用范例

1 {2 const int nProcess = 8, localSize = 8, globalSize = localSize *nProcess;3 intglobalData[globalSize], localData[localSize];4 intcomRank, comSize, i;5

6 MPI_Init(&argc, &argv);7 MPI_Comm_rank(MPI_COMM_WORLD, &comRank);8 MPI_Comm_size(MPI_COMM_WORLD, &comSize);9

10 if (comRank == 0)11 for (i = 0; i < globalSize; globalData[i] = i, i++);12

13 MPI_Scatter(globalData, localSize, MPI_INT, localData, localSize, MPI_INT, 0, MPI_COMM_WORLD);14 for (i = 0; i < localSize; i++) //打印各进程数据

15 printf("%2d,", localData[i]);16

17 MPI_Barrier(MPI_COMM_WORLD); //同步后通信,将每个进程的数据环形发送给下一个进程

18 MPI_Sendrecv_replace(localData, localSize, MPI_INT, (comRank + 1) % nProcess, 0, (comRank + nProcess - 1) % nProcess, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);19 MPI_Barrier(MPI_COMM_WORLD);20

21 for (i = 0; i < localSize; i++) //再次打印各进程数据

22 printf("%2d,", localData[i]);23

24 MPI_Finalize();25 return 0;26 }

● 输出结果,发现数据被正确的轮换了,但是各进程的两次输出却是连在一起的,猜想各进程输出时被加载到了同一个缓冲区中,再按照进程排序合并,最后一次性向 stdout 输出,可以使用 fflush(stdout); 来清空缓冲区

1 D:\Code\MPI\MPIProjectTemp\x64\Debug>mpiexec -n 8 -l MPIProjectTemp.exe2 [0] 0, 1, 2, 3, 4, 5, 6, 7, 56, 57, 58, 59, 60, 61, 62, 63,3 [1] 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7,4 [6]48, 49, 50, 51, 52, 53, 54, 55, 40, 41, 42, 43, 44, 45, 46, 47,5 [5]40, 41, 42, 43, 44, 45, 46, 47, 32, 33, 34, 35, 36, 37, 38, 39,6 [2]16, 17, 18, 19, 20, 21, 22, 23, 8, 9, 10, 11, 12, 13, 14, 15,7 [3]24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23,8 [7]56, 57, 58, 59, 60, 61, 62, 63, 48, 49, 50, 51, 52, 53, 54, 55,9 [4]32, 33, 34, 35, 36, 37, 38, 39, 24, 25, 26, 27, 28, 29, 30, 31,

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值