【MPI】组和通信器

【MPI】组和通信器

引言

在并行计算中,MPI(Message Passing Interface)是一个广泛使用的标准,它提供了一系列的功能来在分布式内存系统中管理进程间的通信。虽然在简单的并行应用程序中,MPI_COMM_WORLD 是最常见的通讯器(communicator),但是当程序规模增大时,使用默认的通讯器进行全局通信变得不再合适。此时,创建多个通讯器以管理不同的进程子集显得尤为重要。本文将详细介绍如何使用 MPI 的 MPI_Comm_splitMPI_Comm_create_group 函数来创建和管理自定义通讯器,并深入探讨 MPI 中的组(group)操作及其使用场景。

1. 通讯器的基本概念

1.1 什么是通讯器?

在 MPI 中,通讯器是一个逻辑上分组的进程集合,它用于标识一组可以彼此通信的进程。一个通讯器不仅仅是进程的集合,它还具有一个上下文(context),用于区分同样的操作在不同通讯器中的执行。通过通讯器,MPI 可以确保不同组的进程之间的消息传递不会发生冲突。

例如,默认通讯器 MPI_COMM_WORLD 包含了所有启动 MPI 程序的进程,而在某些应用中,我们可能只关心其中一部分进程之间的通信。

1.2 MPI_Comm_split 的作用

MPI_Comm_split 是 MPI 中最常见的创建新通讯器的方式。它通过基于特定规则(如颜色和秩)将一个现有的通讯器划分为多个子通讯器。

其函数原型如下:

int MPI_Comm_split(
    MPI_Comm comm,    // 原始通讯器
    int color,        // 确定进程所属子通讯器的标识
    int key,          // 子通讯器中的秩(rank)
    MPI_Comm *newcomm // 新创建的子通讯器
);
  • comm: 原始通讯器,例如 MPI_COMM_WORLD
  • color: 确定进程属于哪个子通讯器,具有相同 color 值的进程将属于同一子通讯器。如果为 MPI_UNDEFINED,则该进程不会参与任何子通讯器。
  • key: 在新通讯器中的秩,通常通过原始秩来设置。
  • newcomm: 创建的新通讯器。
1.3 示例:按行划分网格

假设我们有一个 4x4 的进程网格,使用 MPI_Comm_split 来按行划分进程。每一行的进程将被分配到一个新的通讯器。

以下是具体的实现:


#include <iostream>
#include <mpi.h>
// MPI_Comm_split、MPI_Comm_free ----------------------------
int main() {
  MPI_Init(NULL, NULL);
  int world_size, rank;
  MPI_Comm_size(MPI_COMM_WORLD, &world_size);
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);

  int color = rank / 4;
  MPI_Comm row_comm;
  MPI_Comm_split(MPI_COMM_WORLD, color, rank, &row_comm);

  int row_rank, row_size;
  MPI_Comm_rank(row_comm, &row_rank);
  MPI_Comm_size(row_comm, &row_size);

  std::cout << "WORLD RANK/SIZE: " << rank << "/" << world_size
            << " \t ROW RANK/SIZE: "<< row_rank << "/" << row_size << std::endl;

  // 最后,我们使用 MPI_Comm_free 释放通讯器。 这似乎不是一个重要的步骤,但与在其他任何程序中使用完内存后释放内存一样重要。 
  // 当不再使用 MPI 对象时,应将其释放,以便以后重用。 MPI 一次可以创建的对象数量有限,如果 MPI 用完了可分配对象,则不释放对象可能会导致运行时错误。
  MPI_Comm_free(&row_comm);
  MPI_Finalize();

  return 0;
}

在这个示例中,我们将进程按照 4x4 网格划分,使用 MPI_Comm_split 为每一行创建一个新的通讯器。输出结果如下:

WORLD RANK/SIZE: 0/16     ROW RANK/SIZE: 0/4
WORLD RANK/SIZE: 1/16     ROW RANK/SIZE: 1/4
WORLD RANK/SIZE: 2/16     ROW RANK/SIZE: 2/4
WORLD RANK/SIZE: 3/16     ROW RANK/SIZE: 3/4
WORLD RANK/SIZE: 4/16     ROW RANK/SIZE: 0/4
...

可以看到,每一行的进程具有相同的 color 值,因此它们被分配到同一个新的通讯器 row_comm 中。

2. 更高级的通讯器创建方法

除了 MPI_Comm_split,MPI 还提供了一些更灵活的函数来创建和管理通讯器。例如,MPI_Comm_dup 用于创建一个现有通讯器的副本,而 MPI_Comm_create_group 则用于通过现有的进程组来创建新的通讯器。

2.1 MPI_Comm_dup 示例
MPI_Comm_dup(MPI_COMM_WORLD, &new_comm);

MPI_Comm_dup 函数用于创建一个现有通讯器的副本,与原通讯器具有相同的组和上下文。它的主要用途是在多个库或模块之间避免对同一通讯器的冲突。

