MPI-3 学习笔记(三):混合编程与邻居集合通信
MPI-3 混合编程(Hybrid Programming)与邻居集合通信(Neighborhood Collective Communication)特性学习笔记。
混合编程与 MPI 共享内存
MPI 定义了四种等级的线程安全性:
- MPI_THREAD_SINGLE:默认模式,MPI 进程中只有单个线程;
- MPI_THREAD_FUNNEL:只有主线程可以调用 MPI 函数,即 OpenMP 中的 master 线程或者 OpenMP 中的非并行区调用;
- MPI_THREAD_SERIALIZED:只有单个线程可以调用 MPI 函数;
- MPI_THREAD_MULTIPLE:所有线程都可以调用 MPI 函数。
调用MPI_Init_thread(requested, provided)
来初始化多线程 MPI,返回的provided
等级与请求的requested
等级可能不一样。
使用 MPI 多线程注意:
- 程序员必须保证所有通讯的顺序是正确的以及可以匹配;
- 如果调用的是阻塞通信函数,只有调用该函数的进程会被阻塞。
为什么使用 MPI_THREAD_MULTIPLE 难以优化:
- MPI 系统内部维护了一些资源,而 MPI 语义要求所有线程都可以访问某些数据,在访问的时候需要依次取锁,导致额外开销;
- 各线程调用 MPI 操作的顺序是不确定的,可能因此导致线程之间相互锁住。
MPI-3 允许不同的进程使用 MPI_Win_allocate_shared()
分配并共享同一块内存,以进行 MPI + MPI 的混合编程。下面是一个样例代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// See: https://software.intel.com/en-us/articles/using-mpi-3-shared-memory-in-xeon-phi-processors
long LOCAL_MEM_CNT = ... // The number of enteries each process have
MPI_Comm shmcomm;
MPI_Info win_info;
MPI_Win shm_win;
int shm_size, my_shm_rank;
// Split communicator and get new ranking info
MPI_Comm_split_type(MPI_COMM_WORLD, MPI_COMM_TYPE_SHARED, 0, MPI_INFO_NULL, &shmcomm);
MPI_Comm_rank(shmcomm, &my_shm_rank);
MPI_Comm_size(shmcomm, &shm_size);
MPI_Info_create(&win_info);
MPI_Info_set(win_info, "alloc_shared_noncontig", "true");
// Allocate shared memory
float *base_ptr = NULL;
MPI_Win_allocate_shared(
LOCAL_MEM_CNT * shm_size * sizeof(float), sizeof(float),
win_info, shmcomm, &base_ptr, &shm_win
);
// The following call on each process means each process
// can access data from other processes in group "shm_win"
MPI_Win_lock_all(0, shm_win);
// Retrieve ranks pointers
int dispunit, rep;
MPI_Aint sz;
float *all_ptr[size];
float *my_ptr = NULL;
for (int i = 0; i < size; i++) all_ptr[i] = NULL;
for (int i = 0; i < size; i++)
MPI_Win_shared_query(win, i, &sz, &dispunit, &(all_ptr[i]));
my_ptr = all_ptr[my_rank];
/*
Main calculations.
*/
MPI_Win_unlock_all(shm_win); // Finished memory sharing
注意:
- MPI-3 允许各进程申请不同数量的内存,甚至可以是 0 字节;
- MPI 标准没有规定申请的内存会被放在什么位置;
- 默认设置下,一个通信子(communicator)中申请的所有共享内存是连续的;然而,用户可以通过传入 “noncontig” 参数来提示 MPI 系统将内存放在某些位置以对齐到合适的边界。
拓扑映射与邻居集合通信
MPI 拓扑功能历史:
- MPI-1:笛卡尔坐标系拓扑,允许在 n 维空间中查询邻居;
- MPI-2.2:可扩展的图拓扑(graph topology),每个进程可以指定其邻居;
- MPI-3:邻居集合通信。
创建邻居图:
MPI_Dist_graph_create_adjacent()
:每个进程作为图上的一个节点,指出其出度、入度、出边、入边和各边权重;MPI_Dist_graph_creat()
:每个进程提供全图一部分的信息。
查询邻居:
MPI_Dist_graph_neighbors_count()
:查询有多少个邻居,返回入度、出度以及可能可用的边权重信息;MPI_Dist_graph_neighbors()
:查询邻居列表,可能会返回边的权重信息,邻居列表顺序决定了邻居集合通信的数据排放顺序。
邻居集合通信须知:
- 建立拓扑关系只是告诉 MPI 系统通信模式,让 MPI 系统自行选择优化方法;
- 邻居集合通信仅覆盖了部分的通信模式。
笛卡尔坐标系中的邻居集合通信:
- 只和直接相邻的邻居进行通信;
- 通信子中的所有进程都必须调用,不论该进程是否有邻居;
- 收到的数据在缓冲区中按照邻居的顺序进行存放:
- 依次存放各维度,每个维度先存 -1 方向的邻居再存 +1 方向的邻居;
- 2 * ndims 个源进程和目的进程;
- 在边界的进程,如果对应方向上没有邻居(不是环面结构),则对应的缓冲区位置不会有数据;
图中的邻居集合通信:
- Collective communication along arbitrary neighbors;
- 邻居的顺序由
MPI_Dist_graph_neighbors()
返回的邻居顺序决定; - 图是有向的,因此可以有不同的 Send/Recv 邻居数量;
- 可以进行稠密集合操作,对于固定的通信模式效果较好。
两个邻居集合通信函数:
MPI_Neighbor_allgather(*sendbuf, sendcount, sendtype, *recvbuf, recvcount, recvtype, comm)
MPI_Neighbor_alltoall(*sendbuf, sendcount, sendtype, *recvbuf, recvcount, recvtype, comm)
这两个函数的用法和Allgather
,Alltoall
类似,只是通信对象变成了自己的邻居。这两个函数也有非阻塞和消息变长的版本:**I**neighbor_allgather/alltoall**v**
。
样例代码:参见文件 stencil_mpi_overlap_carttopo_neighcolls.cpp
in demo source code