栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

MPI基础

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

MPI基础

消息传递接口(Message Passing Interface,MPI):定义了一个可以被C/C++/Fortran程序调用的函数库,来实现进程之间的通信。即MPI不是一种编程语言,而是一个编程接口标准。

OpenMP/Pthreads与MPI区别:前者是共享式内存(单节点)并行,后者是分布式内存(多节点)并行。

编译与执行方法:MPICH是MPI的一个重要实现,linux下安装命令为sudo apt-get install -y mpich,编译命令为mpicc -o xxx xxx.c,执行命令为mpiexec -n 进程数量 ./xxx。

一、基本函数
//初始化函数:系统进行必要的初始化设置,如为消息缓冲区分配空间,为进程指定进程号等
//参数argc_p和argv_p分别指向main函数参数argc、argv,若不需要则设为NULL
int MPI_Init(int* argc_p, char*** argv_p);

//终止函数:告知系统MPI使用完毕,相关资源可以释放(但调用后进程仍然存在)
int MPI_Finalize();

//通信子:一组可以互相发送消息的进程集合,类型为MPI_Comm;
//MPI_COMM_WORLD:由用户启动的所有进程所组成的通信子
int comm_sz, my_rank; //通信子大小,进程号
int MPI_Comm_size(MPI_Comm comm, int* comm_sz_p); //获取通信子大小
int MPI_Comm_rank(MPI_Comm_comm, int* my_rank_p); //获取当前进程号
点到点通信
//发送函数
int MPI_Send(
    void* msg_buf_p,         
    int msg_size,            
    MPI_Datatype msg_type,   
    int dest,                
    int tag,                 
    MPI_Comm comm            
);
//接收函数
int MPI_Recv(
    void* msg_buf_p,         
    int msg_size,            
    MPI_Datatype msg_type,   
    int source,              
    int tag,                 
    MPI_Comm comm,           
    MPI_Status* status_p     
);

//status_p参数使用说明
MPI_Status status;  //定义status
......              //将&status作为参数传递给MPI_Recv函数
status.SOURCE       //获取发送者进程号
status.TAG          //获取消息标签
int count;          //消息数据量
MPI_Get_count(&status, msg_type, &count)  //获取数据量
//int MPI_Get_count(MPI_Status* status_p, MPI_Datatype type, int* count_p);

发送与接收函数的额外说明:

  1. 消息匹配:数据类型、标签值、通信子要相同,进程号要对应,recv_msg_size不小于send_msg_size。
  2. 通配符:对于接收函数,可以将第四个参数写为常量MPI_ANY_SOURCE来接收任意进程发来的消息,可以将第五个参数写为MPI_ANY_TAG来接收任意标签的消息。
  3. status_p参数:如果不使用,将其设为MPI_STATUS_IGNORE。什么时候使用呢?在接收者不知道发送者的进程号、发送消息的标签、或者消息的数据量时进行使用。
  4. 阻塞与非阻塞通信:留坑
//示例:test.c
#include 
#include 
#include 

