深圳大学并行计算实验六PSRS排序算法的MPI并行程序

一. 实验目的

1. 学会编写简单的MPI程序;

2. 对并行程序进行简单的性能分析。

二. 实验环境

1. 软件环境:Microsoft Visual Studio 2013。

三. 实验内容

1. 实验要求:用MPI编写7.1.1节中的PSRS排序算法,将数组a中的n个数排序,结果输出到数组b中。

l  初始时,数组a存储在进程0中,其他进程没有数据。结束时,数组b也存储在进程0中。

l  数组a中的每个数都初始化为一个0到1之间的随机double型值(用rand()/double(RAND_MAX)实现)。

l  添加检测计算结果是否正确的代码。

l  计算执行时间用MPI_Wtime()函数。

2. 程序代码和说明:

#include<iostream>

#include<mpi.h>

#include<math.h>

#include<algorithm>

#include<vector>

using namespace std;

int n = 100000000;

//p路归并排序函数

void Mergesort(double* pData, int* pIndex, int len, int pnum)

{

    double* pSort = new double[len];    // 归并后的p路有序数组

    int* idx = new int[pnum];           // p段对应p个下标

    // 下标初始化

    for (int i = 0; i < pnum; i++) idx[i] = pIndex[i];

    // p路归并

    int k = 0;

    for (int i = 0; i < len; i++)

    {

        int tidx = 0;

        double tData = 2;

        // 找到各个指针指向的最小值

        for (int j = 0; j < pnum; j++)

            if (idx[j] < pIndex[+ 1] && pData[idx[j]] < tData)

            {

                tData = pData[idx[j]];

                tidx = j;

            }

        pSort[i] = tData;   // 赋值

        idx[tidx] ++;       // 对应指针右移

    }

    for (int i = 0; i < len; i++) pData[i] = pSort[i];

}

//串行PSRS函数,使用快排替代

void PSRS_single(double* a) {

    sort(a, a + n);

}

