机群应用开发 并行编程原理及 程序设计 Parallel Programming: Fundamentals and Implementation 曙光信息产业股份有限公司 解决方案中心 高性能计算方案部 2011.7 2018年11月.

Slides:



Advertisements
Similar presentations
C enter of C omputational C hemistry 并行计算机与并行计算 张鑫 理论与计算化学国际合作研究中心 分子反应动力学国家重点实验室.
Advertisements

CSIM, PU C Language Introduction to the C Programming Language 重覆敘述 (for,while,break,continue) 適合重複性的計算或判斷.
辅导老师:隋秀峰 2008年10月22日 Lab3:MPI Programming 辅导老师:隋秀峰 2008年10月22日.
郎显宇 中国科学院计算机网络信息中心 超级计算中心
C语言程序设计 主讲教师 :张群燕 电话:
親愛的老師您好 感謝您選用本書作為授課教材,博碩文化準備本書精選簡報檔,特別摘錄重點提供給您授課專用。 說明: 博碩文化:
Loops.
周纯葆 中国科学院计算机网络信息中心 超级计算中心
C语言基础——指针的高级应用 Week 05.
机群应用开发 并行编程原理及 程序设计 Parallel Programming: Fundamentals and Implementation 曙光信息产业有限公司 2017年9月 2017年9月 MPI并行程序设计.
C语言程序设计 第八章 函数.
MPI并行程序设计简介 曙光信息产业(北京)有限公司 2018年11月.
C语言程序设计 第十二章 位运算.
第一章 C语言概述.
函數 授課:ANT 日期:2009/3/24.
并行计算实验上机 国家高性能计算中心(合肥).
并行算法实践.
核探测与核电子学国家重点实验室 报告人:董磊 指导老师:宋克柱
并行编程原理及 程序设计 Parallel Programming: Fundamentals and Implementation
补充内容 结构体 概述 定义结构体类型和定义结构体变量 结构体变量的引用 结构体变量的初始化 指针与结构体 用typedef定义类型的别名.
MPI并行编程      报告人:李俊照.
C 程式設計— 指標.
函數 授課:ANT 日期:2011/3/28.
If … else 選擇結構 P27.
C 程式設計— 語言簡介 台大資訊工程學系 資訊系統訓練班.
Chap 2 用C语言编写程序 2.1 在屏幕上显示 Hello World! 2.2 求华氏温度 100°F 对应的摄氏温度
C 程式設計— 控制敘述 台大資訊工程學系 資訊系統訓練班.
第四讲 MPI并行程序设计 课程网站:CourseGrading buaa.edu.cn 主讲教师: 赵长海
第十一章 文件 文件概述 文件操作 文件操作实例 本章小结 作业: 练习:
并行计算.
基于MPI的并行程序设计 王振海 西北工业大学理学院 西北工业大学高性能计算研究与发展中心 2018/11/28.
C++ 程式設計— 語言簡介 台大資訊工程學系 資訊系統訓練班.
STRUCTURE 授課:ANT 日期:2010/5/12.
计算概论 第十八讲 C语言高级编程 结构与习题课 北京大学信息学院.
Function.
第三章 C++中的C 面向对象程序设计(C++).
程式撰寫流程.
Linux下的程序编译和运行 曙光信息产业股份有限公司.
C语言程序设计 李祥.
程序设计专题一 结构化程序设计与递归函数 主讲教师: 刘新国.
第5章 堆疊(Stacks) 5-1 堆疊的基礎 5-2 堆疊的表示法 5-3 堆疊的應用 - 運算式的計算與轉換
Introduction to the C Programming Language
进程操作.
1. 說明一個一維整數陣列passwd,下標範圍0至49 2. 在屏幕顯示 "Enter password"
C语言 程序设计基础与试验 刘新国、2012年秋.
期中考试成绩分布 《程序设计》-2017年秋.
字符串和字符数组 字符串的输入和输出 字符串的基本操作
并行计算简介 高性能事业部:曹振南 年4月.
十二、并行程序设计基础.
第十章 指针.
第5讲 结构化程序设计(Part II) 周水庚 2018年10月11日.
第1章 概述 本章要点: C语言程序结构和特点 C语言程序的基本符号与关键字 C语言程序的编辑及运行 学习方法建议:
C语言概述 第一章.
C语言环境配置.
C程序设计.
OpenMP程序设计 2019/4/25.
Introduction to the C Programming Language
Chap 5 函数 5.1 计算圆柱体积 5.2 使用函数编写程序 5.3 变量与函数.
Chap 5 函数 5.1 计算圆柱体积 5.2 数字金字塔 5.3 复数运算.
程式的時間與空間 Time and Space in Programming
第十四章 若干深入问题和C独有的特性 作业: 函数指针 函数作参数 函数副作用 运算 语句 位段 存储类别 编译预处理
Introduction to the C Programming Language
第三章 基本的輸出與輸入函數 (Basic Output & Input Function)
C程序设计.
Introduction to the C Programming Language
第七章  数 组.
基本資料型態 變數與常數 運算子 基本的資料處理 授課:ANT 日期:2014/03/03.
第三章 流程控制 程序的运行流程 选择结构语句 循环结构语句 主讲:李祥 时间:2015年10月.
函式庫補充資料 1.
C++语言程序设计 C++语言程序设计 第二章 基本数据类型与表达式 第十一组 C++语言程序设计.
Presentation transcript:

机群应用开发 并行编程原理及 程序设计 Parallel Programming: Fundamentals and Implementation 曙光信息产业股份有限公司 解决方案中心 高性能计算方案部 2011.7 2018年11月

参考文献 黄铠,徐志伟著,陆鑫达等译. 可扩展并行计算技术,结构与编程. 北京:机械工业出版社, P.33~56,P.227~237, 2000. 陈国良著.并行计算—结构、算法、编程. 北京:高等教育出版社,1999. Barry Wilkinson and Michael Allen. Parallel Programming(Techniques and Applications using Networked Workstations and Parallel Computers). Prentice Hall, 1999. 李晓梅,莫则尧等著. 可扩展并行算法的设计与分析. 北京:国防工业出版社,2000. 张宝琳,谷同祥等著. 数值并行计算原理与方法. 北京:国防工业出版社,1999. 都志辉著. 高性能计算并行编程技术—MPI并行程序设计. 北京:清华大学出版社, 2001. 2018年11月

相关网址 MPI: http://ww.mpi-forum.org, http://www.mcs.anl.gov/mpi Pthreads: http://www.oreilly.com PVM: http://www.epm.ornl.gov/pvm/ OpenMP: http://www.openmp.org 网上搜索:www.google.com 2018年11月

Parallel Programming with the Massage Passing Interface (MPI) 2018年11月

参考文献 MPI--the complete reference. Marc Snir, MIT Press, 1998. ISBN 0262692155, 0262692163. Using MPI : portable parallel programming with the message-passing interface. William Gropp, MIT Press, 1999. 2nd edition. ISBN 0262571323. Using MPI-2 : advanced features of the message-passing interface. William Gropp, MIT Press, 1999. ISBN 0262571331. 高性能计算并行编程技术-MPI并行程序设计. 都志辉,清华大学出版社, 2001年8月。 并行计算导论. 张林波, 迟学斌等, 清华大学出版社, 2006. 2018年11月

并行编程标准 多线程库标准 编译制导标准 消息传递库标准 – Win32 API. – POSIX threads. – OpenMP – 可移植共享存储并行编程标准. 消息传递库标准 – MPI – PVM 2018年11月

消息传递并行程序设计 消息传递并行程序设计 并行计算粒度大,特别适合于大规模可扩展并行算法 指用户必须通过显式地发送和接收消息来实现处理机间的数据交换。 在这种并行编程中,每个并行进程均有自己独立的地址空间,相互之间访问不能直接进行,必须通过显式的消息传递来实现。 这种编程方式是大规模并行处理机(MPP)和机群(Cluster)采用的主要编程方式。 并行计算粒度大,特别适合于大规模可扩展并行算法 由于消息传递程序设计要求用户很好地分解问题,组织不同进程间的数据交换,并行计算粒度大,特别适合于大规模可扩展并行算法. 消息传递是当前并行计算领域的一个非常重要的并行程序设计方式 2018年11月

