MPI-3 学习笔记(三):混合编程与邻居集合通信

MPI-3 混合编程(Hybrid Programming)与邻居集合通信(Neighborhood Collective Communication)特性学习笔记。

混合编程与 MPI 共享内存

MPI 定义了四种等级的线程安全性:

  1. MPI_THREAD_SINGLE:默认模式,MPI 进程中只有单个线程;
  2. MPI_THREAD_FUNNEL:只有主线程可以调用 MPI 函数,即 OpenMP 中的 master 线程或者 OpenMP 中的非并行区调用;
  3. MPI_THREAD_SERIALIZED:只有单个线程可以调用 MPI 函数;
  4. 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():每个进程作为图上的一个节点,指出其出度、入度、出边、入边和各边权重;
    DGCA
  • MPI_Dist_graph_creat():每个进程提供全图一部分的信息。
    DGC

查询邻居:

  • MPI_Dist_graph_neighbors_count():查询有多少个邻居,返回入度、出度以及可能可用的边权重信息;
  • MPI_Dist_graph_neighbors():查询邻居列表,可能会返回边的权重信息,邻居列表顺序决定了邻居集合通信的数据排放顺序。

邻居集合通信须知:

  • 建立拓扑关系只是告诉 MPI 系统通信模式,让 MPI 系统自行选择优化方法;
  • 邻居集合通信仅覆盖了部分的通信模式。

笛卡尔坐标系中的邻居集合通信:

  • 只和直接相邻的邻居进行通信;
  • 通信子中的所有进程都必须调用,不论该进程是否有邻居;
  • 收到的数据在缓冲区中按照邻居的顺序进行存放:
    • 依次存放各维度,每个维度先存 -1 方向的邻居再存 +1 方向的邻居;
    • 2 * ndims 个源进程和目的进程;
    • 在边界的进程,如果对应方向上没有邻居(不是环面结构),则对应的缓冲区位置不会有数据;
      CNC

图中的邻居集合通信:

  • Collective communication along arbitrary neighbors;
  • 邻居的顺序由 MPI_Dist_graph_neighbors() 返回的邻居顺序决定;
  • 图是有向的,因此可以有不同的 Send/Recv 邻居数量;
  • 可以进行稠密集合操作,对于固定的通信模式效果较好。

两个邻居集合通信函数:

  1. MPI_Neighbor_allgather(*sendbuf, sendcount, sendtype, *recvbuf, recvcount, recvtype, comm)
  2. 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

MPI-3 学习笔记参考资料