MPI基础(二)

11.

规约(reduce)

在现实生活中,我们常常需要对于数据做同一种操作,并将结果返回到指定的进程中,这个过程称为集合通信。例如,将数据分散到各个进程中,先在各个进程内进行求和,再在全局完成求和-平均这个操作,这个过程是一个规约的过程。

一般来说,集合通信包括通信、同步和计算三个功能。不过,目前我们暂时不需要关注整个过程,而是先使用一个规约函数去体验一下集合通信。

函数说明:

int MPI_Reduce(void * input_data_p, void * output_data_p, int count, MPI_Datatype datatype, MPI_Op operator, int dest_process, MPI_Comm comm)

规约函数,所有进程将待处理数据通过输入的操作子operator计算为最终结果并将它存入目标进程中。

void * input_data_p : 每个进程的待处理数据存放在input_data_p中; 
void * output_data_p : 存放最终结果的目标进程的地址;
int count : 缓冲区中的数据个数;
MPI_Datatype datatype : 数据项的类型;
MPI_Op operator : 操作子,例如加减;
int dest_process : 目标进程的编号;

实验说明:

使用函数MPI_Reduce来完成加法规约到根进程的操作,并在根进程打印出总和和平均值。

 

输出结果:

由于这里是测试用例,所以每个进程的数值都是取3.0。所以,输出结果应该是总和等于进程数乘以3,平均值应该是3。

#include <stdio.h>
#include <mpi.h>

