【上海大学计算机系统结构实验报告】多机环境下MPI并行编程

实验目的

  1. 学习编制多进程并行程序实现如下功能:
    1. 创建多进程,输出进程号和进程数。
    2. 运行多进程并行例子程序。
    3. 编程实现大规模矩阵的并行计算。

实验过程及结果分析

实验环境

  • 操作系统:Ubuntu 20.04
  • 开发工具:GCC 9.3.0、OpenMPI 4.0.3

实验步骤

多主机无密码登录配置
  1. 在任意一台主机上生成RSA密钥对:

    ssh-keygen -t rsa -C "Kevin"
    

​ 该命令将在用户主目录下的 ~/.ssh/ 目录中生成id_rsaid_rsa.pub两个文件。

  1. 将生成的公钥内容追加至同目录下的 authorized_keys 文件中,授权本主机信任该密钥登录:

    cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
    

    此时,将该 authorized_keys 文件拷贝到其他主机,以建立互信。

  2. 为便于多主机通信与配置,使用以下命令将三台主机分别命名为 masterslave1slave2

    sudo vim /etc/hostname
    
  3. 编辑 /etc/hosts 文件,添加各主机的IP与主机名映射,例如:

    192.168.1.100 master
    192.168.1.101 slave1
    192.168.1.102 slave2
    
  4. 重启三台主机,而后使用ssh [username]命令测试主机间的无密码登录是否生效。

安装MPI环境
  1. 在三台主机上分别执行以下命令安装MPI环境:

    sudo apt install openmpi-bin libopenmpi-dev
    
  2. 验证安装是否成功:

    mpicc --version
    mpirun --version
    
    
图 1 安装成功后会显示版本信息
配置NFS共享目录

​ 在多主机运行MPI程序时,所有节点需要访问相同的可执行文件和输入输出路径。如果每台主机都独立保存一份代码和数据,会导致维护成本较高且容易出错。因此,此次实验使用NFS(网络文件系统)在master节点上创建共享目录,并将其挂载到所有计算节点,从而确保各节点读取到的是同一份程序和数据。

​ 具体步骤如下:

  1. master节点上配置NFS服务:

    1. 安装NFS服务端:

      sudo apt install nfs-kernel-server
      
      
    2. 创建共享目录并设置权限:

      sudo mkdir -p /home/ubuntu/shared
      sudo chown -R ubuntu:ubuntu /home/ubuntu/shared
      
      
    3. /etc/exports文件中添加:

      /home/ubuntu/shared *(rw,sync,no_subtree_check)
      
      
    4. 重启NFS服务使配置生效:

      sudo exportfs -a
      sudo systemctl restart nfs-kernel-server
      
      
  2. slave1slave2节点上挂载共享目录:

    1. 安装NFS客户端:

      sudo apt install nfs-common
      
      
    2. 创建本地挂载点:

      sudo mkdir -p /home/ubuntu/shared
      
      
    3. 挂载共享目录:

      sudo mount master:/home/ubuntu/shared /home/ubuntu/shared
      

​ 挂载完成后,从节点可直接访问/home/ubuntu/shared目录,并与master节点保持实时同步。

MPI程序测试
单主机多进程测试

​ 在master节点上编译运行下面的MPI程序,验证并行能力,如果主机的核心数不够,可添加 --oversubscribe 参数从而允许多个进程共享核心。

#include <mpi.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    MPI_Init(&argc, &argv);
    int world_rank;
    MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
    int world_size;
    MPI_Comm_size(MPI_COMM_WORLD, &world_size);
    printf("Hello from rank %d out of %d processors\n", world_rank, world_size);
    MPI_Finalize();
    return 0;
}

​ 运行结果:

图 2 单主机多进程测试结果
多主机多进程测试
  1. master节点创建主机清单文件,在其中设置每个节点的slots数:
	master slots=2
	slave1 slots=2
	slave2 slots=2
  1. 使用--hostfile参数指定三台主机运行:

    mpirun --hostfile hosts -np 6 ./mpi_hello
    

    运行结果:

图 3 多主机多进程测试结果
大规模矩阵并行计算测试

​ 在共享目录中编译并运行下面的矩阵乘法程序 :

#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
#include <string.h>
#include <time.h>

#define MASTER 0

void MatrixGenerate(double *mat, int size) {
    for (int i = 0; i < size * size; ++i)
        mat[i] = (double)rand() / RAND_MAX;
}

