【RDMA】MPI 和MPI在 IB, Omni-Path, Ethernet/iWARP, 和RoCE上的性能

MPI(Message Passing Interface)是一种标准,用于并行计算中的消息传递编程。它包含上百个函数调用,支持C和Fortran语言。MPI的特点是消息传递式并行编程,适合大规模可扩展算法。常用函数包括MPI_Init、MPI_Comm_size、MPI_Comm_rank等。MPI通信机制有标准模式、缓冲模式、同步模式、就绪模式等,提供了阻塞和非阻塞两种通信方式。MPI_Bcast和MPI_Gather是聚合通信的示例。随着计算、存储和网络的发展,MPI在计算密集型任务上仍占优势,但在易用性和生态系统方面相对较弱。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MPI教程

MPI编程1小时入门:https://www.bilibili.com/video/BV1zo4y1j7An

MPI Tutorial --https://mpitutorial.com/tutorials/

MPI在不同网络上的性能对比

MPI 在 InfiniBand,、Omni-Path,、Ethernet/iWARP、RoCE 上的测试结果

MVAPICH :: Performance -- http://mvapich.cse.ohio-state.edu/performance/pt_to_pt/

什么是SPMD

SPMD(Single Program Multiple Data)​ 是一种并行计算模型,其核心思想是多个计算节点(或处理器)运行同一份程序代码,但处理不同的数据集

什么是MPI

推荐:https://zhuanlan.zhihu.com/p/158584571

MPI(Message Passing Interface)​ 是一种消息传递协议,用于实现多进程或多节点之间的通信与协作。

  • 原理
    • 基于分布式内存模型,各节点拥有独立内存,通过显式发送/接收消息协作。
    • 提供点对点通信(如MPI_Send/MPI_Recv)、集合通信(如广播MPI_Bcast)和同步操作(如路障MPI_Barrier)。

MPI是一种标准或规范的代表,而不是特指某一个对它的具体实现
MPI是一种消息传递编程模型,并成为这种编程模型的代表和事实上的标准


 

二、MPI的基本函数
MPI调用借口的总数虽然庞大,但根据实际编写MPI的经验,常用的MPI函数是以下6个:

MPI_Init(…);
MPI_Comm_size(…);
MPI_Comm_rank(…);
MPI_Send(…);
MPI_Recv(…);
MPI_Finalize();

原文链接:https://blog.youkuaiyun.com/qq_40765537/article/details/106425355

更多见后面章节。

三、MPI的通信机制
MPI是一种基于消息传递的编程模型,不同进程间通过消息交换数据。

1.MPI点对点通信类型
所谓点对点的通信就是一个进程跟另一个进程的通信

MPI消息传递过程

在这里插入图片描述

(1)标准模式

在这里插入图片描述
该模式下MPI有可能先缓冲该消息,也可能直接发送,可理解为直接送信或通过邮局送信。是最常用的发送方式。

由MPI决定是否缓冲消息

没有足够的系统缓冲区时或出于性能的考虑,MPI可能进行直接拷贝:仅当相应的接收完成后,发送语句才能返回。
这里的系统缓冲区是指由MPI系统管理的缓冲区。而非进程管理的缓冲区。
MPI环境定义有三种缓冲区:应用缓冲区、系统缓冲区、用户向系统注册的通信用缓冲区

MPI缓冲消息:发送语句在相应的接收语句完成前返回。
这时后发送的结束或称发送的完成== 消息已从发送方发出,而不是滞留在发送方的系统缓冲区中。

该模式发送操作的成功与否依赖于接收操作,我们称之为非本地的,即发送操作的成功与否跟本地没关系。

(2)缓冲模式

可理解为通过邮局送信(应用缓冲区)。注意这里是应用缓冲区,和系统缓冲区不同。需要用户程序事先申请一块足够大的缓冲区,用户定义的缓冲区只能用于缓存模式,一个进程一次只能绑定一块用户缓冲区,通过MPI_Buffer_attch实现

发送是本地的: 完成不依赖于与其匹配的接收操作。发送的结束仅表明消息进入用户指定的缓冲区中。

通过MPI_Buffer_detach来回收申请的缓冲区,阻塞模式下该操作直到缓存区的数据传输完才返回。

缓冲模式在相匹配的接收未开始的情况下,总是将送出的消息放在缓冲区内,这样发送者可以很快地继续计算,然后由系统处理放在缓冲区中的消息。

占用额外内存,一次内存拷贝。

其函数调用形式为:MPI_BSEND(…)。B代表缓冲.

(3)同步模式

在这里插入图片描述
可理解为握手后才送出名片

本质特征:收方接收该消息的缓冲区已准备好,不需要附加的系统缓冲区

任意发出:发送请求可以不依赖于收方的匹配的接收请求而任意发出

成功结束:仅当收方已发出接收该消息的请求后才成功返回,否则将阻塞。

