实习笔记Day8(2022.8.16)

一.总结

继续学习MPI并行编程模型,在虚拟机里搭建了MPI编译和运行环境,并进行了几个简单的MPI编程练习

二.学习笔记 

1.消息传递模型 

(1)理解 

其实就是指程序通过在进程间传递消息来完成一些任务,消息可以理解为带有一些信息和数据的一个数据结构

(2)一些术语

主进程(master process)

从进程(slave process) 

本地的(locolly):在当前进程可见的

(3)一些简例

1)主进程可以通过向从进程发送一个描述任务的消息,把这个任务分配给它 

2)一个并发的排序程序,可以在当前进程中对本地数据排序,然后把排好序的数据发送给邻居进程进行合并操作

2.MPI常用函数

见20220813日报,在第3点中有些地方回顾到了相应函数原型及用法

3.MPI编程练习

(1)MPI版本的Hello World 

#include <mpi.h>                 //需要用到的函数都在头文件mpi.h中
#include <stdio.h>
int main(int argc, char **argv)
{ 
    MPI_Init(&argc, &argv);      //MPI开始部分标识
    printf("Hello World!\n");
    MPI_Finalize();              //MPI结束部分标识
    return 0;
}

(2)获取进程数量 

在MPI编程中,常常需要获取指定通信域的进程个数,以确定程序的规模

函数MPI_Comm_size的第一个参数是通信子,缺省值为MPI_COMM_WORLD,第二个参数返回进程数

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

int main(int argc, char **argv)
{
	int numprocess;
	MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocess);
	printf("Hello World! The number of processes is %d\n",numprocess)
	MPI_Finalize();
	return 0;
}

(3)获取进程id 

同样,我们也常常需要输出当前进程的id,以此来判断具体哪个进程完成了对应的任务 

函数MPI_Comm_rank获得当前进程在指定通信域中的编号,将自身与其他程序区分

其中,第一个参数是通信子,第二个参数返回进程的编号。

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

int main(int argc, char **argv)
{
	int myid, numprocess;
	MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocess);
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
	printf("Hello World!I'm rank %d of %d\n", myid, numprocess);
	MPI_Finalize();
	return 0;
}

(4)消息传递

在并行编程中,消息传递占了很大的比重。良好的消息传递是正常完成进程/节点之间操作的基本条件

最基本的发送/接收函数都是以缓冲区作为端点,通过参数配置来完成指定操作 

int MPI_Send(void* msg_buf_p, int msg_size,
             MPI_Datatype msg_type,
             int dest, int tag,
             MPI_Comm communicator)

 作用:发送缓冲区的信息到目标进程

void* msg_buf_p : 发送缓冲区的起始地址;
int buf_size : 缓冲区大小;
MPI_Datatype msg_type : 发送信息的数据类型;
int dest :目标进程的id值;
int tag : 消息标签;
MPI_Comm communicator : 通信子;
int MPI_Recv(void* msg_buf_p, int buf_size,
             MPI_Datatype msg_type, int source, int tag,
             MPI_Comm communicator, MPI_Status *status_p)

void* msg_buf_p : 接收的缓冲区的起始地址;
int buf_size : 缓冲区大小;
MPI_Datatype msg_type : 接收信息的数据类型;
int dest :源进程的id值;
int tag : 消息标签;
MPI_Comm communicator : 通信子;
MPI_Status *status_p : status_p对象,包含实际接收到的消息的有关信息 

任务:id为0的进程当作根进程,然后在除此之外的进程中使用函数MPI_Send发送一句"hello  

world!"到根进程中,然后在根进程中把这些信息打印出来

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

int main(int argc, char **argv)
{
	int myid, numprocs, source;
	MPI_Status status;
	char message[100];

	MPI_Init(&argc, &argv);
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
    
    if(myid != 0) {
    	strcpy(message, "hello world!");
    	MPI_Send(message, strlen(message)+1, MPI_CHAR, 0, 99, MPI_COMM_WORLD);
	}
	else { //myid == 0
		for(source=1; source<numprocs; source++) {
			MPI_Recv(message, 100, MPI_CHAR, source, 99, MPI_COMM_WORLD, &status);
			printf("%s\n", message);
		}
	}

	MPI_Finalize();
	return 0;
}