什么是MPI? Massage Passing Interface:是消息传递函数库的标准规范,由MPI论坛开发,支持Fortran和C 2018年11月

MPI的发展过程 发展的两个阶段 MPI 1.1: 1995 MPICH:是MPI最流行的非专利实现,由Argonne国家实验室和密西西比州立大学联合开发,具有更好的可移植性. MPI 1.2~2.0:动态进程, 并行 I/O, 远程存储访问、支持F90和C++(1997). 2018年11月

OpenMPI development Team MPICH2 MVAPICH2 OpenMPI Intel MPI 开发者 Argonne National Lab Ohio State University  OpenMPI development Team Intel 是否开源 是 否 支持的网络 以太网 InfiniBand/以太网 MPI标准 2.0 前身 MPICH MVAPICH LAM-MPI / 2018年11月

内容提示 基本的MPI 深入MPI 基本概念 集合(Collective)通信与同步基础 实例:Hello World 集合(Collective)通信与同步基础 数据广播、收集、散发、转置、归约;进程同步 实例:求π 用户自定义(/派生)数据类型(User-defined(Derived) data type) 事实上MPI的所有数据类型均为MPI自定义类型 支持异构系统 允许消息来自不连续的或类型不一致的存储区(结构,数组散元) MPI程序的编译和运行 深入MPI 深入点对点通信 MPI中API的主要内容 深入聚合通信 实例:矩阵向量相乘 MPI环境管理 组,上下文和通信空间/通信域的管理 2018年11月

C和Fortran中MPI函数约定 C Fortran MPI函数的参数被标志为以下三种类型: 必须包含mpi.h. MPI 函数返回出错代码或 MPI_SUCCESS成功标志. MPI-前缀,且只有MPI以及MPI_标志后的第一个字母大写,其余小写. Fortran 必须包含mpif.h. 通过子函数形式调用MPI,函数最后一个参数为返回值. MPI-前缀,且函数名全部为大写. MPI函数的参数被标志为以下三种类型: IN:参数在例程的调用中不会被修正. OUT:参数在例程的调用中可能会被修正. INOUT:参数有初始值,且在例程的调用中可能会被修正 2018年11月

最基本的MPI函数 MPI调用接口的总数虽然庞大,但根据实际编写MPI的经验,使用其中的6个最基本的函数就能编写一个完整的MPI程序去求解很多问题。这6个基本函数,包括启动和结束MPI环境,识别进程以及发送和接收消息。 MPI_Init(…); MPI_Comm_size(…); MPI_Comm_rank(…); MPI_Send(…); MPI_Recv(…); MPI_Finalize(); MPI_Init(…); … 并行代码; MPI_Fainalize(); 只能有串行代码; 2018年11月

MPI并行环境管理函数 MPI_INIT是MPI程序的第一个调用,它完成MPI程序的所有初始化工作。所有的MPI程序的第一条可执行语句都是这条语句。 启动MPI环境,标志并行代码的开始. 要求main必须带参数运行,否则出错. MPI_FINALIZE是MPI程序的最后一个调用,它结束MPI程序的运行,它是MPI程序的最后一条可执行语句,否则程序的运行结果是不可预知的。 标志并行代码的结束,结束除主进程外其它进程. 之后串行代码仍可在主进程(rank = 0)上运行(如果必须). 2018年11月

基本的MPI通信域函数 获得通信域comm/COMM内的进程数目,保存于size/SIZE中 获得该进程在通信域comm/COMM内的进程序号,保存于rank/RANK中 2018年11月

基本的MPI点对点通信函数 将发送缓冲区中的count个datatype数据类型的数据发送到目的进程 从指定的进程source接收消息,并且该消息的数据类型和消息标识和本接收进程指定的数据类型和消息标识相一致,收到的消息所包含的数据元素的个数最多不能超过count. 2018年11月

实例: Hello World 2018年11月

Hello World (c) #include <stdio.h> #include “mpi.h” int main(int argc, char* argv[]) { int numprocs; /*进程数,该变量为各处理器中的同名变量, 存储是分布的 */ int myid; /*我的进程ID,存储也是分布的 */ MPI_Status status; /*消息接收状态变量,存储也是分布的 */ char message[100]; /*消息buffer,存储也是分布的 */ /*初始化MPI*/ MPI_Init(&argc, &argv); /*该函数被各进程各调用一次,得到自己的进程rank值*/ MPI_Comm_rank(MPI_COMM_WORLD, &myid); /*该函数被各进程各调用一次,得到进程数*/ MPI_Comm_size(MPI_COMM_WORLD, &numprocs); 2018年11月