2.2 MPI_Comm_create 示例
#include <iostream>
#include <mpi.h>
#include <cstdio>
// MPI_Comm_group、MPI_Group_incl、MPI_Comm_create、MPI_Group_free---------------------------------------
 int main() {
   MPI_Init(NULL, NULL);
   int world_size, rank;
   MPI_Comm_size(MPI_COMM_WORLD, &world_size);
   MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  

   MPI_Group world_group, new_group;
   MPI_Comm new_comm;

   // 获取原组
   MPI_Comm_group(MPI_COMM_WORLD, &world_group);

   // 从原组创建子组
   int ranks[3] = {0, 1,3};
   // MPI_Group_incl:从现有组中选择一部分进程创建一个新的组。
   // int MPI_Group_incl(MPI_Group group, int n, const int ranks[], MPI_Group *newgroup);
   MPI_Group_incl(world_group, 3, ranks, &new_group);

   // 根据新组创建新通讯器
   MPI_Comm_create(MPI_COMM_WORLD, new_group, &new_comm);

   // 如果某个进程未被选中加入新通讯器,则该函数会返回 MPI_COMM_NULL,表示该进程不属于新创建的通讯器。
   if (new_comm != MPI_COMM_NULL) {
     int new_rank, new_size;
     MPI_Comm_rank(new_comm, &new_rank);
     MPI_Comm_size(new_comm, &new_size);

     printf("World Rank: %d, New Rank: %d, New Size: %d\n", rank,
            new_rank, new_size);

     MPI_Comm_free(&new_comm);  // 释放新通讯器
   }

   // MPI_Group_free 用于释放一个由 MPI 创建的组 (MPI_Group) 对象,并将其设置为 MPI_GROUP_NULL,防止资源泄漏
   MPI_Group_free(&new_group);
   MPI_Group_free(&world_group);

   MPI_Finalize();

   return 0;
 }

3. 组的操作和使用

3.1 组的概念

MPI 中的 组(Group) 是一个进程集合,类似于集合理论中的概念。每个组都包含了一些进程的秩,并且通过 MPI_Comm_group 可以从现有通讯器中获取到进程组。

// 从通讯器中获取对应的组。
MPI_Group group;
MPI_Comm_group(MPI_COMM_WORLD, &group);

// 获取当前进程在组内的 rank(组内的进程编号)
int rank;
MPI_Group_rank(group, &rank);

//获取组中进程的数量。
int size;
MPI_Group_size(group, &size);
3.2 组操作:并集和交集

MPI 提供了多种操作来对组进行处理,其中最常见的是并集(union)和交集(intersection)操作。

  • 并集(Union):两个组的并集是包含所有元素的集合。
  • 交集(Intersection):两个组的交集仅包含同时出现在两个组中的元素。

例如,使用 MPI_Group_union 来获取两个组的并集:

MPI_Group_union(group1, group2, &newgroup);

4. 示例:使用组来创建通讯器

假设我们想要从 MPI_COMM_WORLD 中选择特定的进程(比如所有素数秩的进程),并根据这些进程创建一个新的通讯器:

#include <iostream>
#include <mpi.h>
#include <cstdio>
// MPI_Comm_group、MPI_Group_rank、MPI_Group_size、MPI_Group_union、MPI_Group_intersection、MPI_Comm_create-----------------
int main() {
  MPI_Init(NULL,NULL);

  int world_rank, world_size;
  MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
  MPI_Comm_size(MPI_COMM_WORLD, &world_size);

  // 获取 MPI_COMM_WORLD 的组
  MPI_Group world_group;
  MPI_Comm_group(MPI_COMM_WORLD, &world_group);

  // 定义两个子组
  int group1_ranks[] = {0, 1, 2};  // 子组1包含 rank 0, 1, 2
  int group2_ranks[] = {1, 2, 3};  // 子组2包含 rank 1, 2, 3

  // 创建子组1和子组2
  MPI_Group group1, group2;
  MPI_Group_incl(world_group, 3, group1_ranks, &group1);
  MPI_Group_incl(world_group, 3, group2_ranks, &group2);

  // 打印组内 rank 和大小
  int group1_rank, group1_size;
  MPI_Group_rank(group1, &group1_rank);
  MPI_Group_size(group1, &group1_size);

  if (group1_rank != MPI_UNDEFINED) {
    printf("Rank %d in MPI_COMM_WORLD is Rank %d in Group1 of size %d\n",
           world_rank, group1_rank, group1_size);
  }

  // 并集操作:创建一个包含子组1和子组2的联合组
  MPI_Group union_group;
  MPI_Group_union(group1, group2, &union_group);

  // 交集操作:创建一个包含子组1和子组2交集的组
  MPI_Group intersection_group;
  MPI_Group_intersection(group1, group2, &intersection_group);

  // 基于并集组创建一个新的通讯器
  MPI_Comm union_comm;
  MPI_Comm_create(MPI_COMM_WORLD, union_group, &union_comm);

  if (union_comm != MPI_COMM_NULL) {
    int union_rank, union_size;
    MPI_Comm_rank(union_comm, &union_rank);
    MPI_Comm_size(union_comm, &union_size);
    printf(
        "Rank %d in MPI_COMM_WORLD is Rank %d in Union Communicator of size "
        "%d\n",
        world_rank, union_rank, union_size);
    MPI_Comm_free(&union_comm);
  }

  // 基于交集组创建一个新的通讯器
  MPI_Comm intersection_comm;
  MPI_Comm_create(MPI_COMM_WORLD, intersection_group, 
                        &intersection_comm);

  if (intersection_comm != MPI_COMM_NULL) {
    int intersection_rank, intersection_size;
    MPI_Comm_rank(intersection_comm, &intersection_rank);
    MPI_Comm_size(intersection_comm, &intersection_size);
    printf(
        "Rank %d in MPI_COMM_WORLD is Rank %d in Intersection Communicator of "
        "size %d\n",
        world_rank, intersection_rank, intersection_size);
    MPI_Comm_free(&intersection_comm);
  }

  // 释放所有组
  MPI_Group_free(&group1);
  MPI_Group_free(&group2);
  MPI_Group_free(&union_group);
  MPI_Group_free(&intersection_group);
  MPI_Group_free(&world_group);

  MPI_Finalize();
  return 0;
}

5. 小结

在大规模并行应用中,使用多个通讯器来管理不同进程子集的通信是提高效率的关键。通过 MPI_Comm_splitMPI_Comm_create 等函数,开发者可以灵活地将进程分组并为每个组创建独立的通讯器,从而减少通信冲突并提高并行效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值