MPI-Note[2]: 概述和基本通信

学习MPI主要参考的是如下这本书:

MPI-Note[2]: 概述和基本通信

当然我也会参考一些中科大的文档:

  • https://scc.ustc.edu.cn/zlsc/cxyy/200910/MPICH/
  • https://scc.ustc.edu.cn/389/list.htm

简介

分布式计算误区

其实一直吐槽很多做计算的人不懂网络,大概也就是作者所说的如下几种误区:

  1. the network is reliable,
  2. the latency is zero,
  3. the bandwidth is infinite (or at least large enough),
  4. the network is safe,
  5. the network topology does not change (meaning it is static over time),
  6. there is only one network administrator,
  7. the transport cost is free, and
  8. the network is homogeneous.

很多时候网络为了保证可靠那么就要增加延迟, 虽然用的是光纤,但是光在光纤中传输的速度并不是光速,折合一米有1ns左右的时延,低延迟和高带宽通常不可兼得。很多计算任务都要面临网络异构和拓扑因失效或者升级带来的变化,或者针对不同的计算任务设计不同的拓扑,例如3D-Torus一类的,而且通常由于多个系统管理网络,需要很好的容错性设计。

这些东西都是应用侧的人看不见的,应用侧能搞懂网关和子网掩码就很不错了,毕竟术业有专攻。

MPI

传统的冯诺依曼架构面临着内存墙的瓶颈,因此需要一些In-Memory计算的架构:

MPI-Note[2]: 概述和基本通信

而大量的计算节点,互相通信协作的机制就是Message Passing Interface,也就是MPI。MPI有MPICH、MVAPICH、OpenMPI、Intel MPI等多种实现,

本文系列先采用学习资料推荐的OpenMPI来做,后面再看情况修改。然后编程语言来看,还是以C为主,后期可能看看是否玩一下python的mpi库,毕竟我可以把自己的一些基金量化计算的库Offload到集群上。

MPI原语

MPI有几个基本的原语(Primitives):

  • BroadCast: 主要是将相同的消息分发到其它节点,例如Node-0有数据M, 需要将其分发到Node-1Node-N
  • Gather: 这种模式是指Node-1有一份数据M1,Node-2有一份数据M2Node-n有一份数据Mn, 这些数据需要完全收集到Node-0上的过程被称为Gather
  • Scatter: 这个模式正好和Gather相反,由Node-0按照一定的规则将M1~Mn分发到Node-1 ~ Node-n的过程
  • Reduce : 它主要是通过执行一个满足交换律的二元算子来实现的,最简单的算子是求和(MPI_SUM)和求积(MPI_PROD). 例如MPI_SUM(M1~Mn). 其它常见的算子如下:
MPI name Meaning
MPI_MAX Maximum
MPI_MIN Minimum
MPI_SUM Sum
MPI_PROD Product
MPI_LAND Logical and
MPI_BAND Bit-wise and
MPI_LOR Logical or
MPI_BOR Bit-wise or
MPI_LXOR Logical xor
MPI_BXOR Bit-wise xor
MPI_MAXLOC Max value and location
MPI_MINLOC Min value and location

*思考:为啥没有减法和除法,当然减法好说,加负数就好,除法则是不满足交换律,因此会有执行顺序的考虑。

  • Total Exchange: 即MPI_ALLtoall, 例如Node-AA0~An , Node-BB0~Bn … 通过AlltoAll处理以后,Node-AA0~X0Node-BA1~X1 ….

点对点通信

一切通信的基础都是从最基本的点对点通信出发的。如下的很多内容你会发现和Go语言的Channel非常类似,其实Go的分布式实践很多就是参考了这样的架构。

支持的DataType

MPI通信时可以支持的数据类型如下所示:

MPI datatype C datatype
MPI_CHAR signed char
MPI_SHORT signed short int
MPI_INT signed int
MPI_LONG signed long int
MPI_UNSIGNED_CHAR unsigned char
MPI_UNSIGNED_SHORT unsigned short int
MPI_UNSIGNED unsigned int
MPI_UNSIGNED_LONG unsigned long int
MPI_FLOAT float
MPI_DOUBLE double
MPI_LONG_DOUBLE long double
MPI_BYTE
MPI_PACKED

阻塞通信

  • send(&data, n, Pdest): 从地址&data开始的n个数据发送到Pdest
  • recv(&data,n, Psrc): 从Psrc接收n个数据,并按顺序从&data地址往后写入

阻塞通信模式使用的函数 MPI_Send、MPI_Recv如下:

int MPI_Send(const void *buf, int count, MPI_Datatype type, int dest,
             int tag, MPI_Comm comm)


int MPI_Recv(void *buf, int count, MPI_Datatype type, int source,
             int tag, MPI_Comm comm, MPI_Status *status)

阻塞通信在数据传输前都会有一个发送请求 确认通过得流程。

通信的消息包含几个内容:

  • source
  • destination
  • tag: 用于区分不同的消息
  • communicator : MPI_COMM_WORLD是由MPI提供包含所有节点

