2205-MPI and timer
MPI 和 OpenMP 工作模式
OpenMP对于共享内存处理更优,但是没有通信的功能,所以MPI可以弥补这一缺点。
MPI快速上手
基本概念
关于进程和线程的说明我觉得英语的描述更为合适,可以非常清楚的说明这两个概念。
- 进程:A process is (traditionally) a program counter and address space.
- 线程:Processes may have multiple threads (program counters and associated stacks) sharing a single address space. MPI is for communication among processes, which have separate address spaces.
- 进程处理需要进行:
- 同步
- 数据通信
OpenMPI 和 MPICH
You can read following part later
首先需要认识到这两者不是一个东西,在接口实现上有细微差异。MPICH应该是最新MPI标准的高质量参考实施,以及衍生实施以满足特殊用途需求的基础。OpenMPI在使用和网络通信方面实现更加普通。大部分时候两者都可以使用,但是如果是在有IB通信的机器上,MPICH不可用。
一般使用时,最大的差异是使用 Hydra 的时候会有差异。
OpenMPI的 Hostfile 格式为:
1 |
|
MPICH的 Hostfile 格式为:
1 |
|
更细节的参考:
MPICH Hydra
OpenMPI FAQ
学习的时候个人推荐MPICH进行学习和研究。
安装 MPICH
下载
1 |
|
编译
不做赘述
3对基本函数
MPI Hello World
1 |
|
编译方法:
1 |
|
MPI_Init & MPI_Finalize
- MPI_Init用于启动
- MPI_Finalize用于结束
MPI_Comm_rank & MPI_Comm_size
- MPI_Comm_rank 用于计算当前的rank
- MPI_Comm_size 用于当前通信器里面的总进程数
MPI_Send & MPI_Recv
- MPI_Send 用于发送数据
- MPI_Recv 用于接收数据
接口格式为:
1 |
|
和
1 |
|
对于一个最简单的收发例子如下:
1 |
|
这里我们指定了固定的数据收发模式,[RANK0]发送[RANK1]接收。更详细的说明在之后进行介绍,这里只作为入门例子。
MPI 通信数据类型
一般数据类型
(Tip: 下表是用来查的,不是用来背的)
MPI datatype | C datatype |
---|---|
MPL_CHAR | char (treated as printable character) |
MPI_SHORT | signed short int |
MPL_INT | signed int |
MPI_LONG | signed long int |
MPI_LONG_LONG_INT | signed long long int |
MPI_LONG_LONG (as a synonym) | signed long long int |
MPI_SIGNED_CHAR | signed char (treated as integral value) |
MPI_UNSIGNED_CHAR | unsigned char (treated as integral value) |
MPI_UNSIGNED_SHORT | unsigned short int |
MPI_UNSIGNED | unsigned int |
MPI_UNSIGNED_LONG | unsigned long int |
MPI_UNSIGNED_LONG_LONG | unsigned long long int |
MPI_FLOAT | float |
MPI_DOUBLE | double |
MPI_LONG_DOUBLE | long double |
MPL_WCHAR | wchar_t (defined in |
MPI_C_BOOL | Bool |
MPI_INT_T | int8_t |
MPI_INT16_T | int16_t |
MPI_INT32_T | int32_t |
MPI_INT64_T | int64_t |
MPI UINT8_T | uint8_t |
MPI_UINT16_T | uint16_t |
MPL_UINT32 T | uint32_t |
MPI_UINT64_T | uint64_t |
MPL_C_COMPLEX | float_Complex |
MPI_C_FLOAT_COMPLEX (as a synonym) | float Complex |
MPI_C_DOUBLE_COMPLEX | double_Complex |
MPI_BYTE | |
MPL_PACKED |
对于 C++ 有一些拓展:
MPl datatype | C++ datatype |
---|---|
MPI_CXX_BOOL | bool |
MPI_CXX_FLOAT_COMPLEX | std::complex\ |
MPI_CXX_DOUBLE_COMPLEX | std::complex\ |
MPI_CXX_LONG_DOUBLE_COMPLEX | std::complex\ |
其中的 MPI_PACKED 和 MPI_BYTE 作为拓展消息类型。就是对数据进行压包后进行发送。
MPI_PACKED 数据类型
MPI_PACKED数据类型是一个特殊封装的数据类型,一般用来实现传输地址空间不连续的数据项。
接口格式为: (由于接口文档上,OpenMPI做的更好,后面可能会参考OpenMPI的文档)
MPI_Pack
MPI_Unpack
MPI_Pack_size
一个例子:
1 |
|
MPI 点对点通信
阻塞通信和非阻塞通信
阻塞通信主要特征是:
如果假设进程A发送,进程B接收。在这一对收发任务完成之前,进程A会一致在发送接口处停住,而B会在接收接口处停住。
发送类别主要有以下四类:
- 标准模式 MPI_Send/MPI_Isend: 自由发送接收,不考虑其它进程状态
- 缓存模式 MPI_Bsend/MPI_Ibsend: 由用户显式提供缓存区,辅助通信
- 同步模式 MPI_Ssend/MPI_Issend: 通信双方先建立联系,再通信
- 就绪模式 MPI_Rsend/MPI_Irsend: 接受进程必须先于发送进程提出通信要求
一般使用的时候主要使用标准模式即可。
MPI 原语 | 阻塞 | 非阻塞 |
---|---|---|
Standard Send | MPI_Send | MPI_Isend |
Buffered Send | MPI_Bsend | MPI_Ibsend |
Synchronous Send | MPI_Ssend | MPI_Issend |
Ready Send | MPI_Rsend | MPI_Irsend |
Receive | MPI_Recv | MPI_Irecv |
Completion Check | MPI_Wait | MPI_Test |
流水/管线通信
模式例子:
1 |
|
Example:
1 |
|
双缓冲
模式例子:
1 |
|
针对计算复杂度比较高的情况,第一轮次使用xbuf0和ybuf0,第二轮次使用xbuf1和ybuf1,两组缓冲轮流使用。
对位置消息探查
主要函数有:
MPI_Probe 的接口和 MPI_Recv 近似,他是不接受数据的 Recv
下面这个例子辅助理解(From MPI-Tutorial):
1 |
|
MPI 通信域
MPI 集群通信
类型 | 函数名 | 含义 |
---|---|---|
通信 | MPI_Bcast | 一对多广播同样的消息 |
MPI_Gather | 多对一收集各个进程的消息 | |
MPI_Gatherv | MPI_Gather的一般化 | |
MPI_Allgather | 全局收集 | |
MPI_Allgatherv | MPI_Allgather的一般化 | |
MPI_Scatter | 一对多散播不同的消息 | |
MPI_Scatterv | MPI_Scatter的一般化 | |
MPI_Alltoall | 多对多全局交换消息 | |
MPI_Alltoallv | MPI_Alltoall的一般化 | |
聚集 | MPI_Reduce | 多对一归约 |
MPI_Allreduce | MPI_Reduce的一般化 | |
MPI_Reduce_scatter | MPI_Reduce的一般化 | |
MPI_Scan | 扫描 | |
同步 | MPI_Barrier | 路障同步 |
OpenMP快速上手
实例
实例之前
为了说明各种实例的效果和性能,这里先补充一下关于计时的方法。
方法1:time
1 |
|
方法2:gettimeofday
1 |
|
方法3:clock_gettime
1 |
|
方法4:chrono库
1 |
|
方法5:rdtsc
最精准也是最难用的方法
1 |
|
使用限制:
机器需要有constant_tsc的特性,使用:
cat /proc/cpu_info | grep constant_tsc
命令可以确定是否有该特性乱序执行核能会打乱时钟周期的测量,必要时需要制造“依赖指令”去避免乱序执行
必要时需要使用
memory barrier
cat /proc/cpuinfo | grep rdtscp
如果开启,可以使用rdtscp,更精准一点。使用方法基本一致:1
uint64_t get_tscp() { uint64_t a, d; __asm__ volatile("rdtscp" : "=a"(a), "=d"(d)); return (d << 32) | a; }
小结
如果使用C++,那么使用chrono
库是最好的选择,相信STL不会翻大车
如果机器由constant tsc
特性,那么可以使用rdtsc方法
如果没有,那么使用gettimeofday
时一个比较稳定的方法
实例1:不适用并行的例子
刻意并行化,或者过低的并行层级往往会带来巨大的负优化。这里用一个求和的例子说明。
采用如下方式对一个数组求和。