实现内容
实现不完整的并行归并排序,初始待排序元素数量为n,数组长度可变(即n可以自行指定),结果同时输出初始的数组以及排序完成后的数组,同时通过MPI函数打印机器名。
实现思路
主进程生成随机数据后,将数据分发到其他进程进行归并排序,在归并过程中,对数据进行划分时,如果元素少于规定的阈值,则调用stl中的sort函数直接进行排序。各个进程排好序后将数据发送给主进程进行汇总,主进程再进行一次归并排序操作,将最后排序结果保存输出。
实现步骤
1.功能函数介绍
(1)merge函数用于合并两个有序数组left和right,返回一个合并后的有序数组。
vector<int> merge(const vector<int>& left, const vector<int>& right) {
vector<int> result;
int i = 0, j = 0;
while (i < left.size() && j < right.size()) {
if (left[i] <= right[j]) {
result.push_back(left[i]);
i++;
}
else {
result.push_back(right[j]);
j++;
}
}
while (i < left.size()) {
result.push_back(left[i]);
i++;
}
while (j < right.size()) {
result.push_back(right[j]);
j++;
}
return result;
}
(2)mergeSort函数实现了归并排序算法。如果数组大小小于或等于阈值threshold,则直接使用STL的sort函数进行排序。否则,将数组分成两部分,分别进行递归排序,然后合并结果。
vector<int> mergeSort(vector<int>& arr, int threshold) {
if (arr.size() <= threshold) {
sort(arr.begin(), arr.end());
return arr;
}
int mid = arr.size() / 2;
vector<int> left(arr.begin(), arr.begin() + mid);
vector<int> right(arr.begin() + mid, arr.end());
left = mergeSort(left, threshold);
right = mergeSort(right, threshold);
return merge(left, right);
}
2.主要用到的MPI函数
1)MPI_Scatter
将一段array 的不同部分发送给所有的进程。
0号进程分发数据的时候是根据进程的编号进行的,array中的第一个元素发送给0号进程,第二个元素则发送给1号进程,以此类推。
MPI_Scatter(
void* send_data,//存储在0号进程的数据,array
int send_count,//具体需要给每个进程发送的数据的个数
//如果send_count为1,那么每个进程接收1个数据;如果为2,那么每个进程接收2个数据
MPI_Datatype send_datatype,//发送数据的类型
void* recv_data,//接收缓存,缓存 recv_count个数据
int recv_count,
MPI_Datatype recv_datatype,
int root,//root进程的编号
MPI_Comm communicator)
2)MPI_Gather
MPI_Gather和MPI_scatter刚好相反,他的作用是从所有的进程中将每个进程的数据集中到根进程中,同样根据进程的编号对array元素排序,如图所示:
其函数为:
MPI_Gather(
void* send_data,
int send_count,
MPI_Datatype send_datatype,
void* recv_data,
int recv_count,//注意该参数表示的是从单个进程接收的数据个数,不是总数
MPI_Datatype recv_datatype,
int root,
MPI_Comm communicator)
3.重要代码详细注释
// 将数据分发到各个进程
// 计算每个进程需要处理的数据块大小
int chunk_size = n / size;
// 为每个进程的局部数据分配空间
vector<int> local_data(chunk_size);
// 使用MPI_Scatter函数将数据从根进程(rank 0)分发到所有进程
MPI_Scatter(data.data(), chunk_size, MPI_INT, local_data.data(), chunk_size, MPI_INT, 0, MPI_COMM_WORLD);
// 局部排序
// 每个进程对其接收到的局部数据进行归并排序
local_data = mergeSort(local_data, threshold);
// 归并结果
// 为最终的排序结果分配空间
vector<int> sorted_data(n);
// 使用MPI_Gather函数将所有进程的局部排序结果收集到根进程(rank 0)
MPI_Gather(local_data.data(), chunk_size, MPI_INT, sorted_data.data(), chunk_size, MPI_INT, 0, MPI_COMM_WORLD);
// 根进程合并最终结果
if (rank == 0) {
// 根进程对收集到的所有局部排序结果进行最终的归并排序
sorted_data = mergeSort(sorted_data, threshold);
// 打印排序结果
cout << "排序后的数组: ";
for (int i = 0; i < n; i++) {
cout << sorted_data[i] << " ";
}
cout << endl;
}
4.整体流程
(1)MPI初始化和进程通信设置:
使用MPI初始化函数 MPI_Init 和获取进程信息函数 MPI_Comm_rank、MPI_Comm_size 获取进程的总数和每个进程的编号。
int rank, size;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
(2)生成随机数据:
可以选择进行排序的元素个数n,自己进行设置。具体数据采用生成随机数据的方式。根进程(rank 0)生成随机整数数组作为排序数据,并打印排序前的数组。
(3)数据分发:
使用 MPI_Scatter 函数将根进程(rank 0)生成的数据均匀分发到各个进程中。
(4)局部排序:
每个进程对其接收到的局部数据进行排序,使用归并排序算法,并设置了一个阈值,当归并数组时数组的长度等于这个阈值时,直接调用sort()对数组进行排序,不再继续归并。
(5)归并结果:
使用 MPI_Gather 将各个进程的局部排序结果收集回根进程。
(6)根进程的最终归并排序:
根进程接收所有局部排序结果后,再次进行归并排序,得到最终的有序数组,并打印排序后的数组。
(7)输出主机名:
主进程(rank=0)下,利用MPI_Get_processor_name(processor_name, &namelen)函数输出自己的主机名称。
(8)MPI结束:
最后调用 MPI_Finalize()函数结束MPI进程。
实验结果
设置排序元素个数n=64;
使用的阈值为4,即当数组数量为4时,不在继续归并,直接利用sort函数进行排序。
排序前后的数组如下图所示:
完整代码
#include <iostream>
#include <vector>
#include <mpi.h>
#include <algorithm>
#include <random>
using namespace std;
// 合并两个有序数组
vector<int> merge(const vector<int>& left, const vector<int>& right) {
vector<int> result;
int i = 0, j = 0;
while (i < left.size() && j < right.size()) {
if (left[i] <= right[j]) {
result.push_back(left[i]);
i++;
}
else {
result.push_back(right[j]);
j++;
}
}
while (i < left.size()) {
result.push_back(left[i]);
i++;
}
while (j < right.size()) {
result.push_back(right[j]);
j++;
}
return result;
}
// 归并排序
vector<int> mergeSort(vector<int>& arr, int threshold) {
if (arr.size() <= threshold) {
sort(arr.begin(), arr.end());
return arr;
}
int mid = arr.size() / 2;
vector<int> left(arr.begin(), arr.begin() + mid);
vector<int> right(arr.begin() + mid, arr.end());
left = mergeSort(left, threshold);
right = mergeSort(right, threshold);
return merge(left, right);
}
int main(int argc, char** argv) {
int rank, size;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
// 确保所有进程都完成了初始化
MPI_Barrier(MPI_COMM_WORLD);
// 每个进程输出自己的进程编号
printf("Process %d out of %d processes\n", rank, size);
// 设置排序元素个数
int n = 64;
// 设置阈值
int threshold = 4;
if (rank == 0) {
cout << "使用的阈值: " << threshold << endl;
}
// 生成随机数据
vector<int> data(n);
if (rank == 0) {
random_device rd; // 用于获取随机种子
mt19937 gen(rd()); // 使用 Mersenne Twister 引擎生成随机数
uniform_int_distribution<> distrib(1, 1000); // 设置随机数范围为 1 到 10000
for (int i = 0; i < n; i++) {
data[i] = distrib(gen); // 生成更随机的整数
}
// 打印排序前的数组
cout << "排序前的数组: ";
for (int i = 0; i < n; i++) {
cout << data[i] << " ";
}
cout << endl;
}
// 将数据分发到各个进程
int chunk_size = n / size;
vector<int> local_data(chunk_size);
MPI_Scatter(data.data(), chunk_size, MPI_INT, local_data.data(), chunk_size, MPI_INT, 0, MPI_COMM_WORLD);
// 局部排序
local_data = mergeSort(local_data, threshold);
// 归并结果
vector<int> sorted_data(n);
MPI_Gather(local_data.data(), chunk_size, MPI_INT, sorted_data.data(), chunk_size, MPI_INT, 0, MPI_COMM_WORLD);
// 根进程合并最终结果
if (rank == 0) {
sorted_data = mergeSort(sorted_data, threshold);
// 打印排序结果
cout << "排序后的数组: ";
for (int i = 0; i < n; i++) {
cout << sorted_data[i] << " ";
}
cout << endl;
}
int namelen;
char processor_name[MPI_MAX_PROCESSOR_NAME];
MPI_Get_processor_name(processor_name, &namelen);
// 给出主机名称
if (rank == 0) {
MPI_Get_processor_name(processor_name, &namelen);
printf("%s: Hello world from process %d\n", processor_name, rank);
cout << endl;
}
MPI_Finalize();
return 0;
}