MPI并行实现归并排序

实现内容

实现不完整的并行归并排序,初始待排序元素数量为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;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值