Bootstrap

MPI + OpenMP实现快速排序

题目

结合高性能并行计算领域算例,使用MPI + OpenMP并行化编写代码,并撰写报告,测试并行效率。

方法

  1. 定义快速排序quickSort函数,定义排序总数NUM;
  2. 生成大小为size * calculateSize的二维逆序数组,其中使用MPI_Comm_size获取size,size * calculateSize = NUM;
  3. MPI_Comm_rank获取进程ID,通过通信函数MPI_Scatter将每个子矩阵发送到每个子进程,然后每个子进程通过quickSort函数进行快速排序,使用OpenMP的sections集合函数进行线程划分;
  4. 将每个子进程通过通信函数MPI_Gather收回到0号进程中,再对收回了所有数的0号进程中的数组进行最后一次排序,即size路归并排序,就能得到排完序的数组;
  5. 利用MPI_Wtime()函数进行计时。

代码

#include "mpi.h"
#include "omp.h"
#include <iostream>
#define NUM 12800
using namespace std;

void swap(int &a, int &b)  //交换函数
{
	int temp;
	temp = a;
	a = b;
    b = temp;
}

void printArray(int *array, int len)  //输出数组
{
    for (int i = 0; i < len; i++)
        cout << array[i] << " ";
    cout << endl;
}

void quickSort(int *array, int l, int r)  //快速排序
{
    int i, m;
    if (l >= r) return;
    m = l;
    for (i = l + 1; i <= r; i++)
        if (array[i] < array[l])
            swap(array[++m], array[i]);
		
	swap(array[l], array[m]);
	#pragma omp parallel sections  //sections使用2个线程即两个并行块
    {
		#pragma omp section
        quickSort(array, l, m - 1);
		#pragma omp section
        quickSort(array, m + 1, r);
    };
}

int main(int argc, char *argv[])
{
	double time;  //时间标记
	time = MPI_Wtime();  //返回调用处理器上经过的挂钟时间
	
	MPI_Init(&argc, &argv);	 //MPI初始化
	int size;  //通信域comm中所包含的进程数
	MPI_Comm_size(MPI_COMM_WORLD, &size); //返回指定通信器中MPI进程的总数
	int calculateSize = (int)(NUM / size);  //每个进程分配的计算个数
    int processId;  //进程在comm中的标识号id
    MPI_Comm_rank(MPI_COMM_WORLD, &processId);  //获取MPI进程编号
    
    int arr[size][calculateSize];  //初始数组
	int arrMerge[size][calculateSize];  //合并数组
    int arrEach[calculateSize];  //size分之一个初始数组的数
    int figure = size * calculateSize;  //用于给数组赋值
    int arrFinal[size * calculateSize];  //排完序的完整数组

    for (int i = 0; i < size; i++)  //生成数组
	{
        for (int j = 0; j < calculateSize; j++) {
            arr[i][j] = figure--;
        }
    }
    
	//并行排序,将数组平均分成size块后分给每个子进程排序
	MPI_Scatter(arr, calculateSize, MPI_INT, arrEach, calculateSize, MPI_INT, 0, MPI_COMM_WORLD);	
	//每个进程里的排序
	quickSort(arrEach, 0, calculateSize - 1);
	//将子进程排完序的数组合并成一个
	MPI_Gather(arrEach, calculateSize, MPI_INT, arrMerge, calculateSize, MPI_INT, 0, MPI_COMM_WORLD);
	
	cout << "The current array of " << processId << ":" << endl;  //输出当前进程排序结果
    printArray(arrEach, calculateSize);
		
	//数组用于记录存放选择次数
	int numTimes[size] = {0};
	//用于存放合并数组时最大的四个数的数组
	int arrNumMax[size];
	
	for (int i = calculateSize * size - 1; i >= 0; i--)  //按升序顺序合并为一个数组
	{
		for (int j = 0; j < size; j++)  //找出szie个数组里面分别最大的数
		{
			if (numTimes[j] >= calculateSize)
				arrNumMax[j] = 0;
			else
				arrNumMax[j] = arrMerge[j][calculateSize - numTimes[j] - 1];
		}

		int maxNum = arrNumMax[0];  //找出size个数里面最大的数即size路归并
		for (int k = 1; k < size; k++)
		{
			if (arrNumMax[k] > maxNum)
				maxNum = arrNumMax[k];
		}

		for (int n = 0; n < size; n++)  //标记数最大的数组
		{
			if (maxNum == arrNumMax[n])
			{
				numTimes[n] = numTimes[n] + 1;
				break;
			}
		}
		arrFinal[i] = maxNum;  //存入数组
	}
	
	if (!processId)  
	{
		time = MPI_Wtime() - time;  //结束计时
		cout << "The final array :" << endl;  //输出数组
		printArray(arrFinal, size * calculateSize);
		cout << "NUM = "<< NUM << "\t" << "size = " << size << "\t" << "time = " << time * 1000 << " ms" << endl;
	}
	
    MPI_Finalize();  //终止MPI
    return 0;
} 

结果分析

1、简单输出测试:共100个数据进行排序,使用10个进程,其中线程数export OMP_NUM_THREADS=2。

$ mpicc -o quicksort -fopenmp quicksort.cpp -lstdc++
$ time mpirun -np 10 ./quicksort
The current array of 2:
71 72 73 74 75 76 77 78 79 80 
The current array of 5:
41 42 43 44 45 46 47 48 49 50 
The current array of 6:
31 32 33 34 35 36 37 38 39 40 
The current array of 7:
21 22 23 24 25 26 27 28 29 30 
The current array of 9:
1 2 3 4 5 6 7 8 9 10 
The current array of 1:
81 82 83 84 85 86 87 88 89 90 
The current array of 4:
51 52 53 54 55 56 57 58 59 60 
The current array of 0:
91 92 93 94 95 96 97 98 99 100 
The final array :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 
Parallel running time = 54.8629 ms
The current array of 3:
61 62 63 64 65 66 67 68 69 70 
The current array of 8:
11 12 13 14 15 16 17 18 19 20

结果分析:程序会根据输入进程数自动平均分配排序任务,因此采用二维数组方便进行数据的分布与收集,初始数组为逆序,每个进程排序完毕后进行归并收集,最后变为升序数组。

2、效率测试:在排序总数NUM=10000时,测试不同进程数对时间的影响。
在这里插入图片描述
在这里插入图片描述
结果分析:在本实验中,线程数设置为2;由于通信函数MPI_Scatter限制,故进程数选择需要能被10000整除。通过实验数据可得,当排序数目为10000时,进程数5的时间最快,也具有不错的效率;随着进程数不断增加,如50时,每个进程需要排序2000个数据,使得通信花费大于排序花费,因此时间反弹。

总结

在大规模节点间的并行时,由于节点间通讯的量是成平方项增长的,所以带宽很快就会显得不够。所以用MPI+OpenMP混合编写并行部分,即每个MPI进程执行多个OpenMP线程。OpenMP部分由于不需要进程间通信,直接通过内存共享方式交换信息,所以可以显著减少程序所需通讯的信息。

;