• 【MPI学习7】MPI并行程序设计模式:MPI的进程组和通信域


    基于都志辉老师MPI编程书中的第15章内容。

    通信域是MPI的重要概念:MPI的通信在通信域的控制和维护下进行 → 所有MPI通信任务都直接或间接用到通信域这一参数 → 对通信域的重组和划分可以方便实现任务的划分

    (1)通信域(communicator)是一个综合的通信概念。其包括上下文(context),进程组(group),虚拟处理器拓扑(topology)。其中进程组是比较重要的概念,表示通信域中所有进程的集合。一个通信域对应一个进程组。

    (2)进程(process)与进程组(group)的关系。每个进程是客观上唯一的(一个进程对应一个pid号);同一个进程可以属于多个进程组(每个进程在不同进程组中有个各自的rank号);同一个进程可以属于不同的进程组,因此也可以属于不同的通信域。

    (3)通信域产生的方法。根据看过的资料,大概有三种方法,先简要了解路子即可:

      a. 在已有通信域基础上划分获得:MPI_Comm_split(MPI_Comm comm, int color, int key, MPI_Comm *newcomm)  

       b. 在已有通信域基础上复制获得:MPI_Comm_dup(MPI_Comm comm, MPI_Comm *newcomm)

         c. 在已有进程组的基础上创建获得:MPI_Comm_create(MPI_Comm comm, MPI_Group group, MPI_Comm *newcomm)

    (4)进程组产生的方法。进程组(group)可以当成一个集合的概念,可以通过“子、交、并、补”各种方法。所有进程组产生的方法都可以套到集合的各种运算,用到的时候现看函数就可以了。

    (5)“当前进程”与“通信域产生函数”。如果在已有进程组的基础上创建新的通信域(即(3)中c方法),则newcomm有两种结果:如果调用MPI_Comm_create的当前进程在group中,则newcomm就是新产生的通信域对象;如果调用MPI_Comm_create的当前进程不在group中,则newcomm就是MPI_COMM_NULL。由于MPI是多进程编程,类似“当前进程”与“通信域产生函数”这种情况会比较频繁的出现,在设计思路上要适应并行编程这种改变。

    (6)不同通信域间互不干扰。“互不干扰”严格来说并不完全正确,这里想说的意思是:同一个进程,可以属于不同的通信域;同一个进程可以同时参与不同通信域的通信,互不干扰。

    下面通过一个例子来感受一下进程组和通信域在MPI多进程任务划分和处理上的应用。

    代码做的事情如下:

    (1)共有6个进程,在MPI_COMM_WORLD中的编号分别是{0,1,2,3,4,5}。

    (2)将{1,3,5}进程形成一个新的通信域comm1;将编号为{0,2,4}的进程生成一个新的通信域comm2

    (3)在comm1中执行MAX归约操作;在comm2中执行MIN归约操作;在MPI_COMM_WORLD中执行SUM归约操作

    (4)显示各个通信域中归约操作的结果

    具体代码如下:

     1 #include "mpi.h"
     2 #include <stdio.h>
     3 #include <stdlib.h>
     4 
     5 #define LEN 5
     6 
     7 int main(int argc, char *argv[])
     8 {
     9     MPI_Init(&argc, &argv);
    10     int world_rank, world_size;
    11     MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
    12     MPI_Comm_rank(MPI_COMM_WORLD, &world_size);
    13 
    14     MPI_Group world_group;
    15     MPI_Comm_group(MPI_COMM_WORLD, &world_group);
    16 
    17     int n = 3;
    18     const int ranks[3] = {1,3,5};
    19     const int ori1[1] = {1};
    20     const int ori2[1] = {0};
    21     int root1, root2;
    22 
    23     // 从world_group进程组中构造出来两个进程组
    24     MPI_Group group1, group2;
    25     MPI_Group_incl(world_group, n, ranks, &group1);
    26     MPI_Group_excl(world_group, n, ranks, &group2);
    27     // 根据group1 group2分别构造两个通信域
    28     MPI_Comm comm1, comm2;
    29     MPI_Comm_create(MPI_COMM_WORLD, group1, &comm1);
    30     MPI_Comm_create(MPI_COMM_WORLD, group2, &comm2);
    31 
    32     // 维护发送缓冲区和接受缓冲区
    33     int i;
    34     double *sbuf, *rbuf1, *rbuf2, *rbuf3;
    35     sbuf = malloc(LEN*sizeof(double));
    36     rbuf1 = malloc(LEN*sizeof(double));
    37     rbuf2 = malloc(LEN*sizeof(double));
    38     rbuf3 = malloc(LEN*sizeof(double));
    39     srand(world_rank*100);
    40     for(i=0; i<LEN; i++) sbuf[i] = (1.0*rand()) / RAND_MAX;
    41     fprintf(stderr,"rank %d:	", world_rank);
    42     for(i=0; i<LEN; i++) fprintf(stderr,"%f	",sbuf[i]);
    43     fprintf(stderr,"
    ");
    44     MPI_Group_translate_ranks(world_group, 1, ori1, group1, &root1);
    45     MPI_Group_translate_ranks(world_group, 1, ori2, group2, &root2);
    46     // MPI_COMM_WORLD comm1 comm2分别执行不同的归约操作
    47     if (MPI_COMM_NULL!=comm1) { // comm1
    48         MPI_Reduce(sbuf, rbuf1, LEN, MPI_DOUBLE, MPI_MAX, root1, comm1);
    49         int rank_1;
    50         MPI_Comm_rank(comm1, &rank_1);
    51         if (root1==rank_1) {
    52             fprintf(stderr,"MAX:	");
    53             for(i=0; i<LEN; i++) fprintf(stderr,"%f	",rbuf1[i]);
    54             fprintf(stderr,"
    ");
    55         }
    56     } 
    57     else if (MPI_COMM_NULL!=comm2) { // comm2
    58         MPI_Reduce(sbuf, rbuf2, LEN, MPI_DOUBLE, MPI_MIN, root2, comm2);
    59         int rank_2;
    60         MPI_Comm_rank(comm2, &rank_2);
    61         if (root2==rank_2) {
    62             fprintf(stderr,"MIN:	");
    63             for(i=0; i<LEN; i++) fprintf(stderr,"%f	",rbuf2[i]);
    64             fprintf(stderr,"
    ");
    65         }
    66     }
    67     MPI_Reduce(sbuf, rbuf3, LEN, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); // MPI_COMM_WORLD 
    68     if (0==world_rank) {
    69         fprintf(stderr,"SUM:	");
    70         for(i=0; i<LEN; i++) fprintf(stderr,"%f	",rbuf3[i]);
    71         fprintf(stderr,"
    ");
    72     }
    73     // 清理进程组和通信域
    74     if(MPI_GROUP_NULL!=group1) MPI_Group_free(&group1);
    75     if(MPI_GROUP_NULL!=group2) MPI_Group_free(&group2);
    76     if(MPI_COMM_NULL!=comm1) MPI_Comm_free(&comm1);
    77     if(MPI_COMM_NULL!=comm2) MPI_Comm_free(&comm2);
    78     MPI_Finalize();
    79 }

    代码执行结果如下:

    可以看到:

    a. MIN归约操作针对的是{0,2,4}

    b. MAX归约操作针对的是{1,3,5}

    c. SUM归约操作针对的是{0,1,2,3,4,5}

    d. SUM与MIN或MAX归约操作在时间上可能是重叠的,参与归约操作的进程也有重叠,但在结果上没有互相干扰。

    (7)组间通信域不同的通信域之间也可以通信,核心的操作是需要构造一个新的通信域类型——组间通信域。与一般的通信域不同,组间通信域包含两个进程组:本地进程组,远程进程组。通过组间通信域可以实现上述两个不同进程组内进程之间的通信。组间通信域的创建,需要在本地组和远程组中的相关进程中都调用创建语句,而且创建语句的参数中local_leader和remote_leader还是对称的。

     1 #include "mpi.h"
     2 #include <stdio.h>
     3 #include <stdlib.h>
     4 
     5 int main(int argc, char *argv[])
     6 {
     7     MPI_Comm myComm; // 标示本地子组的组内通信域
     8     MPI_Comm myFirstComm; // 组间通信域
     9     MPI_Comm mySecondComm; // 组间通信域
    10     int color; // split用到的key
    11     int rank;
    12 
    13     MPI_Init(&argc, &argv);
    14     MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    15 
    16     // 划分子通信域
    17     // split是如何划分的 翻阅了英文的mpi tutorial教程
    18     // 通过split函数中第三个参数来控制 当前进程在新的通信域中的新rank值大小
    19     color = rank % 3;
    20     MPI_Comm_split(MPI_COMM_WORLD, color, rank, &myComm);
    21 
    22     // 建立组间通信域
    23     // 需要确定的问题是: 
    24     // 1. A1和A2构造的是01之间的通信域 A1的local leader和remote leader与A2的local leader和remote leader是不是互相对应的
    25     // 2. 同理 B1和B2构造的是12之间的组间通信域 local leader和remote leader是否也是互相对应的 
    26     // 这是可以保证的  通过MPI_Comm_split语句中的第三个参数来保证
    27     if (0==color) {
    28         // 01之间的组间通信域
    29         // 1. 本地组的leader是0 
    30         // 2. remote组的leader在MPI_COMM_WORLD中的rank是1
    31         // 3. tag是1 (tag起到什么作用?)
    32         // 4. 组间通信域存在myFirstComm中
    33         MPI_Intercomm_create(myComm, 0, MPI_COMM_WORLD, 1, 1, &myFirstComm); // A1
    34     }
    35     else if (1==color) {
    36         // 01之间的组间通信域
    37         // 1. 本地组的leader是0 (本地组leader0应该是MPI_COMM_WORLD中的1 这是如何保证的?)
    38         // 2. 同理, MPI_COMM_WORLD中的0应该是color=0那个组中rank为0的进程 这是如何保证的?
    39         MPI_Intercomm_create(myComm, 0, MPI_COMM_WORLD, 0, 1, &myFirstComm); // A2
    40         // 12之间的组间通信域
    41         MPI_Intercomm_create(myComm, 0, MPI_COMM_WORLD, 2, 12, &mySecondComm); // B1
    42     }
    43     else if (2==color) {
    44         // 21之间的组间通信域
    45         MPI_Intercomm_create(myComm, 0, MPI_COMM_WORLD, 1, 12, &myFirstComm); // B2
    46     }
    47 
    48     if (0==color || 2==color) {
    49         MPI_Comm_free(&myFirstComm);
    50     }
    51     else if (1==color) {
    52         MPI_Comm_free(&myFirstComm);
    53         MPI_Comm_free(&mySecondComm);
    54     }
    55     MPI_Finalize();
    56 }
  • 相关阅读:
    CMake 从文件路径中提取文件名
    std::multimap 按照key遍历---
    Windows / Linux 一件编译zlib库
    C++ 11 可变模板参数的两种展开方式
    cmake 生成VS项目文件夹
    C++ 利用文件流复制文件
    利用 getsockname 和 getpeername 来获取某一个链接的本地地址和远端地址
    Windows 用VS编译libevent源码
    揭示同步块索引(上):从lock开始
    C手写一个多线程,供java调用
  • 原文地址:https://www.cnblogs.com/xbf9xbf/p/5239094.html
Copyright © 2020-2023  润新知