分类: 云计算
2012-07-19 18:09:14
你已经熟悉了MPI_COMM_WORLD,它是MPI定义的允许程序里所有进程运行期间互相通信的通信者,或者是点对点通信,又或者是集合通信。然而对于一些应用,可能需要在选定的一个子进程组里进行通信。
通 信者有两类:内部通信者(intra-communicator)和互联通信者(inter-communicator)。内部通信者处理通信者个体里的 进程间的通信,而互联通信者处理内部通信者之间的通信。本质上来说,内部通信者是MPI_COMM_WORLD的进程的子集。我们主要专注于内部通信者。
需要新的通信者的原因经常是因为要处理矩阵的行、列或子块等需求。这些通信者通常和一个虚拟拓扑关联--比笛卡尔拓扑常用--来辅助并行操作的实现。此外,通信者的使用,经常和虚拟拓扑一起,通常增强了程序的可读性和可维护性。
MPI_Comm_group
得到一个通信者的组句柄。函数原型:
int MPI_Comm_group(MPI_Comm comm, MPI_Group *group);
例:
#include "mpi.h"
MPI_Comm comm_world;
MPI_Group group_world;
comm_world = MPI_COMM_WORLD;
MPI_Comm_group(comm_world, &group_world);
和 通信者关联的是它的组标识,或称为句柄。在上面的例子里,我们使用MPI_Comm_group来得到通信者MPI_COMM_WORLD的组句柄。这个 句柄而后可以用作以下例程的输入:MPI_Group_incl、MPI_Comm_create、MPI_Group_rank。
MPI_Group_incl
基于已有的组创建一个新的组,并指明成员进程。incl是include的缩写。函数原型:
int MPI_Group_incl(MPI_Group old_group, int count, int *members, MPI_Group *new_group);
members是原组里挑选出的进程的秩的数组,这些进程以被挑选的顺序放入新的组里。
例:
#include "mpi.h"
MPI_Group group_world, odd_group, even_group;
int i, p, Neven, Nodd, members[8], ierr;
MPI_Comm_size(MPI_COMM_WORLD, &p);
MPI_Comm_group(MPI_COMM_WORLD, &group_world);
Neven = (p+1)/2;
Nodd = p - Neven;
for (i = 0; i < Neven; i++) {
members[i] = 2*i;
}
MPI_Group_incl(group_world, Neven, members, &even_group);
在上例中,创建了一个新的组,它的成员是通信者MPI_COMM_WORLD的偶数编号的进程。在新的通信者里,组的成员以间隔1升序排序。
如果count参数为0,那么new_group的值为MPI_GROUP_EMPTY。
members数组里值的顺序,会影响到新组里各进程的秩。
MPI_Group_excl
基于已有组,创建一个新组,并指明不在新组中的成员。excl是exclude的缩写。函数原型:
int MPI_Group_excl(MPI_Group group, int count, int *nonmembers, MPI_Group *new_group);
和MPI_Group_incl不同,nonmemebers的元素顺序不会影响新组里的进程的秩。各进程在新组里的顺序和在原组的相对顺序相同。
如果count参数为0,那么new_group和old_group相同。
要排除的秩(在nonmembers定义的)必须在原组中存在,而且nonmembers数组的元素必须互不相同,否则会出错。
MPI_Group_rank
查询调用进程在组里的秩。函数原型:
int MPI_Group_rank(MPI_Group group, int *rank);
例:
#include "mpi.h"
MPI_Group group_world, worker_group;
int i, p, ierr, group_rank;
MPI_Comm_size(MPI_COMM_WORLD, &p);
MPI_Comm_group(MPI_COMM_WORLD, &group_world);
MPI_Group_excl(group_world, 1, 0, &worker_group);
MPI_Group_rank(worker_group, &group_rank);
上 例中,首先创建一个新的工作组,它的成员是MPI_COMM_WORLD里除了进程0的其它所有进程。之后查询了这个组的秩。 MPI_COMM_WORLD的秩的范围是(0,1,2,...,p-1)。对于这个简单的例子,新组worker_group的秩的范围是 (0,1,2,...,p-2),因为它比MPI_COMM_WORLD少了一个进程(进程0)。所以,调用进程在新组里对应的秩号会比原来小一。然而对 于其它排列,调用进程在新组里的秩没有那么直接。MPI_Group_rank的省去我们跟踪进程秩号的麻烦。
注意,如果调用进程是进程 0,它不属于worker_group,所以MPI_Group_rank会返回MPI_UNDEFINED作为group_rank的值,表示它不是 worker_group的成员。MPI_UNDEFINED的值由实现决定。比如SGI's MPI里它的值是-3,而在MPICH里是-32766。
MPI_Group_free
当不再需要一个组时,把它归还给系统。函数原型:
int MPI_Group_free(MPI_Group *group);
例:
#include "mpi.h"
MPI_Group group_world, worker_group;
int i, p, ierr, group_rank;
MPI_Comm_size(MPI_COMM_WORLD, &p);
MPI_Comm_group(MPI_COMM_WORLD, &group_world);
MPI_Group_excl(group_world, 1, 0, &worker_group);
MPI_Group_rank(worker_group, &group_rank);
MPI_Group_free(worker_group);
注意:释放一个组并不会释放它所属的通信者。MPI_Comm_free用来释放一个存在的通信者。
MPI_Comm_create
基于一个已有的通信者和组创建一个新的通信者。函数原型:
int MPI_Comm_create(MPI_Comm old_comm, MPI_Group group, MPI_Comm *new_comm);
例:
#include "mpi.h"
MPI_Comm comm_world, comm_worker;
MPI_Group group_world, group_worker;
int ierr;
comm_world = MPI_COMM_WORLD;
MPI_Comm_group(comm_world, &group_world);
MPI_Group_excl(group_world, 1, 0, &group_worker);
MPI_Comm_create(comm_world, group_worker, &comm_worker);
上例中,MPI_COMM_WORLD的组句柄首先被标识。然后一个新的组group_worker被创建。最后MPI_Comm_create用来创建一个新的通信者,它的成员进程和刚刚创建的组的成员一样。通过这个新的通信者,成员进程之间就可以传递消息了。
MPI_Comm_create是一个集合通信例程,它必须被old_comm的所有进程调用,且所有进程的调用的所有参数都必须相同,否则会出错。
对于不在组的进程,MPI_Comm_create会返回MPI_COMM_NULL。
在任务完成后,创建的通信者可以通过调用MPI_Comm_free来释放。
MPI_Comm_split
基于已有的通信者分离出多个新的通信者。
许 多科学和工程计算都处理矩阵或网格(特别是迪卡尔网格),它们都由行和列组成。因此需要把进程逻辑地映射到相似的网格几何结构里。此外,可能需要用没那么 传统的方式处理它们。例如,处理一组行或其它任意的结构,而非一个个独立的行,会很有好处或甚至是必需的。MPI_Comm_split提供了这样的灵活 性来创建新的多个通信者。
函数原型:
int MPI_Comm_split(MPI_Comm old_comm, int color, int key, MPI_Comm *new_comm);
color提供了分组机制,相同颜色的进程在相同的组里。key参数提供了在每个组(颜色)内对秩的选派的控制。
例:对于2D逻辑网格,创建行和列的子网格。
irow = Iam / mcol; /* logical row number */
jcol = mod(Iam, mcol); /* logical column number */
comm2D = MPI_COMM_WORLD;
MPI_Comm_split(comm2D, irow, jcol, row_comm);
MPI_Comm_split(comm2D, jcol, irow, col_comm);
为了证明这个例子的结果,假设我们有6个进程(0, 1, ..., 5)。从数学(和拓扑)的角度,可以把这些进程视为3x2的逻辑网格排列。如下表所示:
(0) | (1) |
(2) | (3) |
(4) | (5) |
秩为Iam的调用进程,使用到的irow和jcol,分别被定义为行号和列号。irow和jcol与Iam的关系如下表:
Iam | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
irow | 0 | 0 | 1 | 1 | 2 | 2 |
jcol | 0 | 1 | 0 | 1 | 0 | 1 |
第一个MPI_Comm_split调用把irow规定为“颜色”(或组),把jcol定义为“关键字”(或组里的独特成员)。这导致每行都被分类为不同的组,如下表所示:
(0) (0) | (1) (1) |
(2) (0) | (3) (1) |
(4) (0) | (5) (1) |
另一方面,第二个MPI_Comm_split调用把jcol定义为颜色,把irow定义为关键字。这使得一列里的所有进程都属于一个组,如下表所示:
(0) (0) | (1) (0) |
(2) (1) | (3) (1) |
(4) (2) | (5) (2) |
在上面两个表里,原来的秩号标为黑色,而新的秩号被标为红色。相同的颜色的表格属于同一个组。
MPI_Comm_split 和MPI_cart_sub相似,但它比后者更通用。MPI_Comm_split创建一个逻辑网格,并通过它的线性秩号来引用它。 MPI_Cart_sub创建一个笛卡尔网格,并用笛卡尔坐标来引用它。例如,在2维的迪卡尔网格里,网格单元由它的(irow, jcol)索引对标识。
MPI_Comm_split是一个集体通信例程,因此old_comm里的所有进程都必须调用这个例程。然而,和许多通信例程不同,关键字和颜色用于区分old_comm里的所有进程。
如果old_comm里有进程不在任何新组里,那么它的颜色必须定义为MPI_UNDEFINED。对于这些进程,对应的返回值new_comm的值为MPI_COMM_NULL。
如果有两个或多个进程有相同的关键字,那么新通信者里的这些进程的秩号以它们在原通信者里的相对顺序来排序。
通信者的示例代码
组例程的使用
这个例子的目的是把MPI_COMM_WORLD的成员进程分成两个新组。一个组由奇数号的进程组成,而另一个由偶数号的进程组成。之后打印一个表来展示一个进程是属于奇数还是偶数组。
#include "mpi.h"
#include "stdio.h"
void main(int argc, char *argv[])
{
int Iam, p;
int Neven, Nodd, members[6], even_rank, odd_rank;
MPI_Group group_world, even_group, odd_group;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &Iam);
MPI_Comm_size(MPI_COMM_WORLD, &p);
Neven = (p+1)/2;
Nodd = p - Neven;
members[0] = 2;
members[1] = 0;
members[2] = 4;
MPI_Comm_group(MPI_COMM_WORLD, &group_world);
MPI_Group_incl(group_world, Neven, members, &even_group);
MPI_Group_excl(group_world, Neven, members, &odd_group);
MPI_Barrier(MPI_COMM_WORLD);
if (Iam == 0) {
printf("MPI_Group_incl/excl Usage Example\n");
printf("\n");
printf("Number of processes is %d \n", p);
printf("Number of odd processes is %d\n", Nodd);
printf("Number of even processes is %d\n", Neven);
printf("\n");
printf(" Iam even odd\n");
}
MPI_Barrier(MPI_COMM_WORLD);
MPI_Group_rank(even_group, &even_rank);
MPI_Group_rank(odd_group, &odd_rank);
printf("%8d %8d %8d\n", Iam, even_rank, odd_rank);
MPI_Finalize();
}
它的输出为:
$ mpirun -np 6 ./odd_even_groups
MPI_Group_incl/excl Usage Example
Number of processes is 6
Number of odd processes is 3
Number of even processes is 3
Iam even odd
0 1 -32766
2 0 -32766
3 -32766 1
4 2 -32766
1 -32766 0
5 -32766 2
其中-32766是mpich里的MPI_UNDEFINED的值。
可 以注意到偶数组是用MPI_Group_incl来创建的,所以它的进程的秩和members数组里定义的顺序一样。而奇数组是用 MPI_Group_excl来创建的,所以它的进程的秩的相对顺序和它们在MPI_COMM_WORLD里时的相对顺序一样。进程在新组中的秩也是由0 开始编号的。
MPI_Comm_split的使用
#include
#include
void main(int argc, char *argv[])
{
int mcol, irow, jcol, p;
MPI_Comm row_comm, col_comm, comm2D;
int Iam, row_id, col_id;
int row_group, row_key, map[6];
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &Iam);
MPI_Comm_size(MPI_COMM_WORLD, &p);
map[0]=2; map[1]=1; map[2]=2; map[3]=1; map[4]=0; map[5]=1;
mcol=2; /* nrow = 3 */
if (Iam == 0) {
printf("\n");
printf("Example of MPI_Comm_split Usage\n");
printf("Split 3x2 grid into 2 different communications\n");
printf("which correspond to 3 rows and 2 columns.");
printf("\n");
printf(" Iam irow jcol row-id col-id\n");
}
irow = Iam / mcol;
jcol = Iam % mcol;
comm2D = MPI_COMM_WORLD;
MPI_Comm_split(comm2D, irow, jcol, &row_comm);
MPI_Comm_split(comm2D, jcol, irow, &col_comm);
MPI_Comm_rank(row_comm, &row_id);
MPI_Comm_rank(col_comm, &col_id);
MPI_Barrier(MPI_COMM_WORLD);
printf("%8d %8d %8d %8d %8d\n", Iam, irow, jcol, row_id, col_id);
MPI_Barrier(MPI_COMM_WORLD);
if (Iam == 0) {
printf("\n");
printf("Next, create more general communicator\n");
printf("which consists of two groups :\n");
printf("Rows 1 and 2 belongs to group 1 and row 3 is group 2\n");
printf("\n");
}
row_group = Iam / 4;
row_key = Iam - row_group * 4;
MPI_Comm_split(comm2D, row_group, row_key, &row_comm);
MPI_Comm_rank(row_comm, &row_id);
printf("%8d %8d\n", Iam, row_id);
MPI_Barrier(MPI_COMM_WORLD);
if (Iam == 0) {
printf("\n");
printf("If two processes have same key, the ranks\n");
printf("of these two processes in the new\n");
printf("communicator will be ordered according'\n");
printf("to their order in the old communicator\n");
printf(" key = map[Iam]; map = (2,1,2,1,0,1)\n");
printf("\n");
}
row_group = Iam / 4;
row_key = map[Iam];
MPI_Comm_split(comm2D, row_group, row_key, &row_comm);
MPI_Comm_rank(row_comm, &row_id);
MPI_Barrier(MPI_COMM_WORLD);
printf("%8d %8d\n", Iam, row_id);
MPI_Finalize();
}
输出:
$ mpirun -np 6 ./comm_split
Example of MPI_Comm_split Usage
Split 3x2 grid into 2 different communications
which correspond to 3 rows and 2 columns.
Iam irow jcol row-id col-id
0 0 0 0 0
2 1 0 0 1
4 2 0 0 2
5 2 1 1 2
3 1 1 1 1
1 0 1 1 0
Next, create more general communicator
which consists of two groups :
Rows 1 and 2 belongs to group 1 and row 3 is group 2
0 0
2 2
5 1
3 3
1 1
4 0
If two processes have same key, the ranks
of these two processes in the new
communicator will be ordered according'
to their order in the old communicator
key = map[Iam]; map = (2,1,2,1,0,1)
0 2
2 3
1 0
4 0
5 1
3 1
从输出的最后一段可以看到当关键字相同时的秩的情况。进程1和进程3都有关键字“1”,所以它们在新组里的秩分别为“0”和“1”。进程0和进程2的关键字为“2”,所以它们在新组里的秩分别为“2”和“3”。