Hello World (c) if (myid != 0) { /*建立消息*/ sprintf(message, “\”Hello World\” from process %d\/%d!",myid, numprocs); /* 发送长度取strlen(message)+1,使\0也一同发送出去*/ 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,标志并行代码段的结束*/ MPI_Finalize(); return 0; } /* End main */ 2018年11月

 运行结果 [dair@node01 ~]$ mpicc -o hello hello.c [dair@node01 ~]$ ./hello () [0] Aborting program ! Could not create p4 procgroup. Possible missing fileor program started without mpirun. [dair@node01 ~]$ mpirun -np 4 hello () “Hello World” from process 1/4! “Hello World” from process 2/4! “Hello World” from process 3/4! [dair@node01 ~]$ 计算机打印字符 我们输入的命令 2018年11月

:Hello是如何被执行的? SPMD: Single Program Multiple Data(SIMD) :::: rsh\ssh #include "mpi.h" #include <stdio.h> main( int argc, char *argv[] ) { MPI_Init( &argc, &argv ); … MPI_Finalize(); } #include "mpi.h" #include <stdio.h> main( int argc, char *argv[] ) { MPI_Init( &argc, &argv ); printf( "Hello, world!\n" ); MPI_Finalize(); } #include "mpi.h" #include <stdio.h> main( int argc, char *argv[] ) { MPI_Init( &argc, &argv ); printf( "Hello, world!\n" ); MPI_Finalize(); } #include "mpi.h" #include <stdio.h> main( int argc, char *argv[] ) { MPI_Init( &argc, &argv ); printf( "Hello, world!\n" ); MPI_Finalize(); } #include "mpi.h" #include <stdio.h> main( int argc, char *argv[] ) { MPI_Init( &argc, &argv ); … MPI_Finalize(); } hello 2018年11月

MPI基本数据类型 2018年11月

内容提示 基本的MPI 深入MPI 基本概念 集合(Collective)通信与同步基础 实例:Hello World 集合(Collective)通信与同步基础 数据广播、收集、散发、转置、归约;进程同步 实例:求π 用户自定义(/派生)数据类型(User-defined(Derived) data type) 事实上MPI的所有数据类型均为MPI自定义类型 支持异构系统 允许消息来自不连续的或类型不一致的存储区(结构,数组散元) MPI程序的编译和运行 深入MPI 深入点对点通信 MPI中API的主要内容 深入聚合通信 实例:矩阵向量相乘 MPI环境管理 组,上下文和通信空间/通信域的管理 2018年11月

集合通信与同步 同步 数据广播 数据收集 数据散发 数据转置 归约 MPI_Barrier MPI_Bcast MPI_Gather MPI_Scatter 数据转置 MPI_Alltoall 归约 MPI_Reduce 2018年11月

Barrier – 障碍同步 int p, myrank; Process 0 float buf; MPI_Comm comm; MPI_Init(&argc, &argv); /*得进程编号*/ MPI_Comm_rank(comm, &my_rank); /* 得进程总数 */ MPI_Comm_size(comm, &p); …… MPI_Barrier( comm ); Process 0 Process 1 Process 2 Process 3 int MPI_Barrier( MPI_Comm comm ) 根进程既是发送缓冲区也是接收缓冲区 2018年11月

Broadcast -- 数据广播 int p, myrank; float buf; MPI_Comm comm; MPI_Init(&argc, &argv); /*得进程编号*/ MPI_Comm_rank(comm, &my_rank); /* 得进程总数 */ MPI_Comm_size(comm, &p); if(myrank==0) buf = 1.0; MPI_Bcast(&buf,1,MPI_FLOAT,0, comm); data buf . MPI_Bcast(); Process 0 myrank = 0 Process 1 myrank = 1 Process p-1 myrank = p-1 int MPI_Bcast ( void *buffer,/*发送/接收buf*/ int count, /*元素个数*/ MPI_Datatype datatype, int root, /*指定根进程*/ MPI_Comm comm) 根进程既是发送缓冲区也是接收缓冲区 2018年11月

Gather -- 数据收集 int p, myrank; float data[10];/*分布变量*/ float* buf; MPI_Comm comm; MPI_Init(&argc, &argv); /*得进程编号*/ MPI_Comm_rank(comm,&my_rank); /* 得进程总数 */ MPI_Comm_size(comm, &p); if(myrank==0) buf=(float*)malloc(p*10*sizeof(float);/*开辟接收缓冲区*/ MPI_Gather(data,10,MPI_FLOAT, buf,10,MPI_FlOAT,0,comm); Process 0 myrank = 0 Process 1 myrank = 1 Process p-1 myrank = p-1 data . MPI_Gather(); data . MPI_Gather(); data . MPI_Gather(); buf 根进程接收其他进程来的消息(包括根进程),按每在进程在通信组中的编号依次联接在一下,存放在要进程的接收缓冲区中. int MPI_Gather ( void *sendbuf, int sendcnt, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm ) 2018年11月

Scatter -- 数据散发 int p, myrank; float data[10]; float* buf; MPI_Comm comm; MPI_Init(&argc, &argv); /*得进程编号*/ MPI_Comm_rank(comm,&my_rank); /* 得进程总数 */ MPI_Comm_size(comm, &p); if(myrank==0) buf = (float*)malloc(p*10*sizeof(float);/*开辟接收缓冲区*/ MPI_Scatter(buf,10,MPI_FLOAT, data,10,MPI_FlOAT,0,comm); Process 0 myrank = 0 Process 1 myrank = 1 Process p-1 myrank = p-1 data . MPI_Scatter(); data . MPI_ Scatter(); data . MPI_ Scatter(); buf 根进程中存储了p个消息,第i个消息将传给第i个进程. int MPI_Scatter ( void *sendbuf, int sendcnt, MPI_Datatype sendtype, void *recvbuf, int recvcnt, MPI_Datatype recvtype, int root, MPI_Comm comm ) 2018年11月

Alltoall -- 数据转置 int i, p, myrank; float data[10]; float sendbuf[3], recvbuf[3]; MPI_Comm comm; MPI_Init(&argc, &argv); /*得进程编号*/ MPI_Comm_rank(comm,&my_rank); /* 得进程总数 */ MPI_Comm_size(comm, &p); for ( i=0; i<3; i++ ) sendbuf[i] = 3*my_rank + i; MPI_Alltoall(sendbuf,3,MPI_FLOAT, recvbuf,3,MPI_FlOAT, comm); 通信域中所有进程从其他进程收集数据,同时将自己的数据散发给其他进程. int MPI_Alltoall( void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, int recvcnt, MPI_Datatype recvtype, MPI_Comm comm ) 2018年11月

Reduce -- 全局归约 + int p, myrank; float data = 0.0; float buf; MPI_Comm comm; MPI_Init(&argc, &argv); /*得进程编号*/ MPI_Comm_rank(comm,&my_rank); /*各进程对data进行不同的操作*/ data = data + myrank * 10; /*将各进程中的data数相加并存入根进程的buf中 */ MPI_Reduce(&data,&buf,1,MPI_FLOAT,MPI_SUM,0,comm); Process 0 myrank = 0 Process 1 myrank = 1 Process p-1 myrank = p-1 data . MPI_Scatter(); data . MPI_ Scatter(); data . MPI_ Scatter(); buf + 对组中所有进程的发送缓冲区中的数据用OP参数指定的操作进行运算,并将结果送回到根进程的接收缓冲区中. int MPI_Reduce ( void *sendbuf, void *recvbuf, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm ) 2018年11月

实例: 求π 2018年11月

串行代码 #include <stdio.h> double f(double a) { return (4.0/(1.0+a*a)); } int main( int argc, char *argv[] ) int i, n = 0; double h, sum, x, pi, sum=0.0; h=1.0/(double)n; /* printf( "Enter the number of intervals: (0 quits) " ); scanf( "%d",&n ); */ if (n==0) n=100; else n=0; for (i=1; i<=n; i++) { x=h*((double)i – 0.5); sum += f(x); pi=h*sum; printf( ) return 0; 2018年11月

并行代码 #include "mpi.h" #include <stdio.h> #include <math.h> double f( double ); double f( double a) { return (4.0 / (1.0 + a*a)); } int main( int argc, char *argv[] ) int done = 0, n, myid, numprocs, i; double PI25DT = 3.141592653589793238462643; double mypi, pi, h, sum, x; double startwtime=0.0, endwtime; int namelen; char processor_name[MPI_MAX_PROCESSOR_NAME]; 2018年11月

MPI_Init( &argc,&argv ); MPI_Comm_size( MPI_COMM_WORLD,&numprocs ); MPI_Comm_rank( MPI_COMM_WORLD, &myid ); MPI_Get_processor_name( processor_name, &namelen ); fprintf( stderr,"Process %d on %s\n", myid, processor_name ); n = 0; while (!done) { if ( myid == 0 ) /* printf( "Enter the number of intervals: (0 quits) " ); scanf( "%d",&n ); */ if (n==0) n=100; else n=0; startwtime = MPI_Wtime(); } MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD); if (n == 0) done = 1; 2018年11月

else { h = 1.0 / (double) n; sum = 0.0; for (i = myid + 1; i <= n; i += numprocs) x = h * ((double)i - 0.5); sum += f(x); } mypi = h * sum; MPI_Reduce(&mypi, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); if (myid == 0) printf("pi is approximately %.16f, Error is %.16f\n", pi, fabs(pi - PI25DT)); endwtime = MPI_Wtime(); printf("wall clock time = %f\n", endwtime-startwtime); MPI_Finalize(); return 0; 2018年11月

内容提示 基本的MPI 深入MPI 基本概念 集合(Collective)通信与同步基础 实例:Hello World 集合(Collective)通信与同步基础 数据广播、收集、散发、转置、归约;进程同步 实例:求π 用户自定义(/派生)数据类型(User-defined(Derived) data type) 事实上MPI的所有数据类型均为MPI自定义类型 支持异构系统 允许消息来自不连续的或类型不一致的存储区(结构,数组散元) MPI程序的编译和运行 深入MPI 深入点对点通信 MPI中API的主要内容 深入聚合通信 实例:矩阵向量相乘 MPI环境管理 组,上下文和通信空间/通信域的管理 2018年11月

MPI中所有数据类型均为MPI自定义类型 用户自定义数据类型/派生数据类型 目的 异构计算: 不同系统有不同的数据表示格式。MPI预 先定义一些基本数据类型,在实现过程中在这些基本 数据类型为桥梁进行转换。 派生数据类型:允许消息来自不连续的和类型不一致的存储区域,如数组散元与结构类型等的传送。 MPI中所有数据类型均为MPI自定义类型 基本数据类型,如MPI_INT,MPI_DOUBLE… 用户定义数据类型或派生数据类型. 2018年11月

数据类型图 2018年11月

Derived Datatype(派生) 常用 MPI_Type_contiguous MPI_Type_vector MPI_Type_indexed MPI_Type_struct 2018年11月

MPI_Type_contiguous 将原数据类型,按顺序进行多次复制 2018年11月

在FORTRAN中定义矩阵的一列 在C中定义矩阵的一行 REAL A(1000,1000) float a[1000][1000]; INTEGER F_COL MPI_TYPE_CONTIGUOUS(1000, MPI_REAL, F_COL) MPI_SEND(A,1,F_COL,right,tag,MPI_COMM_WORLD) 在C中定义矩阵的一行 float a[1000][1000]; MPI_Datatype C_R; MPI_Type_contiguous(1000,MPI_FLOAT,&C_R); MPI_SEND( &(a[0][0]), 1, C_R, right, tag , MPI_COMM_WORLD ) 2018年11月

MPI_Type_vector MPI_Type_vector()函数首先通过连续复制若干个旧数据类型形成一个“块”,然后通过等间隔地复制该块儿形成新的数据类型。块与块之间的空间是旧数据类型的倍数。 #include "mpi.h" int MPI_Type_vector ( int count, /*数据块个数 (非负整数)*/ int blocklen, /*块中元素个数 (非负整数)*/ int stride, /*块间起始地址间隔 (非负整数)*/ MPI_Datatype old_type, /*原始数据类型(句柄)*/ MPI_Datatype *newtype /*派生数据类型指针*/ ) 2018年11月

用MPI_Vector进行矩阵的行列置换 2018年11月

MPI_Type_vector应用示意 /*用MPI_Vector进行矩阵的行列置换*/ … float A[10][10]; MPI_Datatype column_mpi_t;   MPI_Type_vector(10, 1, 10, MPI_FLOAT, &column_mpi_t); MPI_Type_commit(&column_mpi_t); if (my_rank == 0) { MPI_Send(&(A[0][0]), 1, column_mpi_t, 1, 0, MPI_COMM_WORLD); } else { /* my_rank = 1 */ MPI_Recv(&(A[0][0]), 10, MPI_FLOAT, 0, 0, MPI_COMM_WORLD, &status); 2018年11月

MPI_Type_indexed #include "mpi.h" int MPI_Type_indexed ( int count, /*数据块的个数,数据块间不连续*/ int blocklens[], /*每一数据块中元素的个数,为一个非负整型数组*/ int indices[], /*每一块数据在原始数据类型中的起始位置,整型数组*/ MPI_Datatype old_type, /*原始数据类型(名柄)*/ MPI_Datatype* newtype /*派生数据类型指针*/ ) 2018年11月

用MPI_Type_indexed发送矩阵的 上三角部分 以C语言表示的数组为例,数组按行连续存储 2018年11月

MPI_Type_indexed应用示意 /*将A矩阵的上三角部分送到另一个处理器中的T矩阵的对应位置*/ float A[n][n]; /* Complete Matrix */ float T[n][n]; /* Upper Triangle */ int displacements[n]; int block_lengths[n]; MPI_Datatype index_mpi_t;   for (i = 0; i < n; i++) { block_lengths[i] = n-i; displacements[i] = (n+1)*i; } MPI_Type_indexed(n, block_lengths, displacements,MPI_FLOAT, &index_mpi_t); MPI_Type_commit(&index_mpi_t); if (my_rank == 0) MPI_Send(A, 1, index_mpi_t, 1, 0, MPI_COMM_WORLD); else /* my_rank == 1 */ MPI_Recv(T, 1, index_mpi_t, 0, 0, MPI_COMM_WORLD, &status); 2018年11月

MPI_Type_struct 允许每个块包含不同数据类型的拷贝 2018年11月

MPI_Type_struct应用示意 /*MPI_Type_struct的例子*/ struct partstruct {char class; double d[6]; char b[7]}; struct partstruct particle[1000]; int i,dest,rank; MPI_Datatype particletype,type[3]={MPI_CHAR, MPI_DOUBLE,MPI_CHAR} int blocklen[3]={1,6,7}; MPI_Aint disp[3]={0,sizeof(double),7*sizeof(double)}; MPI_Type_struct(3,blocklen,disp,type,&particletype); 2018年11月

其它派生类型 MPI_Hvector MPI_Hindexed MPI_Pack/MPI_Unpack:数据打包/解包 为了与早期其它并行库兼容 2018年11月

MPI_Pack () int MPI_Pack ( void *inbuf, /* 输入缓冲区起始地址*/ int incount, /* 输入数据项个数 */ MPI_Datatype datatype, /* 输入数据项的数据类型 */ void *outbuf, /* 输出缓冲区起始地址 */ int outcount, /* 输出缓冲区大小 */ int *position, /* 输出缓冲区当前位置 */ MPI_Comm comm /* 通信域 */ ) 示例: packsize=0; MPI_Pack(&a,1,MPI_INT,packbuf,100,&packsize,MPI_COMM_WORLD); MPI_Pack(&b,1,MPI_DOUBLE, packbuf,100,&packsize,MPI_COMM_WORLD); 2018年11月

MPI_Unpack() 示例: int MPI_Unpack ( void *inbuf, /* 输入缓冲区起始地址*/ int incount, /* 输入数据项大小*/ int *position, /* 缓冲区当前位置 */ void *outbuf, /* 输出缓冲区起始地址 */ int outcount, /* 输出缓冲区大小 */ MPI_Datatype datatype, /* 输出数据项的数据类型 */ MPI_Comm comm /* 通信域 */ ) 示例: pos=0; MPI_Unpack(packbuf,packsize,&pos,&a,1,MPI_INT,MPI_COMM_WROLD); MPI_Unpack(packbuf,packsize,&pos,&b,1,MPI_FLOAT,MPI_COMM_WROLD); 2018年11月

派生数据类型的应用 提交: 释放: int MPI_Type_commit(MPI Datatype *datatype) 将数据类型映射进行转换或“编译” 一种数据类型变量可反复定义,连续提交 释放: int MPI_Type free(MPI_Datatype *datatype) 将数据类型设为MPI_DATATYPE_NULL 2018年11月

内容提示 基本的MPI 深入MPI 基本概念 集合(Collective)通信与同步基础 实例:Hello World 集合(Collective)通信与同步基础 数据广播、收集、散发、转置、归约;进程同步 实例:求π 用户自定义(/派生)数据类型(User-defined(Derived) data type) 事实上MPI的所有数据类型均为MPI自定义类型 支持异构系统 允许消息来自不连续的或类型不一致的存储区(结构,数组散元) MPI程序的编译和运行 深入MPI 深入点对点通信 MPI中API的主要内容 深入聚合通信 实例:矩阵向量相乘 MPI环境管理 组,上下文和通信空间/通信域的管理 2018年11月

MPI程序的编译 mpicc编译并连接用C语言编写的MPI程序 mpiCC编译并连接用C++编写的MPI程序 mpif77编译并连接用FORTRAN 77编写的MPI程序 mpif90编译并连接用Fortran 90编写的MPI程序 这些命令可以自动提供MPI需要的库,并提供特定的开关选项(用-help查看)。 2018年11月

MPI程序的编译 %小写o 用mpicc编译时,就像用一般的C编译器一样。还可以使用一般的C的编译选项,含义和原来的编译器相同 mpif77 hello.f mpicc hello.c 生成名为“a.out”的可执行文件. mpif77 –o hello hello.f mpicc –o hello hello.c 生成名为“hello”的可执行文件. %小写o 2018年11月

MPI程序的运行 MPI程序的执行步骤一般为: 将可执行程序拷贝到各个节点机上 通过mpirun命令并行执行MPI程序 2018年11月

最简单的MPI运行命令 mpirun –np N <program> 其中: 例如: N: 同时运行的进程数 <program>: 可执行MPI程序名 例如: mpirun –np 6 cpi mpirun –np 4 hello np: The number of process. 2018年11月

完整的MPI运行方式 MPI程序的一般启动方式: 完整的MPI运行方式: 详细参数信息执行mpirun -help mpirun –np <number of processor> <program name and argument> 完整的MPI运行方式: mpirun [mpirun_options] <program> [options…] 详细参数信息执行mpirun -help 2018年11月

OpenMPI运行方式 mpirun -np 16 -machinefile ma --mca pls_rsh_agent rsh --mca btl self,tcp yourprogram -np 16 起16个进程 -machinefile ma 文件写出在哪些节点上执行该命令,格式如下 node1 slots=8 node2 slots=8 slots=8代表每个节点运行8个进程(也可将node1和node2分别写8行) slots=8 --mca pls_rsh_agent rsh 告诉节点间通讯用rsh --mca btl self,tcp 使用以太网tcp/IP通讯 self,sm 当单节点运行的时候,使用内存通讯,效率高 self,openib 有infiniband设备时,使用IB通讯 --mac btl_tcp_if_include eth0 以太网通讯时用eth0通讯 2018年11月

MPICH2运行方式 mpiexe.hydra -f hosts -n 4 yourprogram node1 node2 node3 2018年11月

MVAPICH2运行方式 mpirun_rsh -rsh -np 4 -hostfile hosts yourprogram node0 -rsh 或者 -ssh表示使用rsh或者ssh通讯 2018年11月

Intel MPI运行方式 mpirun -r ssh -f mpd.hosts -machinefile mpd.hosts -np 16 -env I_MPI_DEVICE sock mpiprogram (1) [mpdboot options -r ssh -f mpd.hosts (-n 在mpirun中不能用)] (2) [mpiexec global options -machinefile mpd.hosts] (3) [mpiexec local options -np 16 -env ...] -env 指的是环境变量,通过I_MPI_DEVICE 设定使用的通信设备 sock TCP/Ethernet*/sockets shm Shared memory only (no sockets) ssm TCP + shared memory (for SMP clusters connected via Ethernet*) rdma[:<provider>] InfiniBand*, Myrinet* (via specified DAPL* provider) rdssm[:<provider>] TCP + shared memory + DAPL* (for SMP clusters connected via RDMA-capable fabrics) 选项顺序 2018年11月

内容提示 基本的MPI 深入MPI 基本概念 集合(Collective)通信与同步基础 实例:Hello World 集合(Collective)通信与同步基础 数据广播、收集、散发、转置、归约;进程同步 实例:求π 用户自定义(/派生)数据类型(User-defined(Derived) data type) 事实上MPI的所有数据类型均为MPI自定义类型 支持异构系统 允许消息来自不连续的或类型不一致的存储区(结构,数组散元) MPI程序的编译和运行 深入MPI 深入点对点通信 MPI中API的主要内容 深入聚合通信 实例:矩阵向量相乘 MPI环境管理 组,上下文和通信空间/通信域的管理 2018年11月

MPI消息 MPI消息包括信封和数据两个部分,信封指出了发送或接收消息的对象及相关信息,而数据是本消息将要传递的内容 数据:<起始地址、数据个数、数据类型> 信封:<源/目的、标识、通信域>

消息匹配 接收buffer必须至少可以容纳count个由datatype参数指明类型的数据. 如果接收buf太小, 将导致溢出、出错. 参数匹配 dest,tag,comm/ source,tag,comm Source == MPI_ANY_SOURCE:接收任意处理器来的数据(任意消息来源). Tag == MPI_ANY_TAG:匹配任意tag值的消息(任意tag消息). Source = destination 是允许的, 但是不安全的, 可能导致死锁。 消息传送被限制在同一个communicator. 在send函数中必须指定唯一的接收者(Push/pull通讯机制). 2018年11月

Point to Point通信 单个进程对单个进程的通信,重要且复杂 术语 Blocking(阻塞) :一个例程须等待操作完成才返回,返回后用户可以重新使用调用中所占用的资源. Non-blocking(非阻塞):一个例程不必等待操作完成便可返回,但这并不意味着所占用的资源可被重用. Local(本地):过程的完成仅依赖于本地正在执行的进程。 Non-local(非本地):如果过程的完成要求其他进程的 MPI 过程完成。 2018年11月

什么是缓冲区? 应用程序中说明的变量,在消息传递语句中又用作缓冲区的起始位置. 由系统(不同用户)创建和管理的某一存储区域,在消息传递过程中用于暂存放消息.也被称为系统缓冲区. 用户可设置一定大小的存储区域,用作中间缓冲区以保留可能出现在其应用程序中的任意消息. 进程P A M 进程Q B 进程P A M T 进程Q B 用户缓冲区 进程P A M S 进程Q B 用户指定缓冲区 系统缓冲区 2018年11月

MPI阻塞通信流程 2018年11月

4种阻塞通信模式 由发送方体现(send语句). 阻塞通信中接收语句相同,MPI_Recv 按发送方式的不同,消息或直接被copy到接收者的buffer中或被拷贝到系统buffer中。 标准模式Standard 最常用的发送方式 MPI_Send(…) B:缓冲模式Buffer 发到系统缓冲区 MPI_Bsend(…) S:同步模式Synchronous 任意发出,不需系统缓冲区 MPI_Ssend(…) R:就绪模式Ready 就绪发出,不需系统缓冲区 MPI_Rsend(…) 2018年11月

标准模式Standard --直接送信或通过邮局送信 由MPI决定是否缓冲消息 没有足够的系统缓冲区时或出于性能的考虑,MPI可能进行直接拷贝:仅当相应的接收完成后,发送语句才能返回 MPI缓冲消息:发送语句的相应的接收语句完成前返回 发送的结束 == 消息已从发送方发出,而不是滞留在发送方的系统缓冲区中 非本地的:发送操作的成功与否依赖于接收操作 最常用的发送方式 Process 0 (Rank = 0) 时间(执行顺序) Process 1 (Rank = 1) x; . MPI_Send(&x,1); y; . MPI_Recv(&y,1); 数据传送 数据在发送方buffer与接收方buffer间 直接拷贝 2018年11月

缓冲模式Buffer --通过邮局送信(应用系统缓冲区) 前提: 用户显式地分配用于缓冲消息的系统缓冲区 MPI_Buffer_attach(*buffer, *size) 。 发送是本地的: 完成不依赖于与其匹配的接收操作。发送的结束仅表明消息进入系统的缓冲区中,发送方缓冲区可以重用,而对接收方的情况并不知道。 缓冲模式在相匹配的接收未开始的情况下,总是将送出的消息放在缓冲区内,这样发送者可以很快地继续计算,然后由系统处理放在缓冲区中的消息。 占用内存,一次内存拷贝。 其函数调用形式为:MPI_BSEND(…)。B代表缓冲. Process 0 (Rank = 0) 时间(执行顺序) Process 1 (Rank = 1) x; . MPI_Bsend(&x,1); y; . MPI_Recv(&y,1); 系统缓冲区 通过系统缓冲区传送消息 2018年11月

同步模式Synchronous --握手后才送出名片(遵从three-way协议) 时间(执行顺序) 本质特征:收方接收该消息的缓冲区已准备好,不需要附加的系统缓冲区 任意发出:发送请求可以不依赖于收方的匹配的接收请求而任意发出 成功结束:仅当收方已发出接收该消息的请求后才成功返回,否则将阻塞。意味着: 发送方缓冲区可以重用 收方已发出接收请求 是非本地的 其函数调用形式为:MPI_SSEND(…)。S代表同步 Process 0 (Rank = 0) Process 1 (Rank = 1) x; . MPI_Ssend(&x,1); y; . MPI_Recv(&y,1); 请求发送 确认 消息 x; . MPI_Ssend(&x,1); y; MPI_Recv(&y,1); . 请求发送 确认 消息 同步发送与接收 上图:发送超前于接收 下图:发送滞后于接收 2018年11月

就绪模式Ready --有客户请求,才提供服务 发送请求仅当有匹配的接收后才能发出,否则出错。在就绪模式下,系统默认与其相匹配的接收已经调用。接收必须先于发送。 它不可以不依赖于接收方的匹配的接收请求而任意发出 其函数调用形式为:MPI_RSEND(…)。R代表就绪 Process 0 (Rank = 0) 时间(执行顺序) Process 1 (Rank = 1) x; . .. MPI_Rsend(&x,1); y; . MPI_Recv(&y,1); 请求发送 确认 消息 接收必须先于发送 (只有客户发出服务请求,才提供服务) 2018年11月

标准通信模式 缓存通信模式 同步通信模式 就绪通信模式 2018年11月

非阻塞通信 阻塞通信将发生阻塞,直到通讯完成. 非阻塞可将通讯交由后台处理,通信与计算可重叠. 用户发送缓冲区的重用: 非阻塞的发送:仅当调用了有关结束该发送的语句后才能重用发送缓冲区,否则将导致错误;对于接收方,与此相同,仅当确认该接收请求已完成后才能使用。所以对于非阻塞操作,要先调用等待MPI_Wait()或测试MPI_Test()函数来结束或判断该请求,然后再向缓冲区中写入新内容或读取新内容。 发送语句的前缀由MPI_改为MPI_I, I:immediate: 标准模式:MPI_Send(…)->MPI_Isend(…) Buffer模式:MPI_Bsend(…)->MPI_Ibsend(…) … 2018年11月

非阻塞发送与接收 int MPI_Isend(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request) IN buf 发送缓冲区的起始地址 IN count 发送缓冲区的大小(发送元素个数) IN datatype 发送缓冲区数据的数据类型 IN dest 目的进程的rank值 IN tag 消息标签 IN comm 通信空间/通信域 OUT request 非阻塞通信完成对象(句柄) MPI_Ibsend/MPI_Issend/MPI_Irsend:非阻塞缓冲模式/非阻塞同步模式/非阻塞就绪模式 int MPI_Irecv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request* request) 2018年11月

非阻塞通信对象 所有的非阻塞发送或接收通信都会返回一个“非阻塞通信对象” ,程序员可以通过对这一对象的查询,可以识别各种通信操作,并判断相应的非阻塞操作是否完成。 使用MPI_Cancel()可以取消已调用的非阻塞操作,但仍必须调用MPI_Wait()或MPI_Test()操作来释放非阻塞通信对象。 2018年11月

非阻塞标准发送和接收 2018年11月

通信的完成 发送的完成: 代表发送缓冲区中的数据已送出,发送缓冲区可以重用。它并不代表数据已被接收方接收。数据有可能被缓冲; 同步模式:发送完成==接收方已初始化接收,数据将被接收方接收; 接收的完成:代表数据已经写入接收缓冲区。接收者可访问接收缓冲区,status对象已被释放。它并不代表相应的发送操作已结束。 通过MPI_Wait()和MPI_Test()来判断通信是否已经完成; 2018年11月

MPI_Wait()及应用示例 MPI_WAIT(request, status) INOUT request 非阻塞通信对象 (句柄) OUT status 返回的状态 (状态类型) 当request标识的通信结束后,MPI_Wait()才返回。如果通信是非阻塞 的,返回时request被置成MPI_REQUEST_NULL;函数调用是非本地的; MPI_Request request; MPI_Status status; int x,y; if(rank == 0){ MPI_Isend(&x,1,MPI_INT,1,99,comm,&request) … MPI_Wait(&request,&status); } else { MPI_Irecv(&y,1,MPI_INT,0,99,comm,&request) } 2018年11月

MPI_Test()及应用示例 MPI_TEST(request, flag, status) INOUT request 非阻塞通信对象(句柄) OUT flag 操作是否完成标志(逻辑型) OUT status 返回的状态 (状态类型) 不论通信是否完成都立即返回。如果通信已经完成则在flag 中返回非0,相应 的通信请求被释放,request被置成MPI_REQUEST_NULL。如果通信尚未完成 则在flag 中返回0。 MPI_Request request; MPI_Status status; int x,y,flag; if(rank == 0) { MPI_Isend(&x,1,MPI_INT,1,99,comm,&request) while(!flag) MPI_Test(&request,&flag,&status); } else { MPI_Irecv(&y,1,MPI_INT,0,99,comm,&request) } 2018年11月

消息探测 -- MPI_PROBE / MPI_IPROBE 2018年11月

MPI_Probe应用示例 int x; float y; MPI_Comm_rank(comm, &rank); if(rank ==0) { /*0->2发送一int型数*/ MPI_Send(100,1,MPI_INT,2,99,comm); } else if(rank == 1) { /*1->2发送一float型数*/ MPI_Send(100.0,1,MPI_FLOAT,2,99,comm); else { /* 根进程接收 */ for(int i=0;i<2;i++) { MPI_Probe(MPI_ANY_SOURCE,0,comm,&status);/*Blocking*/ if (status.MPI_SOURCE == 0) MPI_Recv(&x,1,MPI_INT,0,99,&status); else if(status.MPI_SOURCE == 1) MPI_Recv(&y,1,MPI_FLOAT,0,99,&status); 2018年11月

重复非阻塞通信 如果一个通信会被重复执行,比如一个循环内的通信调用,每次都对所需的通信对象进行初始化和释放的效率是很低的。 重复非阻塞通信可在通信参数和MPI内部对象之间建立固定的联系,降低不必要的开销。 步骤 1.通信的初始化,比如MPI_SEND_INIT 2.启动通信,MPI_START 3.完成通信,MPI_WAIT 4.释放非阻塞通信对象,MPI_REQUEST_FREE 2018年11月

MPI_SEND_INIT / MPI_RECV_INIT 2018年11月

MPI_START 重复非阻塞通信在创建后处于非活动状态,要使用MPI_START()或MPI_STARTALL()将它激活 2018年11月

避免死锁deadlock 发送和接收是成对出现的,忽略这个原则 很可能会产生死锁 不安全 安全 总会死锁的通信调用次序 避免死锁的通信调用次序 安全 2018年11月

MPI_Sendrecv MPI_SENDRECV(sendbuf, sendcount, sendtype, dest, sendtag, recvbuf, recvcount, recvtype, source, recvtag, comm, status) INOUT request 非阻塞通信对象 (句柄) IN sendbuf 发送缓冲区起始地址(可选数据类型) IN sendcount 发送数据的个数(整型) IN sendtype 发送数据的数据类型(句柄) IN dest 目标进程标识(整型) IN sendtag 发送消息标识(整型) OUT recvbuf 接收缓冲区初始地址(可选数据类型) IN recvcount 最大接收数据个数(整型) IN recvtype 接收数据的数据类型(句柄) IN source 源进程标识(整型) IN recvtag 接收消息标识(整型) IN comm 通信域(句柄) OUT status 返回的状态(status) 捆绑发送和接收:在一条MPI语句中同时实现向其它进程的数据发送和从其它进程接收数据操作 该操作由通信系统来实现,系统会优化通信次序,从而有效地避免不合理的通信次序,最大限度避免死锁的产生 2018年11月

MPI_Sendrecv应用示意 数据轮换 p0 p1 P(n-1) p2 pi … int a,b; MPI_Status status; int dest = (rank+1)%p; int source = (rank + p -1)%p; /*p为进程个数*/ MPI_Sendrecv( &a, 1, MPI_INT, dest, 99, &b 1, MPI_INT, source, 99, MPI_COMM_WORLD, &status); /*该函数被每一进程执行一次*/ p0 p1 p2 P(n-1) pi 2018年11月

空进程 MPI_PROC_NULL 空进程 rank = MPI_PROC_NULL的进程称为空进程 p0 p1 p2 pi P(n-1) 使用空进程的通信不做任何操作. 向MPI_PROC_NULL发送的操作总是成功并立即返回. 从MPI_PROC_NULL接收的操作总是成功并立即返回,且接收缓冲区内容为随机数. status status.MPI_SOURCE = MPI_PROC_NULL status.MPI_TAG = MPI_ANY_TAG MPI_Get_count(&status,MPI_Datatype datatype, &count) =>count = 0 p0 p1 p2 P(n-1) pi 空进程 2018年11月

空进程应用示意 … MPI_Status status; int dest = (rank+1)%p; int source = (rank + p -1)%p; if(source == p-1) source = MPI_PROC_NULL; if(dest == 0) dest = MPI_PROC_NULL; MPI_Sendrecv( &a, 1, MPI_INT, dest, 99, &b 1, MPI_INT, source, 99, MPI_COMM_WORLD, &status); 2018年11月

MPI点对点通信汇总 2018年11月

内容提示 基本的MPI 深入MPI 基本概念 集合(Collective)通信与同步基础 实例:Hello World 集合(Collective)通信与同步基础 数据广播、收集、散发、转置、归约;进程同步 实例:求π 用户自定义(/派生)数据类型(User-defined(Derived) data type) 事实上MPI的所有数据类型均为MPI自定义类型 支持异构系统 允许消息来自不连续的或类型不一致的存储区(结构,数组散元) MPI程序的编译和运行 深入MPI 深入点对点通信 MPI中API的主要内容 深入聚合通信 实例:矩阵向量相乘 MPI环境管理 组,上下文和通信空间/通信域的管理 2018年11月

数据移动 Broadcast Scatter Gather Allgather Alltoall 2018年11月

数据聚集 Reduce Allreduce Reduce-scatter Scan MPI 预定义全局数据运算符: MPI_MAX / MPI_MIN; MPI_SUM 求和 MPI_PROD 求积MPI_LAND 逻辑与 MPI_LOR 逻辑或 MPI_MAXLOC/MPI_MINLOC 最大/小值求相应位置… … 2018年11月

Reduce_scatter p0 p0 p0 p1 p1 p1 p2 p2 p2 p3 p3 p3 向量按行存储 Reduce-scatter在向量-矩阵乘中的应用 2018年11月

后缀V:更灵活的集合通信 带后缀V的集合通信操作是一种更为灵活的集合通信操作 通信中元素块的大小可以变化 发送与接收时的数据位置可以不连续 2018年11月

MPI_Gather int MPI_Gather ( void *sendbuf, int sendcnt, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm ) 参数: sendbuf 发送缓冲区起始位置 sendcount 发送元素个数 sendtype 发送数据类型 recvcount 接收元素个数(所有进程相同) (该参数仅对根进程有效) recvtype 接收数据类型(仅在根进程中有效) root 通过rank值指明接收进程 comm 通信空间 2018年11月

MPI_Gatherv int MPI_Gatherv ( void *sendbuf, int sendcnt, MPI_Datatype sendtype, void *recvbuf, int *recvcnts, int *displs, MPI_Datatype recvtype, int root, MPI_Comm comm ) 参数: sendbuf 发送缓冲区的起始位置 sendcount 发送元素个数 sendtype 发送数据类型 recvcounts 整型数组(大小等于组的大小),用于指明从各进程要接收的元素的个数(仅对根进程有效) displs 整型数组(大小等于组的大小). 其元素 i指明要接收元素存放位置相对于接收缓冲区起始位置的偏移量 (仅在根进程中有效) recvtype 接收数据类型 root 通过rank值指明接收进程 comm 通信空间 2018年11月

Gather与GatherV 应用Vector派生数据类型 2018年11月

Scatter与ScatterV 2018年11月

注意事项 组内所有进程都参与才能完成 各进程的调用形式都相同 常见问题 有的调用,有的不调用(主进程广播,从进程没有广播) 广播语句和接收语句对应 调用参数不对(若使用一对多或者多对一通信,则各个进程所使用的ROOT值必须都是相同的) 2018年11月

实例: 矩阵向量相乘 p0 p1 p2 2018年11月

parallel_mat_vect.c #include <stdio.h> #include "mpi.h" #define MAX_ORDER 100 typedef float LOCAL_MATRIX_T[MAX_ORDER][MAX_ORDER]; main(int argc, char* argv[]) { int my_rank; int p; LOCAL_MATRIX_T local_A; float global_x[MAX_ORDER]; float local_x[MAX_ORDER]; float local_y[MAX_ORDER]; int m, n; int local_m, local_n; 2018年11月

parallel_mat_vect.c void Read_matrix(char* prompt, LOCAL_MATRIX_T local_A, int local_m, int n, int my_rank, int p); void Read_vector(char* prompt, float local_x[], int local_n, int my_rank, int p); void Parallel_matrix_vector_prod( LOCAL_MATRIX_T local_A, int m, int n, float local_x[], float global_x[], float local_y[], int local_m, int local_n); void Print_matrix(char* title, LOCAL_MATRIX_T local_A, int local_m, void Print_vector(char* title, float local_y[], int local_m, int my_rank, MPI_Init(&argc, &argv); MPI_Comm_size(MPI_COMM_WORLD, &p); MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); if (my_rank == 0) { printf("Enter the order of the matrix (m x n)\n"); scanf("%d %d", &m, &n); } 2018年11月

parallel_mat_vect.c MPI_Bcast(&m, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD); local_m = m/p; local_n = n/p; Read_matrix("Enter the matrix", local_A, local_m, n, my_rank, p); Print_matrix("We read", local_A, local_m, n, my_rank, p); Read_vector("Enter the vector", local_x, local_n, my_rank, p); Print_vector("We read", local_x, local_n, my_rank, p); Parallel_matrix_vector_prod(local_A, m, n, local_x, global_x, local_y, local_m, local_n); Print_vector("The product is", local_y, local_m, my_rank, p); MPI_Finalize(); } /* main */ 2018年11月

parallel_mat_vect.c void Read_matrix(char* prompt /* in */, LOCAL_MATRIX_T local_A /* out */, int local_m /* in */, int n /* in */, int my_rank /* in */, int p /* in */) { int i, j; LOCAL_MATRIX_T temp; /* Fill dummy entries in temp with zeroes */ for (i = 0; i < p*local_m; i++) for (j = n; j < MAX_ORDER; j++) temp[i][j] = 0.0; if (my_rank == 0) { printf("%s\n", prompt); for (j = 0; j < n; j++) scanf("%f",&temp[i][j]); } MPI_Scatter(temp, local_m*MAX_ORDER, MPI_FLOAT, local_A, local_m*MAX_ORDER, MPI_FLOAT, 0, MPI_COMM_WORLD); } /* Read_matrix */ 2018年11月

parallel_mat_vect.c void Read_vector( char* prompt /* in */, float local_x[] /* out */, int local_n /* in */, int my_rank /* in */, int p /* in */) { int i; float temp[MAX_ORDER]; if (my_rank == 0) { printf("%s\n", prompt); for (i = 0; i < p*local_n; i++) scanf("%f", &temp[i]); } MPI_Scatter(temp, local_n, MPI_FLOAT, local_x, local_n, MPI_FLOAT, 0, MPI_COMM_WORLD); } /* Read_vector */ 2018年11月

parallel_mat_vect.c /* All arrays are allocated in calling program */ /* Note that argument m is unused */ void Parallel_matrix_vector_prod( LOCAL_MATRIX_T local_A /* in */, int m /* in */, int n /* in */, float local_x[] /* in */, float global_x[] /* in */, float local_y[] /* out */, int local_m /* in */, int local_n /* in */) { /* local_m = m/p, local_n = n/p */ int i, j; MPI_Allgather(local_x, local_n, MPI_FLOAT, global_x, local_n, MPI_FLOAT, MPI_COMM_WORLD); for (i = 0; i < local_m; i++) { local_y[i] = 0.0; for (j = 0; j < n; j++) local_y[i] = local_y[i] + local_A[i][j]*global_x[j]; } } /* Parallel_matrix_vector_prod */ 2018年11月

parallel_mat_vect.c void Print_matrix( char* title /* in */, LOCAL_MATRIX_T local_A /* in */, int local_m /* in */, int n /* in */, int my_rank /* in */, int p /* in */) { int i, j; float temp[MAX_ORDER][MAX_ORDER]; MPI_Gather(local_A, local_m*MAX_ORDER, MPI_FLOAT, temp, local_m*MAX_ORDER, MPI_FLOAT, 0, MPI_COMM_WORLD); if (my_rank == 0) { printf("%s\n", title); for (i = 0; i < p*local_m; i++) { for (j = 0; j < n; j++) printf("%4.1f ", temp[i][j]); printf("\n"); } } /* Print_matrix */ 2018年11月

parallel_mat_vect.c void Print_vector( char* title /* in */, float local_y[] /* in */, int local_m /* in */, int my_rank /* in */, int p /* in */) { int i; float temp[MAX_ORDER]; MPI_Gather(local_y, local_m, MPI_FLOAT, temp, local_m, MPI_FLOAT, 0, MPI_COMM_WORLD); if (my_rank == 0) { printf("%s\n", title); for (i = 0; i < p*local_m; i++) printf("%4.1f ", temp[i]); printf("\n"); } } /* Print_vector */ 2018年11月

内容提示 基本的MPI 深入MPI 基本概念 集合(Collective)通信与同步基础 实例:Hello World 集合(Collective)通信与同步基础 数据广播、收集、散发、转置、归约;进程同步 实例:求π 用户自定义(/派生)数据类型(User-defined(Derived) data type) 事实上MPI的所有数据类型均为MPI自定义类型 支持异构系统 允许消息来自不连续的或类型不一致的存储区(结构,数组散元) MPI程序的编译和运行 深入MPI 深入点对点通信 MPI中API的主要内容 深入聚合通信 实例:矩阵向量相乘 MPI环境管理 组,上下文和通信空间/通信域的管理 2018年11月

MPI环境管理 通信域:描述进程间的通信关系,包括 通信上下文:区别不同的通信域,一个上下文所发送的消息不能被另一个上下文所接收 进程组:多个进程的有序集合 虚拟拓扑:多个进程在逻辑上的排列关系,反映了进程间的通信模型 属性:用户可通过自定义的属性,将任意信息附加到通信域上 2018年11月

预定义的进程组和通信域 MPI_GROUP_NULL MPI_COMM_NULL MPI_GROUP_EMPTY MPI_COMM_SELF 无效进程组句柄 MPI_COMM_NULL 无效通信域句柄 MPI_GROUP_EMPTY 有效进程组句柄,包括元素个数为0 MPI_COMM_SELF 有效通信域句柄,包括元素仅为当前进程 MPI_COMM_WORLD 有效通信域句柄,包括元素为所有进程 2018年11月

进程组 不同的进程可以有不同的分工,可为之建立不同的通信域,这要借助进程组完成。 得到通信域对应的进程组: 根据组创建新的通信域: MPI_COMM_GROUP(comm,group) 根据组创建新的通信域: MPI_COMM_CREATE(comm,g,ncomm) 2018年11月

进程组操作 比较: 并: 交: 差: MPI_GROUP_COMPARE(g1,g2,result) MPI_GROUP_UNION(g1,g2,newg) 交: MPI_GROUP_INTERSECTION(g1,g2,newg) 差: MPI_GROUP_DIFFERENCE(g1,g2,newg) 2018年11月

包含在组内: 不包含在组内: 得到组的大小 得到当前进程在组中的编号 MPI_GROUP_INCL(g,n,ranks,newg) MPI_GROUP_EXCL(g,n,ranks,newg) 得到组的大小 MPI_GROUP_SIZE(group,size) 得到当前进程在组中的编号 MPI_GROUP_RANK(group,rank) 2018年11月

进程组应用示例 ... MPI_Init(&argc,&argv); MPI_Comm_group(MPI_COMM_WORLD,&MPI_GROUP_WORLD); /*得到MPI_COMM_WORLD对应的进程组*/ MPI_Comm_rank(MPI_COMM_WORLD,&me); MPI_Group_excl(MPI_GROUP_WORLD,1,ranks,&grprem);/*创建一个不包括进程0 的新的进程组*/ MPI_Comm_create(MPI_COMM_WORLD,grprem,&commslave);/*根据新创建的进程 组创建一个不包括进程0的新的通信域*/ if((me!=0) {/*进程0之外的进程执行如下操作*/ MPI_Reduce(send_buf,recv_buff,count,MPI_INT,MPI_SUM,1,commslave);/* 使用 新的通信域进行通信不包括原来的进程0*/ } 2018年11月

通信域 比较 复制 释放 MPI_COMM_COMPARE(comm1,comm2,result) MPI_COMM_DUP(comm,newcomm) 释放 MPI_COMM_FREE(comm) 2018年11月

通信域的分裂 根据color值的不同,形成不同的通信域 根据key值的大小,决定新通信域中的编号顺序 MPI_COMM_SPLIT(comm,color,key,newcomm) 根据color值的不同,形成不同的通信域 根据key值的大小,决定新通信域中的编号顺序 2018年11月

通信域分裂的例子 int membershipKey; int rank; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD,&rank); membershipKey=rank%3; /*将MPI_COMM_WORLD 分裂为三个子通信域membershipKey 相同的进程在 一个子通信域中*/ MPI_Comm_split(MPI_COMM_WORLD,membershipKey,rank,&myComm); 2018年11月

虚拟拓扑 在许多并行应用程序中,进程的线性排列不能充分地反映进程间的通信模型 进程经常被排列成二维或三维网格形式的拓扑模型,而且通常用一个图来描述逻辑进程排列 不同的进程拓扑结构,可使程序设计更为自然,也为在相近的物理拓扑上的高效实现提供支持。 2018年11月

笛卡尔拓扑 使用环、网格等进程拓扑时,笛卡尔坐标更为方便 2018年11月

创建笛卡儿拓扑 2*7 2018年11月

调用 示例: MPI_COMM_WORLD,2,{4,5},{false,false},false,newcomm 2018年11月

得到每一维的大小 MPI_DIMS_CREATE根据用户指定的总维数ndims和总的进程数nnodes ,帮助用户在每一维上选择进程的个数。返回结果放在dims中,它可以作为MPI_CART_CREATE的输入参数。 2018年11月

得到卡氏坐标 MPI_CART_COORDS将进程rank的顺序编号转换为笛卡儿坐标coords。其中maxdims是维数。 2018年11月

平移操作 MPI_CART_SHIFT(comm, direction, disp, rank_source, rank_dest) 哪一维 偏移 当前进程 向右偏移3 2018年11月

应用示例 int rank, value, size, false=0; int right_nbr, left_nbr; MPI_Comm ring_comm; MPI_Status status; MPI_Init( &argc, &argv ); MPI_Comm_size( MPI_COMM_WORLD, &size ); MPI_Cart_create( MPI_COMM_WORLD, 1, &size, &false, 1, &ring_comm ); /* 创建一维网格false意味着没有周期性两端外的进程标识为MPI_PROC_NULL 1表示可以重排 序得到的拓扑坐标相邻关系为MPI_PROC_NULL, 0, 1, ... , size-1 , MPI_PROC_NULL */ MPI_Cart_shift( ring_comm, 0, 1, &left_nbr, &right_nbr ); /*通过在定义的网格上的平移得到左右侧进程的标识*/ MPI_Comm_rank( ring_comm, &rank ); /*得到在新拓扑中的标识或坐标*/ 2018年11月

应用示例(续) MPI_Comm_size( ring_comm, &size );/*得到新通信域中进程的个数*/ do { if (rank == 0) {/*进程0负责读入数据并向下一个进程传递数据*/ scanf( "%d", &value ); MPI_Send( &value, 1, MPI_INT, right_nbr, 0, ring_comm );/*将数据传送到右面的进 程*/ } else { MPI_Recv( &value, 1, MPI_INT, left_nbr, 0, ring_comm, &status );/*后面的进程从左边的进程接收数据*/ MPI_Send( &value, 1, MPI_INT, right_nbr, 0, ring_comm );/*将接收到的数据在传递给右面的进程*/ printf( "Process %d got %d\n", rank, value );/*各进程打印各自得到的数据*/ } while (value >= 0);/*若读入的数据非负则继续读入并传递*/ MPI_Finalize( ); 2018年11月

图拓扑 节点表示进程 边表示进程之间的通信 可以表示所有类型的通信 2018年11月

创建图拓扑 MPI_GRAPH_CREATE返回一个指向新的通信域的句柄,这个通信域包含的进程的拓扑结构是一个由参数nnodes、index和edges定义的图。 2018年11月

得到相邻进程数 得到与指定进程rank相邻的进程总数nneighbors 2018年11月

得到相邻进程标识 得到与指定进程rank相邻的进程标识neighbors 2018年11月

并行程序设计的一些建议 优化并行算法 大并行粒度 顾及负载平衡 尽量减少通信次数 避免大消息(1M) 避免大消息打包 避免消息缓冲区的溢出,且效率较低 避免大消息打包 内存拷贝开销大 2018年11月

谢谢! 2018年11月