MPI(Message Passing Interface)是一种用于编写跨多个计算节点并行应用程序的标准协议。它定义了一组用于并行计算的消息传递标准接口,允许程序通过消息传递的方式在不同的进程间进行通信和同步,这使得MPI非常适合于分布式内存系统上的大规模并行计算任务,广泛应用于高性能计算(HPC)领域。

主要特点

  • 可移植性:MPI标准被设计为与平台无关,这意味着使用MPI编写的程序可以在支持MPI的各种平台上运行,无论是超级计算机还是集群。
  • 灵活性:MPI提供了丰富的通信模式,包括点对点通信(如发送/接收消息)和集体通信(如广播、归约等),可以满足不同类型并行算法的需求。
  • 效率:尽管是基于消息传递的模型,现代MPI实现针对高性能进行了高度优化,能够提供接近硬件极限的性能表现。
  • 消息传递模型:进程通过发送和接收消息进行通信

  • 分布式内存:每个进程有独立的地址空间

  • 显式并行化:程序员完全控制并行行为

基本组件

  • 进程(Process):MPI程序的基本执行单位,在MPI中,每个实例都是一个独立的进程,它们可能分布在不同的计算节点上

  • 通信器(Communicator):定义一组可以互相通信的进程(如MPI_COMM_WORLD),是一个抽象的概念,用来定义一组可以互相通信的进程集合。最常用的通信子是MPI_COMM_WORLD,它包含了所有参与MPI程序执行的进程。

  • 秩(Rank):在通信器内唯一标识进程的整数(0到N-1),每个进程在其所属的通信子中有唯一的标识符,称为rank。Rank通常从0开始编号,并用于指定源或目标进程。

MPI编程基础

基本函数

MPI_Init(&argc, &argv);       // 初始化MPI环境
MPI_Comm_size(comm, &size);   // 获取通信器中的进程总数
MPI_Comm_rank(comm, &rank);   // 获取当前进程的秩
MPI_Finalize();               // 终止MPI环境

点对点通信

// 阻塞式发送和接收
MPI_Send(buf, count, datatype, dest, tag, comm);
MPI_Recv(buf, count, datatype, source, tag, comm, &status);

// 非阻塞式通信
MPI_Isend(buf, count, datatype, dest, tag, comm, &request);
MPI_Irecv(buf, count, datatype, source, tag, comm, &request);
MPI_Wait(&request, &status);  // 等待通信完成

集体通信

MPI_Bcast(buf, count, datatype, root, comm);  // 广播
MPI_Scatter(sendbuf, sendcount, sendtype, 
            recvbuf, recvcount, recvtype, 
            root, comm);  // 分散数据
MPI_Gather(sendbuf, sendcount, sendtype, 
           recvbuf, recvcount, recvtype, 
           root, comm);  // 聚集数据
MPI_Reduce(sendbuf, recvbuf, count, datatype, 
           op, root, comm);  // 归约操作
MPI_Allreduce(sendbuf, recvbuf, count, datatype, 
              op, comm);  // 全归约
MPI_Barrier(comm);  // 同步所有进程

MPI高级特性

派生数据类型

MPI_Datatype newtype;
MPI_Type_contiguous(count, oldtype, &newtype);
MPI_Type_commit(&newtype);
// 使用后需要释放
MPI_Type_free(&newtype);

进程拓扑

int dims[2] = {2, 3};  // 2x3网格
int periods[2] = {0, 0};  // 非周期性边界
MPI_Comm cart_comm;
MPI_Cart_create(MPI_COMM_WORLD, 2, dims, periods, 0, &cart_comm);

// 获取坐标和邻居信息
int coords[2];
MPI_Cart_coords(cart_comm, rank, 2, coords);
int left, right;
MPI_Cart_shift(cart_comm, 0, 1, &left, &right);  // 获取左右邻居

单边通信(RMA)

MPI_Win win;
double *local_buf;
MPI_Win_create(local_buf, size, sizeof(double), 
               MPI_INFO_NULL, MPI_COMM_WORLD, &win);

// 远程内存访问
MPI_Win_lock(MPI_LOCK_SHARED, target_rank, 0, win);
MPI_Get(remote_buf, count, MPI_DOUBLE, 
        target_rank, offset, count, MPI_DOUBLE, win);
MPI_Win_unlock(target_rank, win);

MPI_Win_free(&win);  // 释放窗口

MPI实践示例

矩阵乘法

// 矩阵分块并行乘法
void matrix_multiply(double *A, double *B, double *C, 
                    int block_size, MPI_Comm comm) {
    int rank, size;
    MPI_Comm_rank(comm, &rank);
    MPI_Comm_size(comm, &size);
    
    // 创建网格拓扑
    int dims[2] = {0, 0};
    MPI_Dims_create(size, 2, dims);
    int periods[2] = {1, 1};  // 周期性边界
    MPI_Comm cart_comm;
    MPI_Cart_create(comm, 2, dims, periods, 0, &cart_comm);
    
    // 获取进程坐标
    int coords[2];
    MPI_Cart_coords(cart_comm, rank, 2, coords);
    
    // 矩阵分块和通信...
    // 计算局部块乘积...
    // 收集结果...
    
    MPI_Comm_free(&cart_comm);
}

并行π计算

double compute_pi(int num_steps) {
    int rank, size;
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    
    double step = 1.0 / (double)num_steps;
    double sum = 0.0;
    
    // 每个进程计算自己的部分
    for (int i = rank; i < num_steps; i += size) {
        double x = (i + 0.5) * step;
        sum += 4.0 / (1.0 + x * x);
    }
    
    double pi;
    MPI_Reduce(&sum, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
    
    if (rank == 0) {
        pi *= step;
        return pi;
    }
    return 0.0;
}

MPI性能优化

  1. 通信与计算重叠:使用非阻塞通信

  2. 减少通信量:优化数据布局,使用派生数据类型

  3. 负载均衡:合理分配计算任务

  4. 集体通信选择:根据情况选择最优的集体通信算法

  5. 使用最新MPI特性:如MPI-3的单边通信和共享内存特性

MPI实现与工具

  • 主流实现

    • OpenMPI (开源)

    • MPICH (开源)

    • Intel MPI (商业)

    • Microsoft MPI (Windows平台)

  • 调试工具

    • TotalView

    • DDT

    • MPI并行调试器

  • 性能分析工具

    • Vampir

    • TAU

    • Score-P

    • MPI自带性能分析接口(PMPI)

      MPI作为高性能计算领域最广泛使用的消息传递标准,提供了丰富的功能和良好的可移植性。掌握MPI对于开发大规模并行应用至关重要。

 

简单示例

编写MPI代码(vs2017开发)

现在可以编写你的MPI程序了。下面是一个简单的例子:

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

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

    printf("Hello from process %d of %d\n", rank, size);

    MPI_Finalize();
    return 0;
}

运行MPI程序

     为了运行MPI程序,不能直接按F5或Ctrl+F5来启动调试或运行。需要使用命令行工具mpiexec来启动你的程序。

  1. 打开命令提示符(CMD),确保你的程序已经编译完成,并且.exe文件位于你知道的位置。
  2. 使用mpiexec命令来运行你的程序。例如,如果想以4个进程运行你的程序,可以这样执行:
    mpiexec -n 4 path\to\your\program.exe
    path\to\your\program.exe替换为你的可执行文件的实际路径。

 

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