(5)规约(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来完成加法规约到根进程的操作,并在根进程打印出总和和平均值 

#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;
    MPI_Reduce(&local_num, &global_num, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
    
    if(myid == 0) {
    	printf("Total sum = %f, avg = %f\n", global_num, global_num / numprocs);
	}

	MPI_Finalize();
	return 0;
}

(6)广播(broadcast)

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

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

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

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

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

#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;
    }
    
    MPI_Bcast(array, 5, MPI_INT, source, MPI_COMM_WORLD);
    
    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;
}

(7)收集(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在根进程中从所有进程接收一个数组,并在根进程中打印出来 

#include<stdio.h>
#include<mpi.h>
#include <stdlib.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));
	}

	MPI_Gather(array, 5, MPI_INT, rbuf, 5, MPI_INT, dest, MPI_COMM_WORLD);
	
	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;
} 

(8)散发(scatter) 

与收集相对应有一个相反的集合通信操作,即根进程向所有进程发送缓冲区的数据,称为散发

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

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在根进程中向所有进程发送对应数组,并在对应进程中打印出来 

#include<stdio.h>
#include<mpi.h>
#include <stdlib.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;
		}
	}

	MPI_Scatter(sbuf, 5, MPI_INT, rbuf, 5, MPI_INT, source, MPI_COMM_WORLD);
	
	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;
} 

(9)运行时间 

在MPI编程我们可以使用MPI_Wtime函数在并行代码中计算运行时间,用MPI_Wtick来查看精度 

double MPI_Wtime(void)

double MPI_Wtick(void)

MPI_WTIME返回一个用浮点数表示的秒数, 它表示从过去某一时刻到调用时刻所经历的时间

MPI_WTICK返回MPI_WTIME的精度,单位是秒,可以认为是一个时钟滴答所占用的时间

任务:使用函数MPI_Wtime计算并行代码的运行时间,并且在两次计算时间的函数之间用函数MPI_WTICK打印出精度 

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

int main(int argc, char **argv)
{
	int myid, numprocs;
	double start, finish;
	
	MPI_Init(&argc, &argv);

    MPI_Comm_rank(MPI_COMM_WORLD, &myid);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);

	start = MPI_Wtime();
	
	printf("The precision is: %f\n", MPI_Wtick());
	
	finish = MPI_Wtime();
	
	printf("Hello World!I'm rank %d of %d, running %f seconds.\n", myid, numprocs, finish-start);

	MPI_Finalize();
	return 0;
}

(10)同步 

在实际工作中,我们常常会因为许多原因需要进行同步操作

例如,希望保证所有进程中并行代码在某个地方同时开始运行,或者在某个函数调用结束之前不能返回

这时候我们就需要使用到MPI_Barrier函数

int MPI_Barrier(MPI_Comm comm)

MPI_Comm comm : 通信子;

阻止调用直到communicator中所有进程已经完成调用,就是说,任意一次进程的调用只能在所有communicator中的成员已经开始调用之后进行

任务:在计算运行时间的信息之前调用MPI_Barrier函数完成同步 

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

int main(int argc, char **argv)
{
	int myid, numprocs;
	double start, finish;
	
	MPI_Init(&argc, &argv);

    MPI_Comm_rank(MPI_COMM_WORLD, &myid);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);

	MPI_Barrier(MPI_COMM_WORLD);
	
	start = MPI_Wtime();
	
	printf("The precision is: %f\n", MPI_Wtick());
	
	finish = MPI_Wtime();
	
	printf("Hello World!I'm rank %d of %d, running %f seconds.\n", myid, numprocs, finish-start);

	MPI_Finalize();
	return 0;
}

三.工作记录

1.在虚拟机搭建了MPI编程环境

2.进行了几个MPI基础函数的编程练习

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值