int main(int argc, char **argv)
{
	int myid, numprocs;
	double local_num = 3.0; 

	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
	MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
    
    double global_num;
    
    //your code here
    MPI_Reduce(&local_num, &global_num, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
    //end of your code
    
    if(myid == 0) {
    	printf("Total sum = %f, avg = %f\n", global_num, global_num / numprocs);
	}

	MPI_Finalize();
	return 0;
}

12.

广播(broadcast)

在一个集合通信中,如果属于一个进程的数据被发送到通信子中的所有进程,这样的集合通信叫做广播。

这也是在现实中非常常用的功能。

函数说明:

int MPI_Bcast(void* buffer, int count, MPI_Datatype datatype, int source, MPI_Comm comm)

广播函数,从一个id值为source的进程将一条消息广播发送到通信子内的所有进程,包括它本身在内。

void*  buffer    缓冲区的起始地址; 
int   count     缓冲区中的数据个数; 
MPI_Datatype datatype   缓冲区中的数据类型; 
int   source     发送信息的进程id; 
MPI_Comm comm      通信子;

实验说明:

使用函数MPI_Bcast在根进程中发送一个数组到其他进程,并在其他进程中打印出来。

输出结果:

输出应该是这样的格式:
In process 1, arr[0]=1 arr[1]=2 arr[2]=3 arr[3]=4 arr[4]=5
In process 3, arr[0]=1 arr[1]=2 arr[2]=3 arr[3]=4 arr[4]=5
...
In process n, arr[0]=1 arr[1]=2 arr[2]=3 arr[3]=4 arr[4]=5
...

#include<stdio.h>
#include<mpi.h>

int main(int argc, char **argv)
{
	int myid, numprocs;
	int source = 0;
	int array[5]={1,2,3,4,5};
	int i;
	
	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
	MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
    
    if(myid == source) {
        for(i = 1; i <= 5; i++)
            array[i] = i;
    }
    
    //your code here
    MPI_Bcast(array, 5, MPI_INT, source, MPI_COMM_WORLD);
    //end of your code
    
    if(myid != source) {
    	printf("In process %d, ", myid);
        for(i = 0; i < 5; i++)
            printf("arr[%d]=%d\t", i, array[i]);
        printf("\n");
	}

	MPI_Finalize();
	return 0;
}

13.

收集(gather)

同样,有时候我们希望在一个进程中从所有进程获取信息,例如将所有进程中的一个数组都收集到根进程中作进一步的处理,这样的集合通信我们叫做收集。

函数说明:

int MPI_Gather(void* sendbuf, int sendcount, MPI_Datatype sendtype, 
               void* recvbuf, int recvcount, MPI_Datatype recvtype, 
               int root, MPI_Comm comm)

收集函数,根进程(目标进程)从所有进程(包括它自己)收集发送缓冲区的数据,根进程根据发送这些数据的进程id将它们依次存放到自已的缓冲区中.

void* sendbuf    发送缓冲区的起始地址
int sendcount    发送缓冲区的数据个数
MPI_Datatype sendtype    发送缓冲区的数据类型
void* recvbuf    接收缓冲区的起始地址
int recvcount    待接收的元素个数
MPI_Datatype recvtype    接收的数据类型
int root    接收进程id 
MPI_Comm comm    通信子

实验说明:

使用函数MPI_Gather在根进程中从所有进程接收一个数组,并在根进程中打印出来。

输出结果:

输出应该是这样的格式:
Now is process 1's data: arr[0]=1 arr[1]=2 arr[2]=3 arr[3]=4 arr[4]=5
Now is process 4's data: arr[0]=1 arr[1]=2 arr[2]=3 arr[3]=4 arr[4]=5
Now is process 2's data: arr[0]=1 arr[1]=2 arr[2]=3 arr[3]=4 arr[4]=5
...
Now is process n's data: arr[0]=1 arr[1]=2 arr[2]=3 arr[3]=4 arr[4]=5
...

#include<stdio.h>
#include<mpi.h>

int main(int argc, char **argv)
{
	int myid, numprocs;
	int dest = 0;
	int array[5]={1,2,3,4,5};
	int *rbuf; 
	int i,j;

	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
	MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
    
    if(myid == dest) {
    	rbuf=(int *)malloc(numprocs*5*sizeof(int));
	}

	//your code here
	MPI_Gather(array, 5, MPI_INT, rbuf, 5, MPI_INT, dest, MPI_COMM_WORLD);
	//end of your code
	
	if(myid == dest) {
		for(i=dest+1;i<numprocs;i++) {
			printf("Now is process %d's data: ", i);
			for(j=0;j<5;j++) {
				printf("array[%d]=%d\t", j, rbuf[i*5+j]);
			}
			printf("\n");
		}
	}
	
	MPI_Finalize();
	return 0;
} 

14.

散发(scatter)

在前面我们学习了收集(gather)操作,那么与之相对应也有一个相反的集合通信操作,即根进程向所有进程发送缓冲区的数据,称为散发。

需要特别说明的是,散发操作和广播操作的区别在于发送到各个进程的信息可以是不同的。

函数说明:

int MPI_Scatter(void* sendbuf, int sendcount, MPI_Datatype sendtype,
                void* recvbuf, int recvcount, MPI_Datatype recvtype,
                int root, MPI_Comm comm)

MPI_SCATTER是MPI_GATHER的逆操作,另外一种解释是根进程通过MPI_Send发送一条消息,这条消息被分成n等份,第i份发送给组中的第i个处理器, 然后每个处理器如上所述接收相应的消息。

void* sendbuf   发送缓冲区的起始地址
int sendcount   发送的数据个数
MPI_Datatype sendtype    发送缓冲区中的数据类型
void* recvbuf     接收缓冲区的起始地址
int recvcount  待接收的元素个数
MPI_Datatype recvtype    接收的数据类型
int root       发送进程id
MPI_Comm comm       通信子

实验说明:

使用函数MPI_Scatter在根进程中向所有进程发送对应数组,并在对应进程中打印出来。

输出结果:

输出应该是这样的格式:
Now is process 1: arr[0]=5 arr[1]=6 arr[2]=7 arr[3]=8 arr[4]=9
Now is process 4: arr[0]=20 arr[1]=21 arr[2]=22 arr[3]=23 arr[4]=24
Now is process 2: arr[0]=10 arr[1]=11 arr[2]=12 arr[3]=13 arr[4]=14
...
Now is process n: arr[0]=5n arr[1]=5n+1 arr[2]=5n+2 arr[3]=5n+3 arr[4]=5*n+4
...

#include<stdio.h>
#include<mpi.h>

int main(int argc, char **argv)
{
	int myid, numprocs;
	int source = 0;
	int *sbuf;
	int rbuf[5]; 
	int i;
	
	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
	MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
    
    if(myid == source) {
    	sbuf=(int *)malloc(numprocs*5*sizeof(int));
    	
    	for(i=0;i<numprocs*5;i++) {
    		sbuf[i]=i;
		}
	}

    // your code here
	MPI_Scatter(sbuf, 5, MPI_INT, rbuf, 5, MPI_INT, source, MPI_COMM_WORLD);
	// end of your code
	
	printf("Now is process %d: ", myid);
	for(i=0;i<5;i++) {
		printf("array[%d]=%d\t", i, rbuf[i]);
	}
	printf("\n");
	
	
	
	MPI_Finalize();
	return 0;
} 

15.

组的管理-创建(1)

接下来我们将学习对组的管理。

组是一个进程的有序集合,在实现中可以看作是进程标识符的一个有序集。组内的每个进程与一个整数rank相联系,序列号从0开始并且是连续的。我们可以在通信组中使用组来描述通信空间中的参与者并对这些参与者进行分级(这样在通信空间中为它们赋予了唯一的名字)。

由此可见,组是我们对进程集合更高一级的抽象,我们可以在组的基础上对各个进程进行更进一步的操作,例如通过虚拟拓扑来辅助并行操作的实现。

在这里我们先介绍两个特殊的预定义组,MPI_GROUP_EMPTY和MPI_GROUP_NULL。
需要特别说明的是,前者是一个空组的有效句柄,可以在组操作中作为一个参数使用;而后者是一个无效句柄,在组释放时会被返回。

现在我们可以开始学习第一个函数了。作为组管理的第一个小节,这个函数非常简单,是之后各个函数的基础。而本节重点是理解各个概念之间的关系。

函数说明:

int MPI_Comm_group(MPI_Comm comm, MPI_Group *group)

int MPI_Group_rank(MPI_Group group, int *rank)

MPI_Comm_group用来建立一个通信组对应的新进程组

MPI_Group_rank查询调用进程在进程组里的rank

实验说明:

建立一个与初始通信子MPI_COMM_WORLD相联系的组,打印出当前进程在进程组的rank。

输出结果:

输出结果格式应如下:
rank: 1
rank: 0
...
rank: n

顺序不唯一。

#include<stdio.h>
#include<mpi.h>

int main(int argc, char **argv)
{
	int myid, numprocs;
	MPI_Group group_world;
	int rank_of_group;
	
	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
	
	// your code here
	MPI_Comm_group(MPI_COMM_WORLD, &group_world);
	MPI_Group_rank(group_world, &rank_of_group);
	// end of your code
	
	printf("rank: %d\n", rank_of_group);

	MPI_Finalize();
	return 0;
}

16.

组的管理-创建(2)

在上一节我们知道,可以用MPI_Comm_group函数来获得与通信组MPI_COMM_WORLD相关联的组句柄。
那么我们可以用这个组句柄做什么呢?

首先,我们可以通过这个最原始的组句柄来创建更多的、满足我们需要的组。

在这里需要特别说明的是,MPI没提供凭空构造一个组的的机制,而只能从其它以前定义的组中构造。最基本的组是与初始通信子MPI_COMM_WORLD相联系的组(可通过函数MPI_COMM_GROUP获得〕,其它的组在该组基础上定义。

函数说明:

int MPI_Group_incl(MPI_Group old_group, int count, int *members, MPI_Group *new_group)

MPI_Group old_group : 旧进程组;
int count : members数组中元素的个数;
int *members : 旧进程组中需要放入新进程组的进程的编号;
MPI_Group *new_group : 新进程组;

基于已经存在的进程组创建一个新的组,并指明被包含(included)其中的成员进程。

实验说明:

基于与初始通信子MPI_COMM_WORLD相联系的组创建一个新的组,这个新的组的成员是通信者MPI_COMM_WORLD的奇数编号的进程。

输出结果:

输出结果格式应如下:
In process n: odd rank is x
...

需要特别说明的是,如果在偶数编号的进程中,也就是不属于这个组的进程中输出这个值,MPI_Group_rank会返回MPI_UNDEFINED作为group_rank的值,表示它不是 worker_group的成员,在MPICH里是-32766。

#include<stdio.h>
#include<mpi.h>

int main(int argc, char **argv)
{
	int myid, numprocs, odd_rank;
	MPI_Group group_world, odd_group;
	int i;
	int members[10];
		
	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
	
	MPI_Comm_group(MPI_COMM_WORLD, &group_world);

	for(i=0; i<numprocs/2; i++) {
		members[i] = 2*i+1 ;
	}
	
	// your code here
	MPI_Group_incl(group_world, numprocs/2, members, &odd_group);
	// end of your code
	
	MPI_Group_rank(odd_group, &odd_rank);
	
	printf("In process %d: odd rank is %d\n", myid, odd_rank);

	MPI_Finalize();
	return 0;
} 

17.

组的管理-创建(3)

同样,我们在基于旧进程组创建一个新的组的时候,可能希望排除一些成员进程。

当然,我们可以通过选择出剩下的成员进程的方法来达成我们的目的,但是MPI提供了更好的办法去实现它。

函数说明:

int MPI_Group_excl(MPI_Group old_group, int count, int *nonmembers, MPI_Group *new_group)

MPI_Group old_group : 旧进程组;
int count : nonmembers数组中元素的个数;
int *nonmembers : 旧进程组中不需要放入新进程组的进程的编号;
MPI_Group *new_group : 新进程组;

基于已经存在的进程组创建一个新的组,并指明不被包含(excluded)其中的成员进程。

实验说明:

基于与初始通信子MPI_COMM_WORLD相联系的组创建一个新的组,这个新的组的成员是通信者MPI_COMM_WORLD的偶数编号的进程。

输出结果:

输出结果格式应如下:
In process n: even rank is x
...

需要特别说明的是,如果在奇数编号的进程中,也就是不属于这个组的进程中输出这个值,MPI_Group_rank会返回MPI_UNDEFINED作为group_rank的值,表示它不是 worker_group的成员,在MPICH里这个值是-32766。

#include<stdio.h>
#include<mpi.h>

int main(int argc, char **argv)
{
	int myid, numprocs, even_rank;
	MPI_Group group_world, even_group;
	int i;
	int nonmembers[10];
		
	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
	
	MPI_Comm_group(MPI_COMM_WORLD, &group_world);

	for(i=0; i<numprocs/2; i++) {
		nonmembers[i] = 2*i+1 ;
	}
	
	// your code here
	MPI_Group_excl(group_world, numprocs/2, nonmembers, &even_group);
	// end of your code
	
	MPI_Group_rank(even_group, &even_rank);
	
	printf("In process %d: even rank is %d\n", myid, even_rank);

	MPI_Finalize();
	return 0;
} 

18.

组的管理-比较

有时候我们想要对两个进程组做最基本的判断,例如成员是否相同,次序是否一致等等。

MPI同样提供了这样的函数来完成这个功能。

函数说明:

int MPI_Group_compare(MPI_Group group1, MPI_Group group2, int *result)

MPI_Group group1 : 要比较的组1;
MPI_Group group2 : 要比较的组2;
int *result:结果;

如果在两个组中成员和次序完全相等,返回MPI_IDENT。例如在group1和group2是同一句柄时就会发生这种情况。如果组成员相同而次序不同则返回MPI_SIMILAR,否则返回MPI_UNEQUAL。

实验说明:

创建一个新的组,通过调整输出两个不同的结果。

输出结果:

输出结果格式应如下:
Now the groups are identical.
Now the groups are unequal.
...

#include<stdio.h>
#include<mpi.h>

int main(int argc, char **argv)
{
	int myid, numprocs;
	MPI_Group group_world, new_group_world;
	int members[5];
	int result;
	
	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
	
	MPI_Comm_group(MPI_COMM_WORLD, &group_world);
	
	members[0] = 0 ;
	
	MPI_Group_incl(group_world, 1, members, &new_group_world);
	
	if(myid == 0) {
		// your code here
		MPI_Group_compare(group_world, group_world, &result);
		// end of your code
		
		if (result == MPI_IDENT) {
			printf("Now the groups are identical.\n");
		}
		else if (result == MPI_SIMILAR) {
			printf("Now the groups are similar.\n");
		}
		else {
			printf("Now the groups are unequal.\n");
		}
		
		// your code here
		MPI_Group_compare(group_world, new_group_world, &result);
		// end of your code
		
		if (result == MPI_IDENT) {
			printf("Now the groups are identical.\n");
		}
		else if (result == MPI_SIMILAR) {
			printf("Now the groups are similar.\n");
		}
		else {
			printf("Now the groups are unequal.\n");
		}
	}

	MPI_Finalize();
	return 0;
}

19.

组的管理-相对编号

在创建组之后,可能会有这个疑惑:如果知道了在组MPI_COMM_WORLD中某些进程的编号,如何根据这些编号来操作在不同组的同一进程来完成不同的任务呢?

MPI提供了这样的函数以应付这种常见的情景。

函数说明:

int MPI_Group_translate_ranks(MPI_Group group1, int count, int *ranks1, MPI_Group group2, int *ranks2)

MPI_Group group1 : 进程组1;
MPI_Group group2 : 进程组2;
int count : ranks1和ranks2数组中元素的个数;
int *ranks1 : 进程组1中有效编号组成的数组;
int *ranks2 : ranks1中的元素在进程组2中的对应编号

检测两个不同组中相同进程的相对编号。如果属于进程组1的某个进程可以在ranks1中找到,而这个进程不属于进程组2,则在ranks2中对应ranks1的位置返回值为MPI_UNDEFINED。

实验说明:

建立两个进程组,打印出进程组2中对应进程组1的进程的编号。

输出结果:

输出结果格式应如下:
The rank in group2 is: -32766
The rank in group2 is: 0
...

#include<stdio.h>
#include<mpi.h>

int main(int argc, char **argv)
{
	int myid, numprocs;
	MPI_Group group_world, group1, group2;
	int i;
	int ranks1[10];
	int ranks2[10];
	int ranks_output[10];
		
	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
	
	MPI_Comm_group(MPI_COMM_WORLD, &group_world);

	for(i=0; i<numprocs-1; i++) {
		ranks1[i] = i ;
		ranks2[i] = i+1 ;
	}
	
	MPI_Group_incl(group_world, numprocs-1, ranks1, &group1);
	MPI_Group_incl(group_world, numprocs-1, ranks2, &group2);
	
	// your code here
	MPI_Group_translate_ranks(group1, numprocs-1, ranks1, group2, ranks_output);
	// end of your code
	
	if (myid == 0) {
		for (i=0; i<numprocs-1; i++) {
			printf("The rank in group2 is: %d\n", ranks_output[i]);
		}
	}

	MPI_Finalize();
	return 0;
}

20.

组的管理-集合类操作

对于两个集合,我们经常对其进行各种各样的集合操作,例如交/并。

MPI同样提供了对组的集合类操作。

函数说明:

int MPI_Group_union(MPI_Group group1, MPI_Group group2, MPI_Group *newgroup)

int MPI_Group_intersection(MPI_Group group1,MPI_Group group2,MPI_Group *newgroup) 

int MPI_Group_difference(MPI_Group group1,MPI_Group group2,MPI_Group *newgroup)

MPI_Group group1 : 要操作的组1;
MPI_Group group2 : 要操作的组2;
MPI_Group *newgroup:新的组;

实验说明:

将组按照编号的奇偶分为两个新的组,再用并操作将它们合起来,输出各个进程在新的组的编号。

输出结果:

In process 0: union rank is 2
In process 1: union rank is 0
In process 2: union rank is 3
In process 3: union rank is 1
...

#include<stdio.h>
#include<mpi.h>

int main(int argc, char **argv)
{
	int myid, numprocs, union_rank;
	MPI_Group group_world, odd_group, even_group, union_group;
	int i;
	int members[10];
		
	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
	
	MPI_Comm_group(MPI_COMM_WORLD, &group_world);

	for(i=0; i<numprocs/2; i++) {
		members[i] = 2*i+1 ;
	}
	
	
	MPI_Group_incl(group_world, numprocs/2, members, &odd_group);
	MPI_Group_excl(group_world, numprocs/2, members, &even_group);
	
	// your code here
	MPI_Group_union(odd_group, even_group, &union_group);
	// end of your code
	
	MPI_Group_rank(union_group, &union_rank);
	
	printf("In process %d: union rank is %d\n", myid, union_rank);

	MPI_Finalize();
	return 0;
} 

21.

组的管理-释放

既然有了组的构造,那么与之对应也存在组的析构。

函数说明:

int MPI_Group_free(MPI_Group *group)

调用函数会标记一个被释放的组对象,组句柄被调用置为MPI_GROUP_NULL。
任何正在使用此组的操作将正常完成。

实验说明:

建立一个进程组,打印出它的size,然后释放它。

输出结果:

输出结果格式应如下:
Now the size is n
Now the group is freed.

#include<stdio.h>
#include<mpi.h>

int main(int argc, char **argv)
{
	int myid, numprocs;
	MPI_Group group_world;
	int size0;
	
	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
	
	MPI_Comm_group(MPI_COMM_WORLD, &group_world);
	
	MPI_Group_size(group_world, &size0);
	
	if(myid == 0) {
		printf("Now the size is %d\n", size0);
	}
	
	// your code here
	MPI_Group_free(&group_world);
	// end of your code
	
	if(myid == 0) {
		if (group_world == MPI_GROUP_NULL)
			printf("Now the group is freed.\n");
	}

	MPI_Finalize();
	return 0;
}

22.

通信子的管理-复制

在之前的学习中,我们经常使用系统帮助我们创建的初始组内通信子MPI_COMM_WORLD作为通信子的输入。

其实,还有两个系统默认创建的通信子,一个是COMM_SELF,另一个是COMM_NULL。
COMM_SELF仅仅包含了当前进程,而COMM_NULL则什么进程都没有包含。

在通信子的创建中,需要特别注意的是MPI中有一个"鸡生蛋, 蛋生鸡"的特点,即所有MPI通信子的创建都是由基础通信子,即MPI_COMM_WORLD(是在MPI的外部被定义的),创建的。而这些被创建的通信子又可以作为新的通信子创建的基础。

这个模型是经过讨论后确定的,目的是为了提高用MPI写程序的安全性。

函数说明:

int MPI_Comm_dup(MPI_Comm comm,MPI_Comm *newcomm)

MPI_Comm comm : 旧的通信子;
MPI_Comm *newcomm : 新的通信子;

复制已存在的通信子comm。

实验说明:

复制一个新的通信子,需要特别说明的是,结果显示MPI_IDENT表示上下文(context)和组(group)都相同,MPI_CONGRUENT表示上下文不同(different)但组完全相同(identical),MPI_SIMILAR表示上下文不同,组的成员相同但次序不同(similar),否则就是MPI_UNEQUAL。

输出结果:

输出结果格式应如下:
The comms are congruent.
...

#include<stdio.h>
#include<mpi.h>

int main(int argc, char **argv)
{
	int myid, numprocs;
	MPI_Comm new_comm; 
	int result;
	
	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
    
    // your code here
	MPI_Comm_dup(MPI_COMM_WORLD, &new_comm);
	// end of your code
		
	MPI_Comm_compare(MPI_COMM_WORLD, new_comm, &result);
	
	if(myid == 0) {
		
		if ( result == MPI_IDENT) {
			printf("The comms are identical.\n");
		}
		else if ( result == MPI_CONGRUENT ) {
			printf("The comms are congruent.\n");
		}
		else if ( result == MPI_SIMILAR ) {
			printf("The comms are similar.\n");
		}
		else if ( result == MPI_UNEQUAL ) {
			printf("The comms are unequal.\n");
		}
	}

	MPI_Finalize();
	return 0;
}

23.

通信子的管理-创建

在实际开发中,我们往往需要很多不同的通信子来满足需求,这时候就需要创建新的通信子。

函数说明:

int MPI_Comm_create(MPI_Comm comm, MPI_Group group, MPI_Comm *newcomm)

MPI_Comm comm : 旧的通信子;
MPI_Group group : 与comm相关联的组或其子集;
MPI_Comm *newcomm : 新的通信子;

用由group所定义的通信组及一个新的上下文创建了一个新的通信子newcomm。对于不在group中的进程,函数返回MPI_COMM_NULL。

实验说明:

复制一个新的通信子,并以此为基础创建一个新的通信子。由于示例是用奇数编号的进程来创建通信子的,所以只在奇数进程中输出结果。

输出结果:

输出结果格式应如下:
The new comm's size is 2.
The new comm's size is 2.
...

注意,如果没有添加创建函数的代码,天河可能会由于超时返回长时间没有响应的提示信息。

#include<stdio.h>
#include<mpi.h>

int main(int argc, char **argv)
{
	int myid, numprocs, new_numprocs;
	MPI_Group group_world, odd_group;
	MPI_Comm new_comm;
	int i;
	int members[10];
		
	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
	
	MPI_Comm_group(MPI_COMM_WORLD, &group_world);

	for(i=0; i<numprocs/2; i++) {
		members[i] = 2*i+1 ;
	}
	
	MPI_Group_incl(group_world, numprocs/2, members, &odd_group);
	
	// your code here
	MPI_Comm_create(MPI_COMM_WORLD, odd_group, &new_comm);
	// end of your code
	
	if (myid % 2 != 0 ) {
	    MPI_Comm_size(new_comm, &new_numprocs);
	    
		printf("The new comm's size is %d.\n", new_numprocs);
	} 
	

	MPI_Finalize();
	return 0;
} 

24.

通信子的管理-划分

有时候我们希望根据拓扑来创建不同的域,例如创建一个二维数组,显然一个个创建是很不方便的,这时候我们需要用到一个新的函数来进行划分。

函数说明:

int MPI_Comm_split(MPI_Comm comm, int color, int key, MPI_Comm *newcomm)

MPI_Comm comm : 旧的通信子,也就是被划分的域;
int color : 子域的标识,也就是被划分出来的每个子域都对应一个color,每一个子域包含具有同样color的所有进程;
int key : 在每一个子域内, 进程按照key所定义的值的次序进行排列。
MPI_Comm *newcomm : 新的通信子;

函数将与comm相关的域划分为若干不相连的子域,根据color和key参数决定每个进程所处的位置。

实验说明:

创建一个二维数组,根据行与列进行求和,在每个进程中输出坐标和求出的和。

输出结果:

输出结果格式应如下:
I'm process n, my coordinates are (x, y), row sum is p, column sum is q.
...

#include<stdio.h>
#include<mpi.h>

int main(int argc, char **argv)
{
	int myid, numprocs;
	MPI_Comm row_comm, column_comm;
	int myrow, mycolumn;
	int color = 3 ;
		
	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
	
	myrow = myid / color ;
	mycolumn = myid % color ;

	// your code here
	MPI_Comm_split(MPI_COMM_WORLD, myrow, mycolumn, &row_comm);  
    MPI_Comm_split(MPI_COMM_WORLD, mycolumn, myrow, &column_comm);  
    // end of your code
	
	int rowsum, columnsum;
	
	rowsum = myid;
	columnsum = myid;
	
	MPI_Allreduce(MPI_IN_PLACE, &rowsum, 1, MPI_INT, MPI_SUM, row_comm);  
    MPI_Allreduce(MPI_IN_PLACE, &columnsum, 1, MPI_INT, MPI_SUM, column_comm); 
    
    printf("I'm process %d, my coordinates are (%d, %d), row sum is %d, column sum is %d\n", myid, myrow, mycolumn,  
rowsum, columnsum); 
    
	MPI_Finalize();
	return 0;
} 

25.

通信子的管理-释放

同样,通信子也存在析构的操作。

函数说明:

int MPI_Comm_free(MPI_Comm *comm)

MPI_Comm *comm : 通信子;

用由group所定义的通信组及一个新的上下文创建了一个新的通信子newcomm。对于不在group中的进程,函数返回MPI_COMM_NULL。

实验说明:

这是一个标志通信对象撤消的集合操作。值得注意的是,这个函数操作只是将句柄置为MPI_COMM_NULL,任何使用此通信子的挂起操作都会正常完成;仅当没有对此对象的活动引用时,它才会被实际撤消。

输出结果:

输出结果格式应如下:
The comm is freed.
...

#include<stdio.h>
#include<mpi.h>

int main(int argc, char **argv)
{
	int myid, numprocs;
	MPI_Comm new_comm;
	
	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
	
	MPI_Comm_dup(MPI_COMM_WORLD, &new_comm);
	
	// your code here
	MPI_Comm_free(&new_comm);
	// end of your code
	
	if(myid == 0) {
		if (new_comm == MPI_COMM_NULL)
			printf("Now the comm is freed.\n");
	}

	MPI_Finalize();
	return 0;
}

 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MPI基础实验中,常见的错误和解决方法如下: 1. 忘记初始化MPI 在使用MPI之前,需要先初始化MPI环境。如果忘记初始化,会导致MPI程序无法正常运行。 解决方法:在程序开始处添加MPI_Init()函数,程序结束前添加MPI_Finalize()函数。 2. 错误的进程数 在编写MPI程序时,需要指定运行程序的进程数。如果指定的进程数与实际运行的进程数不一致,会导致程序出错。 解决方法:在运行MPI程序时,通过命令行参数或者MPI API指定正确的进程数。 3. 忘记同步进程 MPI程序中的不同进程之间是相互独立的,如果一个进程需要等待其他进程完成后才能继续执行,必须进行同步。 解决方法:使用MPI_Barrier()函数进行同步。这个函数会阻塞当前进程,直到所有进程都调用了该函数后才会返回。 4. 错误的通信方式 MPI提供了多种通信方式,包括点对点通信和集体通信等。如果使用错误的通信方式,会导致程序出错。 解决方法:根据具体需要选择正确的通信方式。可以参考MPI的文档和示例程序。 5. 内存泄漏 MPI程序中可能存在内存泄漏,导致程序运行时间越来越长,最终崩溃。 解决方法:注意及时释放不再使用的内存,避免内存泄漏。可以使用内存检测工具检查MPI程序中的内存泄漏问题。可以使用Valgrind等工具进行检测。 以上是MPI基础实验中常见的错误和解决方法,希望能对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值