非本地的

可用于实现进程同步

其函数调用形式为:MPI_SSEND(…)。S代表同步

(4)就绪模式

在这里插入图片描述
可理解为有客户请求,才提供服务。

发送请求仅当有匹配的接收后才能发出,否则出错。在就绪模式下,系统默认与其相匹配的接收已经调用。接收必须先于发送。

它依赖于接收方的匹配的接收请求,不可以任意发出。

其函数调用形式为:MPI_RSEND(…)。R代表就绪 。

正常情况下可用标准模式替换,除可能影响性能外,不影响结果。

(5)点对点通信的阻塞性分析

上面的四种方式都有阻塞通信和非阻塞通信的两种版本

阻塞通信
就是按照上面的流程进程自己要等待指定的操作实际完成,或者所涉及的数据被MPI系统安全备份后,函数才返回后才能进行下一步的操作。
非阻塞通信

在这里插入图片描述

通信函数总是立即返回,实际操作由MPI后台进行,需要调用其它函数来查询通信是否完成,通信与计算可重叠。
因为阻塞通信时保证了数据的安全性,所以通信还是返回后,其他的函数或者语句能够对这些数据资源直接访问。

但是非阻塞通信函数立即返回,不能保证数据已经被传送出去或者被备份或者已经读入使用,所以即使你没有阻塞,后面的语句可以继续执行,如果你要操纵上面所说的数据,将会导致发送或接收产生错误。所以对于非阻塞操作,要先调用等待MPI_Wait()或测试MPI_Test()函数来结束或判断该请求,然后再向缓冲区中写入新内容或读取新内容。

所以非阻塞性的通信函数一般时用到后面的语句跟本函数数据无冲突的场景,可以提高效率。

如何使用两种版本?

发送语句的前缀由MPI_改为MPI_I, I指的是immediate,即可改为非阻塞,否则是阻塞。示例如下:

标准模式:MPI_Send(…)->MPI_Isend(…)

Buffer模式:MPI_Bsend(…)->MPI_Ibsend(…)

(6) 通信函数的返回和通信函数的完成、和通信的完成

通信的完成=发送函数的完成+接收函数的完成

在阻塞状态下通信函数的返回是在通信即将完成或者完成之后(根据不同的模式有不同设置,如缓冲模式发送函数的返回是在缓冲区的数据传输完之后返回)。在非阻塞状态下,通信函数立即返回。

下面讲讲通信函数的完成:

发送的完成: 代表发送缓冲区中的数据已送出,发送缓冲区可以重用。它并不代表数据已被接收方接收。数据有可能被缓冲;
这里有一个特例,就是同步模式,它的发送完成==接收方已初始化接收,数据将被接收方接收。

接收的完成:代表数据已经写入接收缓冲区。接收者可访问接收缓冲区,status对象已被释放。它并不代表相应的发送操作已结束(即操作函数可返回)。

通信函数的返回和通信函数完成是不一样的。

通过MPI_Wait()和MPI_Test()来判断通信是否已经完成;

(7) 非阻塞发送和接收

int MPI_Isend(void* buf, int count, MPI_Datatype datatype, int dest, int tag, 
                                MPI_Comm comm, MPI_Request *request)
                                
    IN buf 发送缓冲区的起始地址
    IN count 发送缓冲区的大小(发送元素个数)
    IN datatype 发送缓冲区数据的数据类型
    IN dest 目的进程的rank值
    IN tag 消息标签
    IN comm 通信空间/通信域
    OUT request  非阻塞通信完成对象(句柄)
    MPI_Ibsend/MPI_Issend/MPI_Irsend:非阻塞缓冲模式/非阻塞同步模式/非阻塞就绪模式

int MPI_Irecv(void* buf, int count, MPI_Datatype datatype, int source, int tag,
                MPI_Comm comm, MPI_Request* request)

(8)消息探测

MPI_Probe()和MPI_Iprobe()函数探测接收消息的内容。用户根据探测到的消息内容决定如何接收这些消息,如根据消息大小分配缓冲区等。前者为阻塞方式,即只有探测到匹配的消息才返回;后者为非阻塞,即无论探测到与否均立即返回.

int MPI_Probe(int source, int tag, MPI_Comm comm, MPI_Status* status)
int MPI_Iprobe(int source, int tag, MPI_Comm comm, int*flag, MPI_Status* status)
IN source 数据源的rank,可以是MPI_ANY_SOURCE
IN tag      数据标签,可以是MPI_ANY_TAG
IN comm  通信空间/通信域
OUT flag   布尔值,表示探测到与否(只用于非阻塞方式)
OUT status status对象,包含探测到消息的内容

7
(8)通信检查