void LocalMatrixMultiply(double *a_local, double *b, double *c_local, int local_rows, int size) {
    for (int i = 0; i < local_rows; ++i)
        for (int j = 0; j < size; ++j) {
            double sum = 0.0;
            for (int k = 0; k < size; ++k)
                sum += a_local[i * size + k] * b[k * size + j];
            c_local[i * size + j] = sum;
        }
}

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);

    int test_sizes[] = {1000, 2000, 3000};
    int num_tests = sizeof(test_sizes) / sizeof(int);

    for (int test = 0; test < num_tests; ++test) {
        int matrix_size = test_sizes[test];
        int rows_per_proc = matrix_size / size;
        int remaining = matrix_size % size;
        int local_rows = rows_per_proc + (rank < remaining ? 1 : 0);
        int offset = rank * rows_per_proc + (rank < remaining ? rank : remaining);

        double *A = NULL, *B = NULL, *C = NULL;
        double *A_local = (double *)malloc(local_rows * matrix_size * sizeof(double));
        double *C_local = (double *)malloc(local_rows * matrix_size * sizeof(double));
        B = (double *)malloc(matrix_size * matrix_size * sizeof(double));

        if (rank == MASTER) {
            A = (double *)malloc(matrix_size * matrix_size * sizeof(double));
            C = (double *)malloc(matrix_size * matrix_size * sizeof(double));
            srand(time(NULL) + test);  // 避免相同种子
            MatrixGenerate(A, matrix_size);
            MatrixGenerate(B, matrix_size);
        }

        // 广播 B 矩阵
        MPI_Bcast(B, matrix_size * matrix_size, MPI_DOUBLE, MASTER, MPI_COMM_WORLD);

        // 发送 A 子矩阵
        if (rank == MASTER) {
            int pos = 0;
            for (int i = 0; i < size; ++i) {
                int send_rows = rows_per_proc + (i < remaining ? 1 : 0);
                if (i == MASTER) {
                    memcpy(A_local, A + pos * matrix_size, send_rows * matrix_size * sizeof(double));
                } else {
                    MPI_Send(A + pos * matrix_size, send_rows * matrix_size, MPI_DOUBLE, i, 0, MPI_COMM_WORLD);
                }
                pos += send_rows;
            }
        } else {
            MPI_Recv(A_local, local_rows * matrix_size, MPI_DOUBLE, MASTER, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
        }

        // 开始计时并进行乘法
        double start = MPI_Wtime();
        LocalMatrixMultiply(A_local, B, C_local, local_rows, matrix_size);
        double end = MPI_Wtime();

        // 收集结果
        if (rank == MASTER) {
            int pos = 0;
            memcpy(C + pos * matrix_size, C_local, local_rows * matrix_size * sizeof(double));
            pos += local_rows;
            for (int i = 1; i < size; ++i) {
                int recv_rows = rows_per_proc + (i < remaining ? 1 : 0);
                MPI_Recv(C + pos * matrix_size, recv_rows * matrix_size, MPI_DOUBLE, i, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
                pos += recv_rows;
            }
            printf("Matrix Size %dx%d, Time = %.3f seconds\n", matrix_size, matrix_size, end - start);
        } else {
            MPI_Send(C_local, local_rows * matrix_size, MPI_DOUBLE, MASTER, 1, MPI_COMM_WORLD);
        }

        // 清理内存
        free(A_local); free(C_local); free(B);
        if (rank == MASTER) {
            free(A); free(C);
        }
    }

​ 单主机运行结果:

图 4 单主机矩阵运算结果

​ 多主机运行结果:

图 5 多主机矩阵运算结果

​ 可以看到,随着任务规模扩大,多主机MPI并行计算在性能上展现出明显优势。

问题分析

​ 初次运行程序时,可能出现通信错误:

ubuntu@master:~/shared$ make runs
mpirun --hostfile hosts -np 6 ./mpi_matrix
[slave1][[55832,1],2][btl_tcp_endpoint.c:625:mca_btl_tcp_endpoint_recv_connect_ack] received unexpected process identifier [[55832,1],3]

​ 经排查发现,原因在于通过OpenMPI进行多主机运行时,OpenMPI会寻找主机之前的所有IP接口,但是程序实际上不会用到所有的IP接口,从而发生运行时阻塞或连接被拒绝得问题。所以此时需要通过--mca btl_tcp_if_include 参数来限制网络接口。

mpirun --hostfile hosts --mca btl_tcp_if_include eth0 -np 6 ./mpi_matrix

​ 通过显式指定接口为eth0后,问题得以解决。

总结

​ 通过本次实验,我系统掌握了分布式MPI环境的搭建流程,也进一步巩固了SSH无密码登录以及NFS共享配置的方法。实验中通过多主机MPI程序运行验证了并行计算在大规模数据处理中的高效性。这让我联想到在数据库系统中也有通过部署大规模集群来实现高并发访问和海量数据处理的能力,不过我并不确定这两者之间的原理是否相同,希望以后有机会能进一步学习其中的原理。

评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kutbas

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值