一个简单的通信示例如下:

 

编译执行:

zartbot@mpi1:~/mpi/study$ mpicc block_comm.c -o block
zartbot@mpi1:~/mpi/study$ mpirun -np 2 block
proccessor 0 send 1
proccessor 1 recv msg with tag:1001
Data:1 2 3 4 5

阻塞通信通常会有两种死锁状态:

两个同时都在发,或者两个都再等着收

 

非阻塞通信

阻塞通信因为有一个“请求、确认”的握手机制,因此需要等待执行,执行是“同步”的,另一种做法是异步的非阻塞通信。

非阻塞通信采用如下两个函数

int MPI_Isend(const void *buf, int count, MPI_Datatype type, int dest,
              int tag, MPI_Comm comm, MPI_Request *request)

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

然后需要注意,这些操作由于是非阻塞的, 因此后续代码可以继续执行,也就是说这样的操作是异步执行的,而后面有了一个MPI_Wait函数用于等待完成操作。

 
zartbot@mpi1:~/mpi/study$ mpicc non_block.c -o non_block
zartbot@mpi1:~/mpi/study$ mpirun -np 2 non_block
processor 0 sent 2021
processor 1 received 2021

原书的示例还是不太明显,我们再添加一个MPI_test函数,当*req操作执行完后,flag会被改写为1,否则返回0

int MPI_Test(MPI_Request *request, int *completed, MPI_Status *status)

测试代码如下:

 

执行结果由于非阻塞就有很多种可能了

zartbot@mpi1:~/mpi/study$ mpirun -np 2 non_block
Processor 0 MPI Test flag: 1
Processor 0 MPI Test flag: 1
processor 0 sent 2021
Processor 1 MPI Test flag: 1
Processor 1 MPI Test flag: 1
processor 1 received 2021

zartbot@mpi1:~/mpi/study$ mpirun -np 2 non_block
Processor 1 MPI Test flag: 0
Processor 0 MPI Test flag: 1
Processor 0 MPI Test flag: 1
processor 0 sent 2021
Processor 1 MPI Test flag: 1
processor 1 received 2021

zartbot@mpi1:~/mpi/study$ mpirun -np 2 non_block
Processor 0 MPI Test flag: 1
Processor 0 MPI Test flag: 1
processor 0 sent 2021
Processor 1 MPI Test flag: 0
Processor 1 MPI Test flag: 1
processor 1 received 2021

双向通信

很多场景需要同时进行双向的通信,因此MPI提供了如下函数:

 int MPI_Sendrecv(const void *sendbuf, int sendcount, MPI_Datatype sendtype,
        int dest, int sendtag, void *recvbuf, int recvcount,
        MPI_Datatype recvtype, int source, int recvtag,
        MPI_Comm comm, MPI_Status *status)

例如我们来看一个实例:

 

执行

zartbot@mpi1:~/mpi/study$ mpirun -np 3 sendrecv
[Before]Proccessor 0 Send Buf: 0        Recv Buf: -10000
[Before]Proccessor 0 Send Buf: 0        Recv Buf: -10000
[Before]Proccessor 0 Send Buf: 0        Recv Buf: -10000
[Before]Proccessor 0 Send Buf: 0        Recv Buf: -10000
[After]Proccessor 0 Send Buf: 0 Recv Buf: -10000
[After]Proccessor 0 Send Buf: 0 Recv Buf: -10000
[After]Proccessor 0 Send Buf: 0 Recv Buf: -10000
[After]Proccessor 0 Send Buf: 0 Recv Buf: -10000
[Before]Proccessor 1 Send Buf: 1        Recv Buf: -10000
[Before]Proccessor 1 Send Buf: 1        Recv Buf: -10000
[Before]Proccessor 1 Send Buf: 1        Recv Buf: -10000
[Before]Proccessor 1 Send Buf: 1        Recv Buf: -10000
[After]Proccessor 1 Send Buf: 1 Recv Buf: 0
[After]Proccessor 1 Send Buf: 1 Recv Buf: 0
[After]Proccessor 1 Send Buf: 1 Recv Buf: 0
[After]Proccessor 1 Send Buf: 1 Recv Buf: 0
[Before]Proccessor 2 Send Buf: 2        Recv Buf: -10000
[Before]Proccessor 2 Send Buf: 2        Recv Buf: -10000
[Before]Proccessor 2 Send Buf: 2        Recv Buf: -10000
[Before]Proccessor 2 Send Buf: 2        Recv Buf: -10000
[After]Proccessor 2 Send Buf: 2 Recv Buf: 1
[After]Proccessor 2 Send Buf: 2 Recv Buf: 1
[After]Proccessor 2 Send Buf: 2 Recv Buf: 1
[After]Proccessor 2 Send Buf: 2 Recv Buf: 1

MPI-Note[2]: 概述和基本通信》来自互联网,仅为收藏学习,如侵权请联系删除。本文URL:http://www.bookhoes.com/753.html