int MPI_Wait(MPI_Request* request, MPI_Status * status) 
通信检测函数:必须等待指定的通信请求完成后才能返回,成功返回时,
status 中包含关于所完成的通信的消息,相应的通信请求被释放,即request 被置成MPI_REQUEST_NULL
int MPI_Test(MPI_Request *request,int *flag, MPI_Status *status
通信检测函数:不论通信是否完成都立刻返回,flag为1表示通信完成

其他通信检测函数

int MPI_Waitany(int count,MPI_Request *array_of_requests,int *index, MPI_Status *status)
int MPI_Waitall(int count,MPI_Request *array_of_requests,MPI_Status *array_of_statuses)
int MPI_Waitsome(int incount,MPI_Request *array_of_requests,int *outcount,
                 int *array_of_indices,MPI_Status *array_of_statuses)
int MPI_Testany(int count,MPI_Request *array_of_requests,int *index, int *flag,MPI_Status *status)
int MPI_Testall(int count,MPI_Request *array_of_requests,int *flag,MPI_Status *array_of_statuses)
int MPI_Testsome(int incount,MPI_Request *array_of_requests,int *outcount,
                int *array_of_indices,MPI_Status *array_of_statuses)

(9)请求释放和请求撤销

MPI_Request_free(MPI_Request request)

释放指定的通信请求(及所占用的内存资源)

若该通信请求相关联的通信操作尚未完成,则等待通信的完成,因此通信请求的释放并不影响该通信的完成

该函数成功返回后request 被置为MPI_REQUEST_NULL

一旦执行了释放操作,该通信请求就无法再通过其它任何的调用访问

MPI_Cancel(MPI_Request request)
非阻塞型,用于取消一个尚未完成的通信请求
在MPI系统中设置一个取消该通信请求的标志后立即返回,具体的取消操作由MPI系统在后台完成。
MPI_CANCEL允许取消已调用的通信请求,但并不意味着相应的通信一定会被取消:若相应的通信请求已经开始,则它会正常完成,不受取消操作的影响;若相应的通信请求还没开始,则可以释放通信占用的资源。
调用MPI_CANCEL 后,仍需用MPI_WAIT,MPI_TEST或MPI_REQUEST_FREE 来释放该通信请求。

MPI_Test_cancelled(MPI_Status status,int *flag)
检测通信请求是否被取消

(9)MPI点对点通信的安全性

进程死锁

在这里插入图片描述
不安全的通信

在这里插入图片描述


这里之所以会不安全,是因为两个进程都需要buffer。但仅仅只有两个进程时,程序不会发生问题,因为系统缓冲区够用,但是当有100000个进程的时候就会出现死锁。比如现在A,B是刚刚进入的线程,前面的进程使得系统缓冲区已经满了,A,B都被阻塞,因此C、D也会被阻塞,这两个程序就会陷入死锁。

安全的通信

在这里插入图片描述

2.MPI聚合通信
上面是点对点通信,聚合通信就是包括了一对多,多对一和多对多的通信方式。

在这里插入图片描述

(1) MPI_Bcast
函数原型
int MPI_Bcast (
 void buffer, /* 发送/接收buf/
 int count, /*元素个数*/
 MPI_Datatype datatype,
 int root, /*指定根进程*/
 MPI_Comm comm)
根进程既是发送缓冲区也是接收缓冲区

原理图

示例:

int p, myrank; 
float buf;
MPI_Comm comm;
MPI_Init(&argc, &argv);
/*得进程编号*/
MPI_Comm_rank(comm, &my_rank);
/* 得进程总数 */
MPI_Comm_size(comm, &p);
if(myrank==0)
       buf = 1.0;
MPI_Bcast(&buf,1,MPI_FLOAT,0, comm);

(2) MPI_Gather

在这里插入图片描述

Root进程从n个进程的每一个接收各个进程(包括它自已)的消息. 这n个消息的连接按序号rank进行, 存放在Root进程的接收缓冲中.

int MPI_Gather ( 
        void *sendbuf,
        int sendcnt,
        MPI_Datatype sendtype, 
        void *recvbuf, 
        int recvcount,
        MPI_Datatype recvtype, 
        int root, 
        MPI_Comm comm )
 


int p, myrank; 
float data[10];/*分布变量*/
float* buf;
MPI_Comm comm;
MPI_Init(&argc, &argv);
/*得进程编号*/
MPI_Comm_rank(comm,&my_rank);
/* 得进程总数 */
MPI_Comm_size(comm, &p);
if(myrank==0)
    buf=(float*)malloc(p*10*sizeof(float);/*开辟接收缓冲区*/

MPI_Gather(data,10,MPI_FLOAT,
    buf,10,MPI_FlOAT,0,comm);

想要了解更多函数的用法,可以到官方文档查看。
同时如果想要练习这些函数的用法,可以到超算习堂进行练习。
————————————————
版权声明:本文为优快云博主「JacksonKim」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请
原文链接:https://blog.youkuaiyun.com/qq_40765537/article/details/106425355

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值