const int MAX_LEN = 100;
int main(int argc, char* argv[])
{
    char send_buf[MAX_LEN], recv_buf[MAX_LEN];
    int comm_sz, my_rank;
    MPI_Init(NULL, NULL);
    MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    if (my_rank != 0) {
        sprintf(send_buf, "Greetings from process %d of %d!n", my_rank, comm_sz);
        MPI_Send(send_buf, strlen(send_buf), MPI_CHAR, 0, 0, MPI_COMM_WORLD);
    } else {
        printf("This is process %d of %d!n", my_rank, comm_sz);
        for (int i = 1; i < comm_sz; i++) {
            MPI_Recv(recv_buf, MAX_LEN, MPI_CHAR, MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
            printf("%sn", recv_buf);
        }
    }

    return 0;
}

//编译:mpicc -o test test.c
//执行:mpiexec -n 4 ./test
//输出(也可能是其他顺序,如132等)
This is process 0 of 4!
Hello World from process 1 of 4!
Hello World from process 2 of 4!
Hello World from process 3 of 4!

关于I/O:

  • 输入:大多数MPI实现只允许0号进程接收输入,若有需要,0号进程再将消息发送给其他进程。
  • 输出:由于大多数MPI实现不提供对I/O设备的调度,多个进程同时写标准输出stdout时,会抢占竞争资源,输出顺序无法确定。若不希望出现乱序输出,可以让0号进程外的其他进程向0号进程发送它的输出,然后0号进程按照进程号的顺序打印输出。
二、集合通信

点对点通信:发送和接收是一个进程到一个进程。集合通信:涉及通信子中所有进程的通信函数。

归约函数
//函数对输入数据进行操作(operator),并将输出数据存储到目的进程(dest_process)
int MPI_Reduce(
    void* input_data_p, 
    void* output_data_p,   //只用在dest_process上,其他进程传递NULL
    int count, 
    MPI_Datatype datatype,
    MPI_Op operator,       //MPI_Op也是个预定义类型,包含多个归约操作符
    int dest_process, 
    MPI_Comm comm
);

//MPI_Reduce中只有目的进程得到结果,MPI_AllReduce使每个进程都得到结果
int MPI_AllReduce(
    void* input_data_p, 
    void* output_data_p, 
    int count, 
    MPI_Datatype datatype,
    MPI_Op operator,  
    MPI_Comm comm
);
广播函数
//一个进程中的数据被发送到通信子中的其他所有进程
int MPI_Bcast(
    void* data_p, 
    int count, 
    MPI_Datatype datatype, 
    int source, 
    MPI_Comm comm
);
散射函数
//散射函数:将send_buf_p所指内容分成comm_sz份,第一份给0号进程,第二份给1号进程...
int MPI_Scatter(
    void* send_buf_p, 
    int send_count,          //为local_n=n/comm_sz
    MPI_Datatype send_type, 
    void* recv_buf_p,
    int recv_count,          //为local_n=n/comm_sz
    MPI_Datatype recv_type, 
    int source, 
    MPI_Comm comm
);
聚集函数
//聚集函数:与散射函数相对应,各个进程将自己的数据聚集到目的进程
int MPI_Gather(
    void* send_buf_p, 
    int send_count, 
    MPI_Datatype send_type, 
    void* recv_buf_p,
    int recv_count, 
    MPI_Datatype recv_type, 
    int dest, 
    MPI_Comm comm
);

//全局聚集函数:聚集结果所有进程均得到(参数列表不变)
int MPI_Allgather(...);
三、派生数据类型

在分布式内存系统中,通信的开销远远大于计算,并且使用多条消息发送一定数量的数据,明显比一条消息发送等量数据耗时。因此,如果可以减少消息的数量,就能够提高程序的性能。
派生数据类型:通过存储数据项的类型以及它们在内存中的相对位置(以字节为单位),来表示内存中数据项的任意集合(为什么不用结构体呢?因为MPI_Datatype没有定义结构体,而通信函数参数类型确是MPI_Datatype,所以需要自己进行创建封装)。

int MPI_Type_create_struct(
    int count,                        //元素个数,后面的数组参数都有count个元素
    int array_of_blocklengths[],      //表示count个元素中每个元素长度,如第一个元素为长度为m的数组,则array_of_blocklengths[0]=m
    MPI_Aint array_of_displayments[], //MPI_Aint:整数型,可以表示系统地址,数组存储各元素距离消息起始位置的偏移量(以字节为单位)
    MPI_Datatype array_of_types[],    //表示各个元素的数据类型
    MPI_Datatype* new_type_p          //输出
);   

//需要说明的是:
//1.使用input_mpi_t之前,必须先用一个函数调用指定它:
int MPI_Type_commit(MPI_Datatype* new_mpi_t_p);
//2.若input_mpi_t不再使用,可以释放内存空间:
int MPI_Type_free(MPI_Datatype* old_mpi_t_p);
四、时间性能

MPI_Wtime函数返回墙上时钟时间(经历的全部时间,包括空闲等待时间),C/C++的clock函数返回的是CPU时间(用户代码、库函数和系统调用函数消耗的时间),不包括空闲时间。

//MPI程序计时方法
//函数原型:double MPI_Wtime(void);
double start, finish;
start = MPI_Wtime();
...
finish = MPI_Wtime();

这样做,每个进程都会给出一个时间,而我们需要的是一个总的单独时间。理想情况是,所有进程同时开始运行,并行时间取决于“最慢”进程。

double local_start, local_finish, local_elapsed, elapsed;
MPI_Barrier(comm); //同步函数:直到所有进程都调用了该函数后才继续执行
local_start = MPI_Wtime();
...
local_finish = MPI_Wtime();
local_elapsed = local_finish - local_start;
MPI_Reduce(&local_elapsed, &elapsed, 1, MPI_DOUBLE, MPI_MAX, 0, comm);
if(my_rank == 0) printf...
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/308766.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号