并行与分布式计算导论(六)MPI入门

Section 6 MPI 入门

吐槽:罗老师的教学逻辑实在是太混乱了,看他的PPT要花比学教材多两倍的时间,学到教材里一半的知识(总量多,可那种组织形式进到脑子有效值就一半)。

6.1 Why MPI?

(因为要写作业)

6.1.1 MPI与OpenMP

MPI的目标是高性能,大规模性,和可移植性。

先前我们介绍的OpenMP主要是基于共享内存的并行系统,适合多核CPU上的并行程序开发。而MPI是多主机联网协作的工具(当然也可以相对低效地用于单主机上的多核计算)。

尽管MPI的编写相对麻烦烦,由于使用了进程间通讯的方式协调导致并行效率相对低一些,但因其可以协调主机间的并行计算,所以在并行规模上的可伸缩性极强——从PC到超级计算机均可。

总而言之,OpenMP是为单主机上的多线程计算而设计的(仅能在单台主机工作),而MPI是为多主机协作设计的。

6.1.2 为什么要了解MPI

MPI是最重要的MP library标准。尽管现在可能有一些更加方便更加modern的library,学习MPI仍然有助于使你更好的了解collective communication(组通信)的概念

6.2 MPI基础讲解

先来个HelloWorld吧

#include <mpi.h>
int main(int argc, char *argv[])
{
    int npes, myrank;
    MPI_Init(&argc, &argv);//初始化MPI
    MPI_Comm_size(MPI_COMM_WORLD, &npes);//npes获得通信域中的进程总数
    MPI_Comm_rank(MPI_COMM_WORLD, &myrank);//myrank获得本进程的序号
    printf("From process %d out of %d, Hello World!\n",myrank, npes);
    MPI_Finalize();//结束MPI
    return 0;
}

与OpenMP不同,MPI并不拆解某个代码块的任务,而是程序员将任务拆分完成后,指挥不同处理器执行的工具。所有的处理器都将执行相同的代码,区分具体任务形式的往往是进程的序号

  • 例如,筛法求素数的时候,将长为1000数组均分为10段,第k号进程处理第k段,即下标从 ( k − 1 ) ∗ 100 + 1 (k-1)*100+1 (k1)100+1 k ∗ 100 k*100 k100的部分

6.2.1 头文件

#include<mpi.h>

6.2.2 基本库函数

int MPI_Init(int*argc,char***argv);//初始化MPI环境,必须出现在其他MPI函数之前
int MPI_Finalize();//结束MPI环境(执行各种clean-up tasks)
int MPI_Comm_size(MPI_COMM comm, int *size);//size对应位置获得通信域中的进程总数
int MPI_Comm_rank(MPI_COMM comm, int *rank);//rank对应位置获得当前进程的序号

(所谓的通讯域就是能相互传递消息的处理器形成的一个集合)

6.2.3 编译

mpicc -o name example.c
mpic++ -o name example.cpp

将example中的代码编译并将可执行程序存入name文件

6.2.4 执行

指定进程数目

mpirun -np number name

生成number个进程来跑name

指定主机处理器

需要时考虑使用 --host 和 --hostfile

Multiple Program Multiple Data(MPMD)

mpirun-np 1 master : -np 7 slave

上例中,cmd命令为两种完全不同的执行程序展开共八个进程,这些执行程序将处于同一通信域中(即可以通过MPI彼此通信)

6.2.5 Send & Receive

Non-buffered blocking

对于信息传递来说,最简单(也是最安全)的收发模式就是sender和receiver在确定彼此状态后才能执行。

sender将发送send请求,receiver将发送准许请求的消息,只有两者接上了,才会发送data。也就是说,不管是receiver还是sender,先执行的都要等待后执行的。

所谓的non-buffered blocking就是指

  • sender开始执行后,必须等待receiver准许进行sending,获得准许后,sender完成sending才能返回,从sender开始到返回这段时间原进程在sender处停滞
  • receiver开始执行后,只有等待sender准备好发送请求,receiver完成receiving才能返回,从receiver开始到返回这段时间原进程在receiver处停滞

缺点:将产生大量的idling开销(除非两者精确的同时执行)

buffered blocking

还有一种收发模式:sender直接将data复制到接受进程的缓冲区,receiver直接从本进程缓冲区复制走data

缓存(buffering)实际上是把空闲开销(idling overhead)替换成了缓冲存取开销(buffer copying overhead)。

  • 当接收端没有相关通信硬件时,sender将打断接收端的进程,把data复制到接收进程的缓冲区
  • 接收端肯定还是需要等待发送端把东西发送过来才能继续

Non-blocking

non-blocking模式不管到底安全不安全,请求发送后就继续执行后续语句,准许发送后也去执行后续语句。当请求和准许match了,正式执行send和receive

  • 对于无缓冲的non-blocking模式,潜在的危险主要有两方面:发送端后续的程序可能会在发送前改变待发送的数据,从而导致正式发送的数据实际上是有错误的;接收端后续的程序可能会在接收前就访问待接收数据。

使用non-blocking模式需要程序员很好的理解和掌握程序的内容

Message Passing Libraries一般会同时提供blocking和non-blocking的原语

MPI的send模式

int MPI_Send(
    void *message,//被传送数据的起始地址
    int count,//被传送的数据项个数
    MPI_Datatype datatype,//被传送的数据类型
    int dest,//接收进程号
    int tag,//信息的标记(可以说明用途)
    MPI_Comm comm//通信域
);

遵从阻塞模式,只有发送缓冲区可以使用时(例如数据已被安全送到接收缓冲区或中间缓冲区)才返回。一般来说,系统会把这些消息复制到系统缓冲区。

其他一些可以考虑的发送方式(了解有相关方法即可,具体使用时再考虑细节)

  • MPI_Send:
  • MPI_Bsend:立刻返回
  • MPI_Ssend:匹配到了接收器才返回
  • MPI_Rsend:只有确定对应接收器一定早已待命才会使用这个语句;在这种特殊情况下,这个sender会给系统提供一些信息,从而可以减少一些开销
  • MPI_Isend:遵从非阻塞模式,但不能立刻再次使用发送缓冲区

MPI的receive模式

int MPI_Recv( 
    void *message,//要被存放的起始位置
    int count,//能接收的最大数目
    MPI_Datatype datatype,//接收的数据类型
    int source,//发送进程号
    int tag,//信息的标记(可以说明用途)
    MPI_Comm comm,//通信域
    MPI_Status *status//一条类型为MPI_Status的记录,Recv完成后,可以访问status获得函数相关的状态信息
);

特别说明,从status中可以获得下面几项内容

  • status->MPI_source 发送进程号
  • status->MPI_tag 接收到的消息的tag
  • status->MPI_ERROR 错误情况
为什么要有status呢?
  • MPI_Recv中的source一般设置为常数MPI_ANY_SOURCE,从而接收任意来源的信息
  • MPI_Recv中的tag一般设置为常数MPI_ANY_TAG,从而接收任意tag的信息

在这种不对发送来源和发送标签做限制的情况下,需要查探状态记录(status)才能确定某特定信息的发送进程或者tag值

此外,还有一个辅助函数

int MPI_Get_count(MPI_Status* status,MPI_Datatype datatype,int* count);

该辅助函数可以给出具体接收到的数据条目数
(接收函数仅给由接收端给出了最大容量,所以确定具体接收到的数据数目也是一个需要解决的事情)

猜你喜欢

转载自blog.csdn.net/Kaiser_syndrom/article/details/105361466