int main(int argc,char**argv) {

    int rank, size;  //分别是进程号与进程数目

    int times = 5;   //执行次数

    MPI_Status status;  //MPI状态变量

    double avg_time[5]; //并行时间数组

    double end1;         //串行时间

    MPI_Init(&argc, &argv);//初始化MPI

    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    MPI_Comm_size(MPI_COMM_WORLD, &size);

    while (times--) {

       

        int num = n / size;             //分段长度

        double* a = new double[n];      //初始数组

        double* b = new double[n];      //结果数组

        double* b_a = new double[num];  //各个进程存放的分段数组

        double* temp = new double[n];   //串行计算的结果数组  

        double* sample = new double[size];          //单个进程的样本元素数组

        double* sample1 = new double[size*size];    //所有进程的样本元素数组

        double* mainyuan = new double[size - 1];    //主元数组

        int* sendindex1 = new int[size + 1];        //发送主元所在下标的数组

        int* sendlength1 = new int[size + 1];       //发送各个进程主元段的长度的数组

        int* recvindex1 = new int[size + 1];        //各个进程接受主元所在下标的数组

        int* recvlength1 = new int[size + 1];       //各个进程接受主元段长度的数组

        //主进程初始化

        if (rank == 0)

        {

            for (int i = 0; i < n; i++) {

                a[i] = (double)rand() / (RAND_MAX);

                temp[i] = a[i];

            }

        }

        double start;

        //串行计算

        if (rank == 0 && times == 4) {

            start = MPI_Wtime();

            PSRS_single(temp);

            end1 = MPI_Wtime() - start;

            cout << "串行时间为:" << end1 << endl;

        }

        MPI_Barrier(MPI_COMM_WORLD);

        start = MPI_Wtime();

       

        //a分成num段分发给各个进程

        if (rank == 0)

            //Scatter需要分进程执行,否则除主进程外其余进程不能接收到其散播的数据

            MPI_Scatter(a, num, MPI_DOUBLE, b_a, num, MPI_DOUBLE, 0, MPI_COMM_WORLD);

        else

            MPI_Scatter(NULL, num, MPI_DOUBLE, b_a, num, MPI_DOUBLE, 0, MPI_COMM_WORLD);

       

        //段内比较排序

        sort(b_a, b_a + num);

       

        //每段选出size个值

        int p = 0;

        for (int i = 0; i < num; i+=n/(size*size)) {

            sample[p++] = b_a[i];

        }

       

        //将各个进程的sample数组里的size个样本元素汇集给0号进程的sample1数组中

        MPI_Gather(sample, size, MPI_DOUBLE, sample1, size, MPI_DOUBLE, 0, MPI_COMM_WORLD);

       

        //主进程对size平方的值进行排序并且选出p个主元

        if (rank == 0) {

            //p^2个主元进行排序

            sort(sample1, sample1 + size * size);

            int k = 0;

            for (int i = 0; i < size-1; i ++) {

                mainyuan[k] = sample1[(i+1)*size];

                k++;

            }          

        }

        //将主进程的主元广播出去,让各个进程接收到相同的主元

        MPI_Bcast(mainyuan, size - 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);   //将最终选取的主元广播出去

   

       

        //找到各进程需要找到的下标,以及长度  找下标使用的是二分法

        sendindex1[0] = sendlength1[0] = 0;

        for (int i = 1; i < size; i++) {

            //对每个进程里的主元求秩,找到他们对应的位置

            int l = 0, r =  num- 1;

            double x = mainyuan[- 1];

            while (< r) {

                int mid = l + r >> 1;

                if (b_a[mid] > x) r = mid;

                else l = mid + 1;

            }

            sendindex1[i] = r;

        }

        sendindex1[size] = num;//标兵下标

       

        //根据找到的主元段下标算主元段的长度

        for (int i = 0; i < size; i++) sendlength1[i] = sendindex1[+ 1] - sendindex1[i];

       

        //全局交换发送和接收长度   各个进程将各自的主元段长度发给相应的进程

        MPI_Alltoall(sendlength1, 1, MPI_INT, recvlength1, 1, MPI_INT, MPI_COMM_WORLD);

       

        //计算接收下标,totalnum=接收长度总长

        int totalnum = 0;

        for (int i = 0; i < size; i++){

            recvindex1[i] = totalnum;

            totalnum += recvlength1[i];

        }

        recvindex1[size] = totalnum;

        double* result = new double[totalnum];

       

        //将每个进程的b_a数组从sendindex1发送sendlength1个长度给result数组,其接收长度为recvlength1,下标为recvindex1

        MPI_Alltoallv(b_a, sendlength1, sendindex1, MPI_DOUBLE, result, recvlength1, recvindex1, MPI_DOUBLE, MPI_COMM_WORLD);

       

        //各个进程对其中的主元段进行p路归并

        Mergesort(result,recvindex1,totalnum,size );

       

        //将各个进程算出来的总长度以及结果数组发送给主进程

        for (int i = 0; i < totalnum; i++)

            b[i] = result[i];

        if (rank != 0) {

            MPI_Send(&totalnum, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);

            MPI_Send(result, totalnum, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);

        }

        if(rank==0)

        for (int i = 1; i < size; i++) {

            int recvsum;

            MPI_Recv(&recvsum, 1, MPI_INT, i, 0, MPI_COMM_WORLD, &status);

            MPI_Recv(+ totalnum, recvsum, MPI_DOUBLE, i, 0, MPI_COMM_WORLD, &status);

            totalnum += recvsum;

        }

        double end = MPI_Wtime();

        //校对结果

        if(rank==0&&times==4)

        for (int i = 0; i < n; i++) {

            if (b[i] - temp[i]>1e-6) {

                cout << i << " | error | b[i]="<<b[i]<<" | temp[i]="<<temp[i] << endl;

                exit(0);

            }

        }

        //输出结果

        if (rank == 0) {

            avg_time[times] = end - start;

            cout << size << "个进程并行时间为:" << end - start << endl;

        }

       

        //删除数组,节省空间

        delete[] a;

        delete[] b;

        delete[]  b_a ;

        delete[] temp ;

        delete[] sample;

        delete[] sample1 ;

        delete[] mainyuan ;

        delete[] sendindex1;

        delete[] sendlength1;

        delete[] recvindex1 ;

        delete[] recvlength1 ;

        delete[] result;   

    }

    //结束MPI

    MPI_Finalize();

    if (rank == 0) {

        double alltime = 0;

        for (int i = 0; i < 5; i++) {

            alltime += avg_time[i];

        }

        cout << "平均时间为:" << alltime / 5 << endl;

        cout << "加速比为:" << (5 * end1) / alltime;

    }

}

3. 实验结果和分析:测试并行程序在不同进程数下的执行时间和加速比(串行执行时间/并行执行时间),并分析实验结果。其中,n固定为100000000,进程数分别取1、2、4、8、16、32、64时,为减少误差,每项实验进行5次,取平均值作为实验结果。

表1 并行程序在不同进程数下的执行时间(秒)和加速比

线程数

执行时间

1

2

4

8

16

32

64

第1次

14.20

5.59

5.80

4.47

4.31

3.89

3.55

第2次

10.44

6.45

4.51

5.46

4.06

3.64

3.46

第3次

10.42

5.54

7.29

4.09

4.66

3.67

3.57

第4次

10.08

5.82

5.63

5.21

4.87

3.55

3.26

第5次

9.98

6.49

5.35

4.01

4.02

3.49

3.32

平均值

11.03

5.98

5.71

4.65

4.38

3.64

3.43

加速比

0.78

1.48

1.50

1.84

1.96

2.46

2.62

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值