目录 第一章 MPI 简介 消息传递编程的相关概念 分布式内存 消息传输 进程 消息传递库 发送 / 接收 同步 / 异步 阻塞

Similar documents
Linux Linux Linux

PowerPoint 演示文稿

消息传递并行编程环境MPI.doc

模板


投影片 1

第7章-并行计算.ppt

Department of Science and Engineering Computing School of Mathematics School Peking University October 9, 2007

Microsoft PowerPoint - Tongji_MPI编程初步

Microsoft PowerPoint - KN002.ppt

Parallel Programming with MPI

Parallel Programming with MPI

PowerPoint 演示文稿

大綱介紹 MPI 標準介紹 MPI 的主要目標 Compiler & Run 平行程式 MPICH 程式基本架構 點對點通訊 函數介紹 集體通訊 函數介紹

PowerPoint 演示文稿

PowerPoint Presentation

PowerPoint 演示文稿

PowerPoint 演示文稿

1 学习目标了解并掌握 MPI 的各种非阻塞通信形式及其作用, 并能运用 MPI 的非阻塞通信语句编写高级的并行程序 2 重点和难点非阻塞通信的语义特点, 如何运用非阻塞通信的特点来实现特定的功能和性能 3 学习方法所有的阻塞调用形式都有其相应的非阻塞调用形式, 此外非阻塞调用还有其特殊的接口形式

mpi

目 录 参 考 材 料 1 第 一 章 预 备 知 识 高 性 能 并 行 计 算 机 系 统 简 介 微 处 理 器 的 存 储 结 构

CC213

07-1.indd

C 1

C/C++语言 - C/C++数据

C++ 程式設計

Microsoft Word - 把时间当作朋友(2011第3版)3.0.b.06.doc

mannal

to AztecOO Department of Science and Engineering Computing School of Mathematics School Peking University October 9, 2007

MPI实验.doc

chap07.key

FY.DOC

C/C++ 语言 - 循环

untitled


Trilinos 简介 Trilinos 简介 卢朓 Trilinos 简介 卢朓 Trilinos 简介 Trilinos 简介 Trilinos 的安装和使用 Trilinos 简介 Trilinos 简介 Trilinos 的安装和使用 Trilinos 简介 Trilinos Epetra

OOP with Java 通知 Project 4: 4 月 18 日晚 9 点 关于抄袭 没有分数

新・明解C言語入門編『索引』

C

untitled

untitled


1

新版 明解C++入門編

OOP with Java 通知 Project 4: 4 月 19 日晚 9 点

Slide 1

í

OOP with Java 通知 Project 2 提交时间 : 3 月 14 日晚 9 点 另一名助教 : 王桢 学习使用文本编辑器 学习使用 cmd: Power shell 阅读参考资料

Microsoft PowerPoint - 4. 数组和字符串Arrays and Strings.ppt [兼容模式]

Department of Science and Engineering Computing School of Mathematics School Peking University October 9, 2007

C 1 # include <stdio.h> 2 int main ( void ) { 4 int cases, i; 5 long long a, b; 6 scanf ("%d", & cases ); 7 for (i = 0;i < cases ;i ++) 8 { 9

untitled

《C语言程序设计》第2版教材习题参考答案


mpic_2002

, 7, Windows,,,, : ,,,, ;,, ( CIP) /,,. : ;, ( 21 ) ISBN : -. TP CIP ( 2005) 1

《C语言程序设计》教材习题参考答案


科学计算的语言-FORTRAN95

c_cpp

Department of Science and Engineering Computing School of Mathematics School Peking University August 14, 2007

Microsoft PowerPoint - ds-1.ppt [兼容模式]

1 Project New Project 1 2 Windows 1 3 N C test Windows uv2 KEIL uvision2 1 2 New Project Ateml AT89C AT89C51 3 KEIL Demo C C File

3.1 num = 3 ch = 'C' 2

ō

( CIP) /. :, ( ) ISBN TP CIP ( 2005) : : : : * : : 174 ( A ) : : ( 023) : ( 023)

(i) (ii) (iii) (i) (ii) (iii) (iv) 1. 2

` ù




í

C/C++语言 - 分支结构



ǎ ì



Guava学习之Resources




Microsoft PowerPoint - PC14.pptx


ò ó ì á è ó

099

ì

à

ttian


ú ì


ü Ä ä ä ï ï ü ä ä

第3章.doc


二零零六年一月二十三日會議

厨房小知识(四)

妇女更年期保健.doc

小儿传染病防治(上)

<4D F736F F D B875B9B5A448ADFBBADEB27AA740B77EA4E2A5555FA95EAED6A641ADD75F2E646F63>

Transcription:

神威蓝光 计算机系统 MPI 用户手册 国家超级计算济南中心 2011 年 03 月

目录 第一章 MPI 简介... 1 1.1 消息传递编程的相关概念... 2 1.1.1 分布式内存... 2 1.1.2 消息传输... 3 1.1.3 进程... 3 1.1.4 消息传递库... 3 1.1.5 发送 / 接收... 3 1.1.6 同步 / 异步... 3 1.1.7 阻塞通讯... 4 1.1.8 非阻塞通讯... 4 1.1.9 应用程序缓冲区... 4 1.1.10 系统缓冲区... 4 1.2 MPI 环境... 5 1.2.1 头文件... 5 1.2.2 MPI 调用格式... 5 1.2.3 一般 MPI 程序结构... 6 1.2.4 通信因子和组... 6 1.2.5 秩... 6 第二章 MPI 环境管理调用... 7 2.1 函数调用... 7 2.1.1 MPI_Init... 7 2.1.2 MPI_Comm_size... 7 2.1.3 MPI_Comm_rank... 7 2.1.4 MPI_Abort... 7 2.1.5 MPI_Get_processor_name... 8 2.1.6 MPI_Initialized... 8 2.1.7 MPI_Wtime... 8 2.1.8 MPI_Wtick... 8 2.1.9 MPI_Finalize... 8 2.2 MPI 环境管理例子... 9 2.2.1 C 语言例子... 9 2.2.2 Fortran 语言例子... 9 第三章点对点通信函数... 11 3.1 传递参数说明... 11 3.1.1 MPI 消息传递函数参数... 11 3.1.2 缓冲区 (buffer)... 12 3.1.3 元素个数 (count)... 12 3.1.4 数据元素类型 (type)... 12 3.1.5 目的地 (dest)... 12 3.1.6 源 (source)... 12 3.1.7 标识符 (tag)... 13 i

3.1.8 通信因子 (comm)... 13 3.1.9 状态 (status)... 13 3.1.10 请求 (request)... 13 3.2 阻塞消息通信函数... 13 3.2.1 MPI_Send... 13 3.2.2 MPI_Recv... 14 3.2.3 MPI_Ssend... 14 3.2.4 MPI_Bsend... 14 3.2.5 MPI_Buffer_attach MPI_Buffer_detach... 14 3.2.6 MPI_Rsend... 15 3.2.7 MPI_Sendrecv... 15 3.2.8 MPI_Wait MPI_Waitany MPI_Waitall MPI_Waitsome... 15 3.2.9 MPI_Probe... 15 3.3 阻塞消息传递例子... 16 3.3.1 C 程序例子... 16 3.3.2 Fortran 程序例子... 16 3.4 非阻塞消息通信函数... 17 3.4.1 MPI_Isend... 17 3.4.2 MPI_Irecv... 18 3.4.3 MPI_Issend... 18 3.4.4 MPI_Ibsend... 18 3.4.5 MPI_Irsend... 18 3.4.6 MPI_Test MPI_Testany MPI_Testall MPI_Testsome... 18 3.4.7 MPI_Iprobe... 19 3.5 非阻塞消息传递例子... 19 3.5.1 C 语言例子... 19 3.5.2 Fortran 语言例子... 20 第四章集合通信函数... 21 4.1 集合通信函数... 21 4.1.1 MPI_Barrier... 21 4.1.2 MPI_Bcast... 21 4.1.3 MPI_Scatter... 22 4.1.4 MPI_Gather... 22 4.1.5 MPI_Allgather... 23 4.1.6 MPI_Reduce... 23 4.1.7 MPI_Allreduce... 24 4.1.8 MPI_Reduce_scatter... 24 4.1.9 MPI_Alltoall... 24 4.1.10 MPI_Scan... 24 4.2 集合通信操作例子... 26 4.2.1 C 语言例子... 26 4.2.2 Fortran 语言例子... 27 4.2.3 例子输出结果... 28 第五章派生数据类型... 29 ii

5.1 派生数据类型函数... 29 5.1.1 MPI_Type_contiguous... 29 5.1.2 MPI_Type_vector MPI_Type_hvector... 30 5.1.3 MPI_Type_indexed MPI_Type_hindexed... 30 5.1.4 MPI_Type_struct... 31 5.1.5 MPI_Type_extent... 31 5.1.6 MPI_Type_commit... 31 5.2 连续数据类型的例子... 32 5.2.1 C 语言例子... 32 5.2.2 Fortran 语言例子... 33 5.2.3 例子输出结果... 34 5.3 向量派生数据类型例子... 34 5.3.1 C 语言例子... 34 5.3.2 Fortran 语言例子... 35 5.3.3 程序输出结果... 36 5.4 索引派生数据类型例子... 37 5.4.1 C 语言例子... 37 5.4.2 Fortran 语言例子... 38 5.4.3 例子输出结果... 39 5.5 结构派生数据类型例子... 40 5.5.1 C 语言例子... 40 5.5.2 Fortran 语言例子... 41 5.5.3 例子输出结果... 43 第六章组和通信因子管理函数... 44 6.1 组和通信因子管理函数... 45 6.1.1 MPI_Comm_group... 45 6.1.2 MPI_Group_rank... 45 6.1.3 MPI_Group_size... 45 6.1.4 MPI_Group_excl... 45 6.1.5 MPI_Group_incl... 45 6.1.6 MPI_Group_intersection... 45 6.1.7 MPI_Group_union... 46 6.1.8 MPI_Group_difference... 46 6.1.9 MPI_Group_compare... 46 6.1.10 MPI_Group_free... 46 6.1.11 MPI_Comm_create... 46 6.1.12 MPI_Comm_dup... 46 6.1.13 MPI_Comm_compare... 47 6.1.14 MPI_Comm_free... 47 6.2 组和通信因子管理函数运用例子... 47 6.2.1 C 语言例子... 47 6.2.2 Fortran 语言例子... 48 6.2.3 例子结果输出... 49 第七章虚拟拓扑... 50 iii

7.1 虚拟拓扑函数... 50 7.1.1 MPI_Cart_coords... 50 7.1.2 MPI_Cart_create... 50 7.1.3 MPI_Cart_get... 50 7.1.4 MPI_Cart_map... 50 7.1.5 MPI_Cart_rank... 50 7.1.6 MPI_Cart_shift... 51 7.1.7 MPI_Cart_sub... 51 7.1.8 MPI_Cartdim_get... 51 7.1.9 MPI_Dims_create... 51 7.1.10 MPI_Graph_create... 51 7.1.11 MPI_Graph_get... 52 7.1.12 MPI_Graph_map... 52 7.1.13 MPI_Graph_neighbors... 52 7.1.14 MPI_Graphdims_get... 52 7.1.15 MPI_Topo_test... 52 7.2 笛卡儿虚拟拓扑例子... 52 7.2.1 C 语言例子... 53 7.2.2 Fortran 语言例子... 54 7.2.3 例子输出结果... 55 第八章 MPI 运行环境... 56 8.1 MPI 用户程序的编译与连接... 56 8.2 例子... 56 8.2.1 例子一 (hello 程序 )... 56 8.2.2 例子二 ( 一个简单的串行程序转换为并行程序 )... 57 8.2.3 例子三 ( 计算 π 的串行及并行程序 )... 59 iv

第一章 MPI 简介 在消息传递库方法的并行编程中, 一组进程所执行的程序是用标准串行语言书写的代码加上用于消息接收和发送的库函数调用 其中,MPI(Message Passing Interface ) 是 1994 年 5 月发布的一种消息传递接口, 它实际上是一个消息传递函数库的标准说明, 吸取了众多消息传递系统的优点, 是目前国际上最流行的并行编程环境之一, 尤其是分布式存储的可缩放并行计算机和工作站网络以及机群的一种编程范例 MPI 具有许多优点 : 具有可移植性和易用性 ; 有完备的异步通信功能 ; 有正式和详细的精确定义 因而为并行软件产业的增长提供了必要的条件 在基于 MPI 编程模型中, 计算是由一个或多个彼此通过调用库函数进行消息收 发通信的进程所组成 在绝大部分 MPI 实现中, 一组固定的进程在程序初始化时生成, 一般情况下, 一个处理器只生成一个进程 这些进程可以执行相同或不同的程序 ( 相应地称为单程序多数据 (SPMD) 或多程序多数据 (MPMD) 模式 ) 进程间的通信可以是点到点的, 也可以是集合的 MPI 只是为程序员提供一个并行环境库, 程序员通过调用 MPI 的库程序来达到程序员所要达到的并行目的,MPI 提供 C 语言和 Fortran 语言接口 MPI 是个复杂的系统, 它包含了 129 个函数 ( 根据 1994 年发布的 MPI 标准 ) 事实上,1997 年修订的标准, 称之为 MPI-2, 已超过 200 个, 目前最常用的也有约 30 个, 然而我们可以只使用其中的 6 个最基本的函数就能编写一个完整的 MPI 程序去求解很多问题 这 6 个基本函数, 包括启动和结束 MPI 环境, 识别进程以及发送和接收消息 : MPI_INIT: 启动 MPI 环境 MPI_Init( ); MPI_FIANLIZE: MPI_Finalize(); 结束 MPI 环境 MPI_COMM_SIZE: MPI_Comm_size( ); 确定进程数 MPI_COMM_RANK: MPI_Comm_rank( ); 确定自己的进程标识符 MPI_SEND: MPI_Send( ); 发送一条消息 MPI_RECV: MPI_Recv( ) 接收一条消息 1

一个简单例子 : #include mpi.h main(int argc,char **argv) { int myrank,i,j,k; MPI_Status status; char msg[20]; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD,&myrank); if(myrank == 0){ strcpy(msg, Hello there ); MPI_Send(msg,strlen(msg) + 1,MPI_CHAR,1,99,MPI_COMM_WORLD); else if(myrank == 1){ MPI_Recv(msg,20,MPI_CHAR,0,99,MPI_COMM_WORLD,&status); printf( Receive message = %s\n,msg); MPI_Finalize(); 其中,MPI_COMM_WORLD 是一个缺省的进程组, 它指明所有的进程都参与计算 1.1 消息传递编程的相关概念 1.1.1 分布式内存 每个处理器都有自己的私有存储空间, 数据从一个处理器到另一个处理器只有通过网络来传输, 而共享存储中的多个处理器可同时访问同一内存单元 分布式系统的示意图如下 : 2

1.1.2 消息传输 消息传输是消息数据从一个处理器的内存拷贝到另一个处理器内存的方法 在分布式存储系统中, 数据通常是以消息包的形式通过网络从一个处理器发送到另一个处理器 在消息包中, 包含了消息头控制信息及消息体数据信息两部分 1.1.3 进程 进程是运行在一个处理器上的一个程序, 多个进程可运行在同一个处理器上 在消息传递系统中, 即使是在同一个处理器中运行的多个进程, 他们之间的数据通讯也是通过消息传输来实现的 为了提高效率, 在消息传递系统中, 一般一个处理器只运行一个进程 1.1.4 消息传递库 可连接到用户应用程序中实现消息发送 消息接收以及其他消息传递操作的一组函数的集合 1.1.5 发送 / 接收 消息通信包括数据传输从一个进程 ( 发送 ) 到另一个进程 ( 接收 ) 这就要求两个进程协作完成消息发送和接收过程, 发送进程一般要求指定发送数据的源 数据长度 数据类型及目的地, 而接收操作也要求指定相应的操作与发送相匹配 1.1.6 同步 / 异步 同步发送操作只有等到消息被接收进程安全接收后才完成, 而异步发送操作完成后消息不一定被接收进程接收 3

1.1.7 阻塞通讯 阻塞通讯的调用是否完成要依靠某些 事件 : 对于阻塞发送, 数据必须成功的发送或被拷贝到系统缓冲区, 使得该数据缓冲区可被重新使用 ; 对于阻塞接收, 数据必须保证正确接收到本地缓冲区 1.1.8 非阻塞通讯 非阻塞通讯不等待任何通讯事件就可以完成, 它不保证数据已正确发送或接收 发送或接收的数据也许在源方, 也许正在网上, 也许已经到目的方 1.1.9 应用程序缓冲区 在用户应用程序中定义的用于保存发送和接收数据的地址空间 1.1.10 系统缓冲区 保留消息的系统空间 在异步通讯的条件下, 一般需要把数据从应用程序缓冲区中拷贝到系统缓冲区, 保证用户数据不被覆盖 4

1.2 MPI 环境 1.2.1 头文件 要求所有包含 MPI 调用得程序文件应加入 : C 包含文件 #include mpi.h Fortran 包含文件 include mpif.h 1.2.2 MPI 调用格式 C 程序区分大小写,Fortran 程序不区分大小写 C 程序格式 : rc = MPI_Xxxxx(parameter,... ) 例子 : rc = MPI_Bsend(&buf,count,type,dest,tag,comm) 错误码 : 如果调用成功则 rc 返回 MPI_SUCCESS 格式 : 例子 : 错误码 : Fortran 程序 CALL MPI_XXXXX(parameter,..., ierr) call mpi_xxxxx(parameter,..., ierr) CALL MPI_BSEND(buf,count,type,dest,tag,comm,ierr) 如果调用成功则 ierr 返回 MPI_SUCCESS 在 C 语言描述中, 函数名均冠以 MPI 前缀, 且其首字母需大写 返回的状态值是整数 ; 成功完成的返回代码是 MPI_SUCCESS; 失败时也会定义一组错误代码 编译时的常数均须大写且被定义在文件 mpi.h 中,mpi.h 在任何需调用 MPI 的程序中必须被包含进来 在 Fortran 语言描述中, 函数名也冠以 MPI 前缀, 且必须大写 函数返回代码由一个附加的整数变量表示之 ; 成功完成的返回代码是 MPI_SUCCESS; 失败时也会定义一组错误代码 编译时的常数均须大写且被定义在文件 mpif.h 中, 它在任何需调用 MPI 的程序中必须被包含进来 5

1.2.3 一般 MPI 程序结构 包含 MPI 头文件.. 初始化 MPI 环境... 消息交换处理及计算等.. 退出 MPI 环境 1.2.4 通信因子和组 MPI 通过指定通信因子和组来完成各个进程间得通信, 大多数 MPI 调用要求加入通信因子这个参数 MPI_COMM_WORLD 通信因子是在 MPI 环境初始化过程中创建地包含了所有进程, 也是最重要的一个通信因子, 详细的用法下面再介绍 1.2.5 秩 在一个通信因子中, 每个进程都有一个唯一的整数标识符, 该标识符在 MPI 初始化时创建, 有时也称作 进程 ID, 秩是从 0 开始的连续整数 在用户程序中, 经常用秩来判断程序的运行方向 如 : if (rank ==0) { do this else if(rank == 1) { do that 6

第二章 MPI 环境管理调用 2.1 函数调用 2.1.1 MPI_Init 初始和启动 MPI 运行环境 每个 MPI 程序必须调用这个函数, 并且这个函数必须在所有调用 MPI 函数之前调用, 而且只能调用一次 对于 C 程序,MPI_Init 必须传递所有的命令行参数, 即要把 argc 和 argv 传递到 MPI 初始化函数中 : MPI_Init (*argc,***argv) MPI_INIT (ierr) 2.1.2 MPI_Comm_size 该函数返回与该组通信因子相关的进程数, 通常可以根据全局通信因子 MPI_COMM_WORD 来查询用户程序包含的进程数 : MPI_Comm_size (comm,*size) MPI_COMM_SIZE (comm,size,ierr) IN comm 通信因子 ; OUT size 在该通信因子中包含的进程数目 2.1.3 MPI_Comm_rank 该函数返回该进程在指定通信因子中的秩 (0 ~ 进程数 -1), 一个进程在不同通信因子 中的秩可能不同 : MPI_Comm_rank (comm,*pid) MPI_COMM_RANK (comm,pid,ierr) IN comm 通信因子 ; OUT pid 在该通信因子中本进程的进程号 2.1.4 MPI_Abort 结束所有与该通信因子相关的进程, 但一般来说, 调用该函数后, 所有的进程都退出, 不管该进程是否与该通信因子相关 : MPI_Abort (comm,errorcode) MPI_ABORT (comm,errorcode,ierr) IN comm 通信因子 ; 7

OUT errorcode 进程退出时返回的错误号 2.1.5 MPI_Get_processor_name 返回该进程所在的计算节点的名称, 该名称根据网络地址命名,name 缓冲区的大小必 须大于 MPI_MAX_PROCESSOR_NAME, 真正的长度返回在 resultlength 变量中 : MPI_Get_processor_name (*name,*resultlength) MPI_GET_PROCESSOR_NAME (name,resultlength,ierr) OUT name 计算节点名称 ( 即计算节点的 hostname); OUT resultlength 该名称字符串的实际长度 2.1.6 MPI_Initialized 判断 MPI_Init 是否被执行, 返回 true(1),false(0),mpi 应用程序只允许每一个进程仅运行一次 MPI_Init: MPI_Initialized (*flag) MPI_INITIALIZED (flag,ierr) OUT flag MPI_Init 是否运行标识 (0/1) 2.1.7 MPI_Wtime 返回调用进程已经执行过地时间, 以秒为单位, 双精度 : MPI_Wtime () MPI_WTIME () 2.1.8 MPI_Wtick 按秒返回 MPI_Wtime 的分辨率, 也就是返回一个双精度值 ( 连续时间之间的秒数 ) 例如, 如果时钟由作为按毫秒递增的计数器实现, 则 MPI_Wtick 返回的值是 10-3 MPI_Wtick () MPI_WTICK () 2.1.9 MPI_Finalize 结束 MPI 执行环境 该函数一旦被应用程序调用时, 就不能调用 MPI 的其它例行函数 ( 包括 MPI_Init), 用户必须保证在进程调用 MPI_Finalize 之前把与完成进程有关的所有通信结束 MPI_Finalize () MPI_FINALIZE (ierr) 8

2.2 MPI 环境管理例子 2.2.1 C 语言例子 #include "mpi.h" #include <stdio.h> int main(argc,argv) int argc; char *argv[]; { int numtasks, rank, rc; rc = MPI_Init(&argc,&argv); if (rc!= 0) { printf ("Error starting MPI program. Terminating.\n"); MPI_Abort(MPI_COMM_WORLD, rc); MPI_Comm_size(MPI_COMM_WORLD,&numtasks); MPI_Comm_rank(MPI_COMM_WORLD,&rank); printf ("Number of tasks= %d My rank= %d\n", numtasks,rank); /******* do some work *******/ MPI_Finalize(); 2.2.2 Fortran 语言例子 program simple include 'mpif.h' integer numtasks, rank, ierr, rc call MPI_INIT(ierr) if (ierr.ne. 0) then print *,'Error starting MPI program. Terminating.' call MPI_ABORT(MPI_COMM_WORLD, rc, ierr) end if 9

call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr) call MPI_COMM_SIZE(MPI_COMM_WORLD, numtasks, ierr) print *, 'Number of tasks=',numtasks,' My rank=',rank C ****** do some work ****** call MPI_FINALIZE(ierr) end 10

第三章点对点通信函数 点对点通信 (Point-to-Point Communication) 是 MPI 中比较复杂的一部分, 其数据传送有阻塞和非阻塞两组机制 : 对于阻塞方式, 它必须等到消息从本地送出之后才可以执行后续的语句, 保证了缓冲区等资源的可再用性 ; 对于非阻塞方式, 它不须等到消息从本地送出就可以执行后续的语句, 从而允许通信和计算的重叠, 但非阻塞调用的返回并不保证资源的可再用性 如下图所示, 阻塞和非阻塞有四种模式 : 1. 标准模式, 包括阻塞 ( 标准 ) 发送 MPI_SEND 阻塞( 标准 ) 接收 MPI_RECV 非阻塞 ( 标准 ) 发送 MPI_ISEND 非阻塞( 标准 ) 接收 MPI_IRECV; 2. 缓冲区模式 : 包括阻塞缓冲发送 MPI_BSEND 和非阻塞缓冲发送 MPI_IBSEND; 3. 同步模式 : 包括阻塞同步发送 MPI_SSEND 和非阻塞同步发送 MPI_ISSEND; 4. 就绪模式 : 包括阻塞就绪发送 MPI_RSEND 和非阻塞就绪发送 MPI_IRSEND 在标准通信模式中,MPI 根据当前的状况选取其它三种模式或用户定义的其它模式 ; 缓冲区模式在相匹配的接收未开始的情况下, 总是将送出的消息放在缓冲区内, 这样发送者可以很快地继续计算, 然后由系统处理放在缓冲区中的消息, 但这不仅占用内存, 而且多用了一次内存拷贝 ; 在同步模式中,MPI 必须保证接收者执行到某一点, 这样接收者是必须有确认信息的 ; 在就绪模式下, 系统默认与其相匹配的接收已经调用 标准 1 缓冲 S R 1 同步 S 2 R 3 1 就绪 S 2 R 点到点消息通信四种模式 1 2 3.1 传递参数说明 3.1.1 MPI 消息传递函数参数 MPI 点对点通信函数的参数格式一般如下所示 : 阻塞发送 MPI_Send(buffer,count,type,dest,tag,comm) 非阻塞发送 MPI_Isend(buffer,count,type,dest,tag,comm,request) 阻塞接收 MPI_Recv(buffer,count,type,source,tag,comm,status) 非阻塞接收 MPI_Irecv(buffer,count,type,source,tag,comm,request) 11

3.1.2 缓冲区 (buffer) 指应用程序定义地用于发送或接收数据的缓冲区 3.1.3 元素个数 (count) 指发送或接收数据元素的个数 3.1.4 数据元素类型 (type) MPI 定义了一些缺省的数据类型, 用户也可以根据需要建立自己的数据类型, 其中 MPI_BYTE 和 MPI_PACKED 与 C 或 Fortran 语言的类型不对应 : MPI C 语言数据类型 MPI_CHA signed char R signed short int MPI_SHOR T MPI_INT signed int MPI_LONG signed long int MPI_UNSIGNED_CHAR unsigned char MPI Fortran 语言数据类型 MPI_CHARACTER character(1) MPI_INTEGER integer MPI_UNSIGNED_SHORT unsigned short int MPI_UNSIGNED unsigned int MPI_UNSIGNED_LONG unsigned long int MPI_FLOAT float MPI_REAL real MPI_DOUBLE double MPI_DOUBLE_PRECISION double precision MPI_LONG_DOUBLE long double MPI_COMPLEX complex MPI_LOGICAL logical MPI_BYTE MPI_PACKED 8 binary digits data packed or unpacked with MPI_Pack()/ MPI_Unpack MPI_BYTE MPI_PACKED 8 binary digits data packed or unpacked with MPI_Pack()/ MPI_Unpack 3.1.5 目的地 (dest) 发送进程指定的接收该消息的目的进程, 也就是接收进程的秩 3.1.6 源 (source) 接收进程指定的发送该消息的源进程, 也就是发送进程的秩 如果该值为 MPI_ANY_SOURCE 表示接收任意源进程发来的消息

12

3.1.7 标识符 (tag) 由程序员指定地为标识一个消息的唯一非负整数值 (0-32767), 发送操作和接收操作的标识符一定要匹配, 但对于接收操作来说, 如果 tag 指定为 MPI_ANY_TAG 则可与任何发送操作的 tag 相匹配 3.1.8 通信因子 (comm) 包含源与目的进程的一组上下文相关的进程集合, 除非用户自己定义 ( 创建 ) 了新的通信因子, 否则一般使用系统预先定义的全局通信因子 MPI_COMM_WORLD, 它表示所有进程都参与计算 3.1.9 状态 (status) 对于接收操作, 包含了接收消息的源进程 (source) 和消息的标识符 (tag) 在 C 程序中, 这个参数是指向 MPI_Status 结构的指针 ( 如 :status.mpi_source status.mpi_tag); 在 Fortran 程序中, 这个参数是大小为 MPI_STATUS_SIZE 的整数矩阵 ( 如 : status(mpi_source) status(mpi_tag) ) 另外, 实际接收到的消息长度可以通过 MPI_Get_count() 函数从该参数中得到 3.1.10 请求 (request) 这个参数用于非阻塞发送和非阻塞接收操作 由于非阻塞操作返回后, 消息实际上还没有完成到达真正发送或接收, 因此用户可以根据该变量调用其它函数完成消息的实际发送和接收 在 C 程序中, 这个参数是指向 MPI_Request 结构的指针 ; 在 Fortran 程序中, 这个参数是一个整数 3.2 阻塞消息通信函数 这里介绍一些最主要的阻塞消息通信函数 : 3.2.1 MPI_Send 该函数是最基本的阻塞发送函数 当函数返回时, 应用程序的发送缓冲区空闲, 可以继续使用 MPI_Send (*buf,count,datatype,dest,tag,comm) MPI_SEND (buf,count,datatype,dest,tag,comm,ierr) IN IN IN buf count datatype 发送数据缓冲区的起始地址 ; 发送数据元素的个数 (>=0); 数据元素的数据类型 ; 13

IN IN IN dest tag comm 接收进程的进程号 ; 消息标识 ; 通信因子 3.2.2 MPI_Recv 阻塞接收消息, 直到该消息到达本进程的接收缓冲区后才返回 MPI_Recv (*buf,count,datatype,source,tag,comm,*status) MPI_RECV (buf,count,datatype,source,tag,comm,status,ierr) OUT buf 接收数据缓冲区的起始地址 ; IN IN IN IN IN count datatype source tag comm 接收数据元素的个数 (>=0); 接收数据元素的数据类型 ; 发送进程的进程号或 MPI_ANY_SOURCE; 消息标识或 MPI_ANY_TAG; 通信因子 ; OUT status 接收状态信息 3.2.3 MPI_Ssend 同步阻塞发送 : 发送一个消息, 直到发送进程的缓冲区空闲并且接收进程已经开始接收该消息后返回 MPI_Ssend (*buf,count,datatype,dest,tag,comm,ierr) MPI_SSEND (buf,count,datatype,dest,tag,comm,ierr) 3.2.4 MPI_Bsend 缓冲区阻塞发送 : 应用程序首先应申请一个足够大的缓冲区, 然后用 MPI_Buffer_attach 函数加以确认, 当 MPI_Bsend 函数返回时, 消息数据已经从应用程序发送缓冲区拷贝到分配的缓冲区中 MPI_Bsend (*buf,count,datatype,dest,tag,comm) MPI_BSEND (buf,count,datatype,dest,tag,comm,ierr) 3.2.5 MPI_Buffer_attach MPI_Buffer_detach 用于分配和释放用于 MPI_Bsend 函数的发送缓冲区,size 参数是以字节为计的缓冲区大小 : MPI_Buffer_attach (*buffer,size) MPI_Buffer_detach (*buffer,size) MPI_BUFFER_ATTACH (buffer,size,ierr) MPI_BUFFER_DETACH (buffer,size,ierr) 14

3.2.6 MPI_Rsend 预备方式的阻塞发送 如果能确认接收进程已经开始匹配接收时, 可以使用该发送函数 : MPI_Rsend (*buf,count,datatype,dest,tag,comm) MPI_RSEND (buf,count,datatype,dest,tag,comm,ierr) 3.2.7 MPI_Sendrecv 阻塞发送并阻塞接收一个消息 只有当发送缓冲区空并接收缓冲区消息有效后该函数才返回 : MPI_Sendrecv (*sendbuf,sendcount,sendtype,dest,sendtag, *recvbuf,recvcount,recvtype,source,recvtag, comm,*status) MPI_SENDRECV (sendbuf,sendcount,sendtype,dest,sendtag, recvbuf,recvcount,recvtype,source,recvtag, comm,status,ierr) 3.2.8 MPI_Wait MPI_Waitany MPI_Waitall MPI_Waitsome MPI_Wait 函数只有当指定的消息发送或接收完成后才返回, 对于多个非阻塞的操作, 程序员可以指定任何一个或一些或全部的消息发送或接收完成后再返回 : MPI_Wait (*request,*status) MPI_Waitany (count,*array_of_requests,*index,*status) MPI_Waitall (count,*array_of_requests,*array_of_statuses) MPI_Waitsome (incount,*array_of_requests,*outcount, array_of_offsets, *array_of_statuses) MPI_WAIT (request,status,ierr) MPI_WAITANY (count,array_of_requests,index,status,ierr) MPI_WAITALL (count,array_of_requests,array_of_statuses, ierr) MPI_WAITSOME (incount,array_of_requests,outcount, array_of_offsets, array_of_statuses,ierr) 3.2.9 MPI_Probe 该函数探测一个消息是否完成接收, 只有当探测到消息接收完成后才返回 : MPI_Probe (source,tag,comm,*status) MPI_PROBE (source,tag,comm,status,ierr) 15

3.3 阻塞消息传递例子 3.3.1 C 程序例子 #include "mpi.h" #include <stdio.h> int main(argc,argv) int argc; char *argv[]; { int numtasks, rank, dest, source, rc, tag=1; char inmsg, outmsg='x'; MPI_Status Stat; MPI_Init(&argc,&argv); MPI_Comm_size(MPI_COMM_WORLD, &numtasks); MPI_Comm_rank(MPI_COMM_WORLD, &rank); if (rank == 0) { dest = 1; source = 1; rc = MPI_Send(&outmsg, 1, MPI_CHAR, dest, tag, MPI_COMM_WORLD); rc = MPI_Recv(&inmsg, 1, MPI_CHAR, source, tag, MPI_COMM_WORLD, &Stat); else if (rank == 1) { dest = 0; source = 0; rc = MPI_Recv(&inmsg, 1, MPI_CHAR, source, tag, MPI_COMM_WORLD, &Stat); rc = MPI_Send(&outmsg, 1, MPI_CHAR, dest, tag, MPI_COMM_WORLD); MPI_Finalize(); 3.3.2 Fortran 程序例子 program ping include 'mpif.h' integer numtasks, rank, dest, source, tag, ierr 16

integer stat(mpi_status_size) character inmsg, outmsg tag = 1 call MPI_INIT(ierr) call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr) call MPI_COMM_SIZE(MPI_COMM_WORLD, numtasks, ierr) & & & & if (rank.eq. 0) then dest = 1 source = 1 outmsg = 'x' call MPI_SEND(outmsg, 1, MPI_CHARACTER, dest, tag, MPI_COMM_WORLD, ierr) call MPI_RECV(inmsg, 1, MPI_CHARACTER, source, tag, MPI_COMM_WORLD, stat, ierr) else if (rank.eq. 1) then dest = 0 source = 0 call MPI_RECV(inmsg, 1, MPI_CHARACTER, source, tag, MPI_COMM_WORLD, stat, err) call MPI_SEND(outmsg, 1, MPI_CHARACTER, dest, tag, MPI_COMM_WORLD, err) endif call MPI_FINALIZE(ierr) end 3.4 非阻塞消息通信函数 这里介绍一些最主要的非阻塞消息通信函数 3.4.1 MPI_Isend 非阻塞发送, 该函数调用后立即返回, 并回送一个发送消息句柄, 发送的消息还在应用程序缓冲区中, 应用程序不能再使用该发送缓冲区, 直到完成 MPI_Wait 或 MPI_Test 函数调用后 : MPI_Isend (*buf,count,datatype,dest,tag,comm,*request) MPI_ISEND (buf,count,datatype,dest,tag,comm,request,ierr) 17

3.4.2 MPI_Irecv 非阻塞接收, 该调用返回后, 所要接收的消息未必到本地接收缓冲区中, 直到完成 MPI_Wait 或 MPI_Test 函数调用后, 该接收缓冲区的消息才有效 : MPI_Irecv (*buf,count,datatype,source,tag,comm,*request) MPI_IRECV (buf,count,datatype,source,tag,comm,request,ierr) 3.4.3 MPI_Issend 同步方式的无阻塞发送, 类似于 MPI_Isend() 函数去掉 MPI_Wait() 或 MPI_Test() 调用 : MPI_Issend (*buf,count,datatype,dest,tag,comm,*request) MPI_ISSEND (buf,count,datatype,dest,tag,comm,request,ierr) 3.4.4 MPI_Ibsend 非阻塞缓冲区发送, 类似于 MPI_Bsend() 函数去掉 MPI_Wait() 或 MPI_Test() 调用 : MPI_Ibsend (*buf,count,datatype,dest,tag,comm,*request) MPI_IBSEND (buf,count,datatype,dest,tag,comm,request,ierr) 3.4.5 MPI_Irsend 预备方式的非阻塞发送, 类似于 MPI_Rsend() 函数去掉 MPI_Wait() 或 MPI_Test() 调用, 只有确认接收进程已经开始匹配接收时才可以调用该函数 : MPI_Irsend (*buf,count,datatype,dest,tag,comm,*request) MPI_IRSEND (buf,count,datatype,dest,tag,comm,request,ierr) 3.4.6 MPI_Test MPI_Testany MPI_Testall MPI_Testsome MPI_Test 函数用于检测非阻塞发送或接收的消息是否完成, 完成则返回 true(1), 否则返回 false(0), 对于多个非阻塞操作, 可以用 MPI_Testany 或 MPI_Testall 或 MPI_Testsome 调用来检测是否有任一个或所有或一些操作完成 : MPI_Test (*request,*flag,*status) MPI_Testany (count,*array_of_requests,*index,*flag,*status) MPI_Testall (count,*array_of_requests,*flag,*array_of_statuses) MPI_Testsome (incount,*array_of_requests,*outcount,*array_of_offsets, *array_of_statuses) MPI_TEST (request,flag,status,ierr) MPI_TESTANY (count,array_of_requests,index,flag,status,ierr) MPI_TESTALL (count,array_of_requests,flag,array_of_statuses,ierr) MPI_TESTSOME (incount,array_of_requests,outcount, array_of_offsets, array_of_statuses,ierr) 18

3.4.7 MPI_Iprobe 0: 该函数探测一个消息接收是否完成, 并立即返回, 如果接收成功参数 falg 为 1, 否则为 MPI_Iprobe (source,tag,comm,*flag,*status) MPI_IPROBE (source,tag,comm,flag,status,ierr) 3.5 非阻塞消息传递例子 3.5.1 C 语言例子 #include "mpi.h" #include <stdio.h> int main(argc,argv) int argc; char *argv[]; { int numtasks, rank, next, prev, buf[2], tag1=1, tag2=2; MPI_Request reqs[4]; MPI_Status stats[4]; MPI_Init(&argc,&argv); MPI_Comm_size(MPI_COMM_WORLD, &numtasks); MPI_Comm_rank(MPI_COMM_WORLD, &rank); prev = rank-1; next = rank+1; if (rank == 0) prev = numtasks - 1; if (rank == (numtasks - 1)) next = 0; MPI_Irecv(&buf[0], 1, MPI_INT, prev, tag1, MPI_COMM_WORLD, &reqs[0]); MPI_Irecv(&buf[1], 1, MPI_INT, next, tag2, MPI_COMM_WORLD, &reqs[1]); MPI_Isend(&rank, 1, MPI_INT, prev, tag2, MPI_COMM_WORLD, &reqs[2]); MPI_Isend(&rank, 1, MPI_INT, next, tag1, MPI_COMM_WORLD, &reqs[3]); MPI_Waitall(4, reqs, stats); MPI_Finalize(); 19

3.5.2 Fortran 语言例子 program ringtopo include 'mpif.h' integer numtasks, rank, next, prev, buf(2), tag1, tag2, ierr integer stats(mpi_status_size,4), reqs(4) tag1 = 1 tag2 = 2 call MPI_INIT(ierr) call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr) call MPI_COMM_SIZE(MPI_COMM_WORLD, numtasks, ierr) prev = rank - 1 next = rank + 1 if (rank.eq. 0) then prev = numtasks - 1 endif if (rank.eq. numtasks - 1) then next = 0 endif & ierr) & ierr) & ierr) & ierr) call MPI_IRECV(buf(1), 1, MPI_INTEGER, prev, tag1, MPI_COMM_WORLD, reqs(1), call MPI_IRECV(buf(2), 1, MPI_INTEGER, next, tag2, MPI_COMM_WORLD, reqs(2), call MPI_ISEND(rank, 1, MPI_INTEGER, prev, tag2, MPI_COMM_WORLD, reqs(3), call MPI_ISEND(rank, 1, MPI_INTEGER, next, tag1, MPI_COMM_WORLD, reqs(4), call MPI_WAITALL(4, reqs, stats, ierr); call MPI_FINALIZE(ierr) end 20

第四章集合通信函数 l 集合通信是包含在通信因子中的所有进程都参加操作, 当通信因子为 MPI_COMM_WORLD 时, 应用程序中所有进程都参加 l 集合操作的三种类型 : Ø 同步 (Barrier): 集合中所有进程都到达后, 每个进程再接着运行 ; Ø 数据传递 : 广播 (Broadcast) 分散(Scatter) 收集(Gather) 全部到全部(Alltoall); Ø 归约 (Reduction): 集合中的其中一个进程收集所有进程的数据并计算 ( 如 : 求最大值 求最小值 加 乘等 ); l 集合操作是阻塞的 l 集合操作不带标识符 (tag) 参数 l 只支持 MPI 标准的数据类型, 不支持派生数据类型 4.1 集合通信函数 4.1.1 MPI_Barrier 在组中建立一个同步栅栏 当每个进程都到达 MPI_Barrier 调用后, 程序才接着往下执行 : MPI_Barrier (comm) MPI_BARRIER (comm,ierr) 4.1.2 MPI_Bcast 从指定的一个根进程中把数据广播发送给组中的所有其它进程 : 21

MPI_Bcast (*buffer,count,datatype,root,comm) MPI_BCAST (buffer,count,datatype,root,comm,ierr) 4.1.3 MPI_Scatter 把指定的根进程中的数据分散发送给组中的所有进程 ( 包括自己 ): MPI_Scatter (*sendbuf,sendcnt,sendtype,*recvbuf, recvcnt,recvtype,root,comm) MPI_SCATTER (sendbuf,sendcnt,sendtype,recvbuf, recvcnt,recvtype,root,comm,ierr) 4.1.4 MPI_Gather 在组中指定一个进程收集组中所有进程发送来的消息, 这个函数操作与 MPI_Scatter 函数操作相反 : 22

MPI_Gather (*sendbuf,sendcnt,sendtype,*recvbuf, recvcount,recvtype,root,comm) MPI_GATHER (sendbuf,sendcnt,sendtype,recvbuf, recvcount,recvtype,root,comm,ierr) 4.1.5 MPI_Allgather 在组中的每个进程都收集组中所有进程发送来的消息 : MPI_Allgather (*sendbuf,sendcount,sendtype,*recvbuf, recvcount,recvtype,comm) MPI_ALLGATHER (sendbuf,sendcount,sendtype,recvbuf,recvcount,recvtype,comm,info) 4.1.6 MPI_Reduce 在组内所有的进程中, 执行一个归约操作, 并把结果存放在指定的一个进程中 : MPI_Reduce (*sendbuf,*recvbuf,count,datatype,op,root,comm) MPI_REDUCE (sendbuf,recvbuf,count,datatype,op,root,comm,ierr) MPI 缺省定义了如下的归约操作, 用户可根据自己的需要用 MPI_Op_create 函数创建新 的归约操作 : MPI 归约操作 C 语言数据类型 Fortran 语言数据类型 MPI_MAX MPI_MIN MPI_SUM MPI_PROD MPI_LAND 求最大值求最小值和乘积逻辑与 integer, float integer, float integer, float integer, float integer integer, real, complex integer, real, complex integer, real, complex integer, real, complex logical MPI_BAND 按位与 integer, MPI_BYTE integer, MPI_BYTE MPI_LOR 逻辑或 integer logical MPI_BOR 按位或 integer, MPI_BYTE integer, MPI_BYTE MPI_LXOR 逻辑异或 integer logical MPI_BXOR 按位异或 integer, MPI_BYTE integer, MPI_BYTE MPI_MAXLOC 最大值和存储单元 float, double and long double real, complex,double precision 23

MPI_MINLOC 最小值和存储单元 float, double and long double real, complex,double precision 4.1.7 MPI_Allreduce 执行一个归约操作, 并把结果广播到组内的所有进程中 该操作等价于先执行 MPI_Reduce 操作, 然后再执行 MPI_Bcast: MPI_Allreduce (*sendbuf,*recvbuf,count,datatype,op,comm) MPI_ALLREDUCE (sendbuf,recvbuf,count,datatype,op,comm,ierr) 4.1.8 MPI_Reduce_scatter 在组内按元素单位先进行归约操作, 然后把结果分段后分布到组内的其它进程上 该操作等价于先执行 MPI_Reduce 函数, 然后再执行 MPI_ Scatter 函数 : MPI_Reduce_scatter (*sendbuf,*recvbuf,recvcount,datatype, op,comm) MPI_REDUCE_SCATTER (sendbuf,recvbuf,recvcount,datatype, op,comm,ierr) 4.1.9 MPI_Alltoall 组中每个进程执行一个发散操作, 发送不同的消息到组内的所有进程上 ( 包括自己 ): MPI_Alltoall (*sendbuf,sendcount,sendtype,*recvbuf, recvcnt,recvtype,comm) MPI_ALLTOALL (sendbuf,sendcount,sendtype,recvbuf, recvcnt,recvtype,comm,ierr) 4.1.10MPI_Scan 用来对分布在进程组上的数据执行前缀归约 : 24

MPI_Scan (*sendbuf,*recvbuf,count,datatype,op,comm) MPI_SCAN (sendbuf,recvbuf,count,datatype,op,comm,ierr) 25

4.2 集合通信操作例子 4.2.1 C 语言例子 #include "mpi.h" #include <stdio.h> #define SIZE 4 int main(argc,argv) int argc; char *argv[]; { int numtasks, rank, sendcount, recvcount, source; float sendbuf[size][size] = { {1.0, 2.0, 3.0, 4.0, {5.0, 6.0, 7.0, 8.0, {9.0, 10.0, 11.0, 12.0, {13.0, 14.0, 15.0, 16.0 ; float recvbuf[size]; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &numtasks); if (numtasks == SIZE) { source = 1; 26

sendcount = SIZE; recvcount = SIZE; MPI_Scatter(sendbuf,sendcount,MPI_FLOAT,recvbuf,recvcount, MPI_FLOAT,source,MPI_COMM_WORLD); printf("rank= %d Results: %f %f %f %f\n",rank,recvbuf[0], recvbuf[1],recvbuf[2],recvbuf[3]); else printf("must specify %d processors. Terminating.\n",SIZE); MPI_Finalize(); 4.2.2 Fortran 语言例子 program scatter include 'mpif.h' integer SIZE parameter(size=4) integer numtasks, rank, sendcount, recvcount, source, ierr real*4 sendbuf(size,size), recvbuf(size) C Fortran stores this array in column major order, so the C scatter will actually scatter columns, not rows. data sendbuf /1.0, 2.0, 3.0, 4.0, & 5.0, 6.0, 7.0, 8.0, & 9.0, 10.0, 11.0, 12.0, & 13.0, 14.0, 15.0, 16.0 / call MPI_INIT(ierr) call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr) call MPI_COMM_SIZE(MPI_COMM_WORLD, numtasks, ierr) recvbuf, & if (numtasks.eq. SIZE) then source = 1 sendcount = SIZE recvcount = SIZE call MPI_SCATTER(sendbuf, sendcount, MPI_REAL, else endif recvcount, MPI_REAL, source, MPI_COMM_WORLD, ierr) print *, 'rank= ',rank,' Results: ',recvbuf print *, 'Must specify',size,' processors. Terminating.' 27

call MPI_FINALIZE(ierr) end 4.2.3 例子输出结果 rank= 0 Results: 1.000000 2.000000 3.000000 4.000000 rank= 1 Results: 5.000000 6.000000 7.000000 8.000000 rank= 2 Results: 9.000000 10.000000 11.000000 12.000000 rank= 3 Results: 13.000000 14.000000 15.000000 16.000000 28

第五章派生数据类型 l MPI 预定义的基本数据类型 : MPI C 语言数据类型 MPI_CHAR MPI_SHORT MPI_INT MPI_LONG MPI_UNSIGNED_CHAR MPI_UNSIGNED_SHORT MPI_UNSIGNED MPI_UNSIGNED_LONG MPI_FLOAT MPI_DOUBLE MPI_LONG_DOUBLE MPI_BYTE MPI_PACKED MPI Fortran 语言数据类型 MPI_CHARACTER MPI_INTEGER MPI_REAL MPI_DOUBLE_PRECISION MPI_COMPLEX MPI_LOGICAL MPI_BYTE MPI_PACKED l l MPI 的基本数据类型是连续的, 派生数据类型允许定义非连续的数据类型 MPI 提供了基于基本数据类型创建新的派生数据类型的方法 5.1 派生数据类型函数 MPI 中的派生数据类型允许将不连续的数据元素组合在一起, 这种派生数据类型可以使用构造函数构造, 这些派生数据函数包括 : l MPI_TYPE_CONTIGUOUS: 它用来定义由一个或多个连续数据元素组成的数据类型 ; l MPI_TYPE_VECTOR: 它用来定义由一个或多个成块数据元素组成的类型, 这些成块的数据元素是由数组中的恒间距所分开 ; l MPI_TYPE_INDEXED: 它用来定义由一个或多个基本的或先前已定义数据类型的数据块组成的类型, 其中块的长度和块间位移量由数组指定 ; l MPI_TYPE_COMMIT: 它用来提交数据类型, 必须在使用导出数据类型之前使用 5.1.1 MPI_Type_contiguous 最简单的数据类型构造函数 它构造的类型映象包括将一个数据类型复制到连续的位置 : MPI_Type_contiguous (count,oldtype,*newtype) 29

MPI_TYPE_CONTIGUOUS (count,oldtype,newtype,ierr) 5.1.2 MPI_Type_vector MPI_Type_hvector 允许将一个数据类型复制到由相等间隔块组成的位置的构造函数 每个块通过连接旧数据类型的相同拷贝数而得到 块之间的跨度是旧数据类型长度的倍数 ;MPI_TYPE_HVECTOR 等同于 MPI_TYPE_VECTOR, 除了跨度是用字节而不是用元素度量外 : MPI_Type_vector (count,blocklength,stride,oldtype,*newtype) MPI_TYPE_VECTOR (count,blocklength,stride,oldtype,newtype,ierr) MPI_TYPE_VECTORO 数据类型构造函数 MPI_TYPE_HVECTOR 数据类型构造函数 5.1.3 MPI_Type_indexed MPI_Type_hindexed MPI_Type_indexed 将一个旧的数据类型复制到一系列块中 ( 其中每个块都是旧数据类型的连接 ), 每个块可以含有不同数目的旧数据类型的拷贝, 并且位移也不相同 所有块的位移按旧数据类型的长度为单位度量 ;MPI_TYPE_HINDEXED 等价于 MPI_TYPE_INDEXED, 除了块位移是用字节计而不是用 oldtype 长度的倍数来规定外 : MPI_Type_indexed (count,blocklens[],offsets[],old_type,*newtype) MPI_TYPE_INDEXED (count,blocklens(),offsets(),old_type,newtype,ierr) 30

MPI_TYPE_INDEXED 数据类型构造函数 MPI_TYPE_HINDEXED 数据类型构造函数 5.1.4 MPI_Type_struct MPI_TYPE_STRUCT 是最通用的类型构造函数 它将 MPI_PYPE_HINDEXED 进一步通用化, 使之允许每个块含有不同数据类型的复制 其意图是允许将数组结构作为单个数据类型来描述 : MPI_Type_struct (count,blocklens[],offsets[],old_types,*newtype) MPI_TYPE_STRUCT (count,blocklens(),offsets(),old_types,newtype,ierr) MPI_TYPE_STRUCT 数据类型构造函数 5.1.5 MPI_Type_extent MPI_TYPE_EXTENT 返回一个数据类型的长度 该函数除了用于派生数据类型外, 还可以用来查询原始数据类型的长度 例如,MPI_TYPE_EXTENT(MPI_INT,extent) 将在 extent 中返回 int 的字节数, 由 C 返回的相同值称为 Sizeof(int) MPI_Type_extent (datatype,*extent) MPI_TYPE_EXTENT (datatype,extent,ierr) 5.1.6 MPI_Type_commit MPI_TYPE_COMMIT 提交数据类型 使系统承认该数据类型的存在, 使之有效 数据类型 31

被提交后, 可以重复用于不同数据的通信 MPI_Type_commit (datatype) MPI_TYPE_COMMIT (datatype,ierr) 5.2 连续数据类型的例子 构造一个新数据类型, 把一矩阵中的行当成一个数据类型, 分布到进程组中的其它进程上 : 5.2.1 C 语言例子 #include "mpi.h" #include <stdio.h> #define SIZE 4 int main(argc,argv) int argc; char *argv[]; { int numtasks, rank, source=0, dest, tag=1, i; float a[size][size] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0; float b[size]; MPI_Status stat; MPI_Datatype rowtype; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &numtasks); MPI_Type_contiguous(SIZE, MPI_FLOAT, &rowtype); MPI_Type_commit(&rowtype); if (numtasks == SIZE) { if (rank == 0) { for (i=0; i<numtasks; i++) MPI_Send(&a[i][0], 1, rowtype, i, tag, MPI_COMM_WORLD); MPI_Recv(b, SIZE, MPI_FLOAT, source, tag, MPI_COMM_WORLD, &stat); printf("rank= %d b= %3.1f %3.1f %3.1f%3.1f\n", rank,b[0],b[1],b[2],b[3]); 32

else printf("must specify %d processors. Terminating.\n",SIZE); MPI_Finalize(); 5.2.2 Fortran 语言例子 program contiguous include 'mpif.h' integer SIZE parameter(size=4) integer numtasks, rank, source, dest, tag, i, ierr real*4 a(0:size-1,0:size-1), b(0:size-1) integer stat(mpi_status_size), columntype C Fortran stores this array in column major order data a /1.0, 2.0, 3.0, 4.0, & 5.0, 6.0, 7.0, 8.0, & 9.0, 10.0, 11.0, 12.0, & 13.0, 14.0, 15.0, 16.0 / call MPI_INIT(ierr) call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr) call MPI_COMM_SIZE(MPI_COMM_WORLD, numtasks, ierr) call MPI_TYPE_CONTIGUOUS(SIZE, MPI_REAL, columntype, ierr) call MPI_TYPE_COMMIT(columntype, ierr) tag = 1 if (numtasks.eq. SIZE) then if (rank.eq. 0) then do 10 i=0, numtasks-1 call MPI_SEND(a(0,i), 1, columntype, i, tag, MPI_COMM_WORLD,ierr) 10 continue endif source = 0 call MPI_RECV(b, SIZE, MPI_REAL, source, tag, & MPI_COMM_WORLD, stat, ierr) print *,'rank=',rank,' b= ',b else print *, 'Must specify',size,' processors. Terminating.' endif call MPI_FINALIZE(ierr) end 33

5.2.3 例子输出结果 rank= 0 b= 1.0 2.0 3.0 4.0 rank= 1 b= 5.0 6.0 7.0 8.0 rank= 2 b= 9.0 10.0 11.0 12.0 rank= 3 b= 13.0 14.0 15.0 16.0 5.3 向量派生数据类型例子 构造一个新数据类型, 把一矩阵中的列看作一数据类型, 分布到进程组中的其它进程上 : 5.3.1 C 语言例子 #include "mpi.h" #include <stdio.h> #define SIZE 4 int main(argc,argv) int argc; char *argv[]; { int numtasks, rank, source=0, dest, tag=1, i; float a[size][size] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 34

9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0; float b[size]; MPI_Status stat; MPI_Datatype columntype; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &numtasks); MPI_Type_vector(SIZE, 1, SIZE, MPI_FLOAT, &columntype); MPI_Type_commit(&columntype); if (numtasks == SIZE) { if (rank == 0) { for (i=0; i<numtasks; i++) MPI_Send(&a[0][i], 1, columntype, i, tag, MPI_COMM_WORLD); MPI_Recv(b, SIZE, MPI_FLOAT, source, tag, MPI_COMM_WORLD, &stat); printf("rank= %d b= %3.1f %3.1f %3.1f%3.1f\n", rank,b[0],b[1],b[2],b[3]); else printf("must specify %d processors. Terminating.\n",SIZE); MPI_Finalize(); 5.3.2 Fortran 语言例子 program vector include 'mpif.h' integer SIZE parameter(size=4) integer numtasks, rank, source, dest, tag, i, real*4 a(0:size-1,0:size-1), b(0:size-1) integer stat(mpi_status_size), rowtype ierr C Fortran stores this array in column major order data a /1.0, 2.0, 3.0, 4.0, & 5.0, 6.0, 7.0, 8.0, & 9.0, 10.0, 11.0, 12.0, & 13.0, 14.0, 15.0, 16.0 / call MPI_INIT(ierr) 35

ierr) call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr) call MPI_COMM_SIZE(MPI_COMM_WORLD, numtasks, ierr) call MPI_TYPE_VECTOR(SIZE, 1, SIZE, MPI_REAL, rowtype, call MPI_TYPE_COMMIT(rowtype, ierr) tag = 1 if (numtasks.eq. SIZE) then if (rank.eq. 0) then do 10 i=0, numtasks-1 call MPI_SEND(a(i,0), 1, rowtype, i, tag, & MPI_COMM_WORLD, ierr) 10 continue endif source = 0 call MPI_RECV(b, SIZE, MPI_REAL, source, tag, & MPI_COMM_WORLD, stat, ierr) print *,'rank=',rank,' b= ',b else print *, 'Must specify',size,' processors. Terminating.' endif call MPI_FINALIZE(ierr) end 5.3.3 程序输出结果 rank= 0 b= 1.0 5.0 9.0 13.0 rank= 1 b= 2.0 6.0 10.0 14.0 rank= 2 b= 3.0 7.0 11.0 15.0 rank= 3 b= 4.0 8.0 12.0 16.0 36

5.4 索引派生数据类型例子 构造一数据类型, 抽取矩阵中的某些元素发散到进程组其它进程上 : 5.4.1 C 语言例子 #include "mpi.h" #include <stdio.h> #define NELEMENTS 6 int main(argc,argv) int argc; char *argv[]; { int numtasks, rank, source=0, dest, tag=1, i; int blocklengths[2], displacements[2]; float a[16] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0; 37

float b[nelements]; MPI_Status stat; MPI_Datatype indextype; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &numtasks); blocklengths[0] = 4; blocklengths[1] = 2; displacements[0] = 5; displacements[1] = 12; MPI_Type_indexed(2, blocklengths, displacements, MPI_FLOAT, &indextype); MPI_Type_commit(&indextype); if (rank == 0) { for (i=0; i<numtasks; i++) MPI_Send(a, 1, indextype, i, tag, MPI_COMM_WORLD); MPI_Recv(b, NELEMENTS, MPI_FLOAT, source, tag, MPI_COMM_WORLD, &stat); printf("rank= %d b= %3.1f %3.1f %3.1f %3.1f %3.1f %3.1f\n", rank,b[0],b[1],b[2],b[3],b[4],b[5]); MPI_Finalize(); 5.4.2 Fortran 语言例子 program indexed include 'mpif.h' integer NELEMENTS parameter(nelements=6) integer numtasks, rank, source, dest, tag, i, ierr integer blocklengths(0:1), displacements(0:1) real*4 a(0:15), b(0:nelements-1) integer stat(mpi_status_size), indextype data a /1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, & 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0 / call MPI_INIT(ierr) call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr) call MPI_COMM_SIZE(MPI_COMM_WORLD, numtasks, ierr) blocklengths(0) = 4 blocklengths(1) = 2 displacements(0) = 5 38

displacements(1) = 12 call MPI_TYPE_INDEXED(2, blocklengths, displacements, MPI_REAL, & indextype, ierr) call MPI_TYPE_COMMIT(indextype, ierr) tag = 1 if (rank.eq. 0) then do 10 i=0, numtasks-1 call MPI_SEND(a, 1, indextype, i, tag, MPI_COMM_WORLD, ierr) 10 continue endif source = 0 call MPI_RECV(b, NELEMENTS, MPI_REAL, source, tag, & MPI_COMM_WORLD, stat, ierr) print *, 'rank= ',rank,' b= ',b call MPI_FINALIZE(ierr) end 5.4.3 例子输出结果 rank= 0 b= 6.0 7.0 8.0 9.0 13.0 14.0 rank= 1 b= 6.0 7.0 8.0 9.0 13.0 14.0 rank= 2 b= 6.0 7.0 8.0 9.0 13.0 14.0 rank= 3 b= 6.0 7.0 8.0 9.0 13.0 14.0 39

5.5 结构派生数据类型例子 创建一结构数据类型, 把用户定义的结构数据发送到进程组中的所有进程上 : 5.5.1 C 语言例子 #include "mpi.h" #include <stdio.h> #define NELEM 25 int main(argc,argv) int argc; char *argv[]; { int numtasks, rank, source=0, dest, tag=1, i; typedef struct { float x, y, z; float velocity; int n, type; Particle; Particle p[nelem], particles[nelem]; MPI_Datatype particletype, oldtypes[2]; int blockcounts[2]; /* MPI_Aint type used to be consistent with syntax of */ /* MPI_Type_extent routine */ MPI_Aint offsets[2], extent; MPI_Status stat; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &numtasks); /* Setup description of the 4 MPI_FLOAT fields x, y, z, velocity */ offsets[0] = 0; oldtypes[0] = MPI_FLOAT; blockcounts[0] = 4; /* Setup description of the 2 MPI_INT fields n, type */ /* Need to first figure offset by getting size of MPI_FLOAT */ MPI_Type_extent(MPI_FLOAT, &extent); offsets[1] = 4 * extent; oldtypes[1] = MPI_INT; 40

blockcounts[1] = 2; /* Now define structured type and commit it */ MPI_Type_struct(2, blockcounts, offsets, oldtypes, &particletype); MPI_Type_commit(&particletype); /* Initialize the particle array and then send it to each task */ if (rank == 0) { for (i=0; i<nelem; i++) { particles[i].x = i * 1.0; particles[i].y = i *-1.0; particles[i].z = i * 1.0; particles[i].velocity = 0.25; particles[i].n = i; particles[i].type = i % 2; for (i=0; i<numtasks; i++) MPI_Send(particles, NELEM, particletype, i, tag, MPI_COMM_WORLD); MPI_Recv(p, NELEM, particletype, source, tag, MPI_COMM_WORLD, &stat); /* Print a sample of what was received */ printf("rank= %d %3.2f %3.2f %3.2f %3.2f %d %d\n", rank,p[3].x, p[3].y,p[3].z,p[3].velocity,p[3].n,p[3].type); MPI_Finalize(); 5.5.2 Fortran 语言例子 program struct include 'mpif.h' integer NELEM parameter(nelem=25) integer numtasks, rank, source, dest, tag, i, integer stat(mpi_status_size) type Particle sequence real*4 x, y, z, velocity integer n, type end type Particle ierr type (Particle) p(nelem), particles(nelem) 41

integer particletype, oldtypes(0:1), blockcounts(0:1), offsets(0:1), extent call MPI_INIT(ierr) call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr) call MPI_COMM_SIZE(MPI_COMM_WORLD, numtasks, ierr) C C C C particletype, ierr) Setup description of the 4 MPI_REAL fields x, y, z, velocity offsets(0) = 0 oldtypes(0) = MPI_REAL blockcounts(0) = 4 Setup description of the 2 MPI_INTEGER fields n, type Need to first figure offset by getting size of MPI_REAL call MPI_TYPE_EXTENT(MPI_REAL, extent, ierr) offsets(1) = 4 * extent oldtypes(1) = MPI_INTEGER blockcounts(1) = 2 Now define structured type and commit it call MPI_TYPE_STRUCT(2, blockcounts, offsets, oldtypes, call MPI_TYPE_COMMIT(particletype, ierr) C Initialize the particle array and then send it to each task tag = 1 if (rank.eq. 0) then do 10 i=0, NELEM-1 particles(i) = Particle ( 1.0*i, -1.0*i, 1.0*i, 0.25, i, mod(i,2) ) 10 continue do 20 i=0, numtasks-1 call MPI_SEND(particles, NELEM, particletype, i, tag, & MPI_COMM_WORLD, ierr) 20 continue endif & ierr) source = 0 call MPI_RECV(p, NELEM, particletype, source, tag, MPI_COMM_WORLD, stat, print *, 'rank= ',rank,' p(3)= ',p(3) call MPI_FINALIZE(ierr) end 42

5.5.3 例子输出结果 rank= 0 3.00-3.00 3.00 0.25 3 1 rank= 2 3.00-3.00 3.00 0.25 3 1 rank= 1 3.00-3.00 3.00 0.25 3 1 rank= 3 3.00-3.00 3.00 0.25 3 1 43

第六章组和通信因子管理函数 l 组是一组有秩序的进程的集合, 组中的每一个进程通过唯一的整数秩相关联, 秩的值从 0~N-1(N 为组中的进程数 ) 在 MPI 中, 组在内存中是以一个对象的方式存在着, 并且组总和一个通信因子相关联 l 通信因子包含一个相互之间通信的进程组, 所有的 MPI 通信必须指定一个通信因子 系统缺省的通信因子为 MPI_COMM_WORLD l 对程序员来说, 组和通信因子是一样的, 组函数主要用来指定那些进程可以创建一通信因子 l 使用组和通信因子的主要目的是 : A. 通过一些函数, 允许组织一些进程, 形成新的进程组 B. 允许只在相关的进程间执行集合通讯操作 C. 提供基本的工具来定义虚拟拓扑结构 D. 提供可靠的通信 l 组和通信因子是动态地, 它可以在程序运行过程中创建或释放 l 一个进程可以参加多个组或通信因子, 但它们在组或通信因子中的秩不一定一样 l MPI 提供关于组 通信因子 虚拟拓扑的函数将近 40 多个 l 典型用法 : 1) 使用 MPI_Comm_group 函数从 MPI_COMM_WORLD 因子中提取全局组的句柄 ; 2) 使用 MPI_Group_incl 函数创建一个新组 ( 全局组的子组 ); 3) 根据新组, 使用 MPI_Comm_create 函数创建一个新的通信因子 ; 4) 使用 MPI_Comm_rank 函数确定某一进程在该组中的秩 ; 5) 使用任何 MPI 消息函数使用该新的通信因子 ; 6) 当使用结束后, 用 MPI_Comm_free 和 MPI_Group_free 释放通信因子和组 ; 44

6.1 组和通信因子管理函数 6.1.1 MPI_Comm_group 返回与指定通信因子相关联的组的句柄 : MPI_Comm_group (comm,*group) MPI_COMM_GROUP (comm,group,ierr) 6.1.2 MPI_Group_rank 返回该进程在指定组中的秩, 如果返回值为 MPI_UNDEFINED, 则标识该进程不在该组内 : MPI_Group_rank (group,*rank) MPI_GROUP_RANK (group,rank,ierr) 6.1.3 MPI_Group_size 返回该组中的进程数 : MPI_Group_size (group,*size) MPI_GROUP_SIZE (group,size,ierr) 6.1.4 MPI_Group_excl 从指定的组中, 淘汰一些进程后建立一新组 : MPI_Group_excl (group,n,*ranks,*newgroup) MPI_GROUP_EXCL (group,n,ranks,newgroup,ierr) 6.1.5 MPI_Group_incl 从指定的组中, 提取一些进程产生一新组 : MPI_Group_incl (group,n,*ranks,*newgroup) MPI_GROUP_INCL (group,n,ranks,newgroup,ierr) 6.1.6 MPI_Group_intersection 产生的新组是两个组的交集进程 : MPI_Group_intersection (group1,group2,*newgroup) MPI_GROUP_INTERSECTION (group1,group2,newgroup,ierr) 45

6.1.7 MPI_Group_union 产生的新组是两个组的联合 : MPI_Group_union (group1,group2,*newgroup) MPI_GROUP_UNION (group1,group2,newgroup,ierr) 6.1.8 MPI_Group_difference 产生的组是两个组中的不同进程组成的 : MPI_Group_difference (group1,group2,*newgroup) MPI_GROUP_DIFFERENCE (group1,group2,newgroup,ierr) 6.1.9 MPI_Group_compare 比较两个组, 如果两个组中的成员和秩序都一样则返回 MPI_IDENT, 如果只是成员一样则返回 MPI_SIMILAR, 否则返回 MPI_UNEQUAL: MPI_Group_compare (group1,group2,*result) MPI_GROUP_COMPARE (group1,group2,result,ierr) 6.1.10MPI_Group_free 释放一个用户创建的组 : MPI_Group_free (group) MPI_GROUP_FREE (group,ierr) 6.1.11MPI_Comm_create 该函数根据组和通信因子, 创建一个新的通信因子 : MPI_Comm_create (comm,group,*newcomm) MPI_COMM_CREATE (comm,group,newcomm,ierr) 6.1.12MPI_Comm_dup 复制一个新的通信因子 : MPI_Comm_dup (comm,*newcomm) MPI_COMM_DUP (comm,newcomm,ierr) 46

6.1.13MPI_Comm_compare 该函数比较两个通信因子, 找出两个组内通信因子之间的关系 如果两个通信因子中的上下文关系和组都一样, 则返回 MPI_IDENT, 如果上下文关系相同, 而组不相同则返回 MPI_CONGRUENT, 如果上下文关系不相同, 而组相同则返回 MPI_SIMILAR 否则结果为 MPI_UNEQUEL: MPI_Comm_compare (comm1,comm2,*result) MPI_COMM_COMPARE (comm1,comm2,result,ierr) 6.1.14MPI_Comm_free 释放用户创建地通信因子 : MPI_Comm_free (*comm) MPI_COMM_FREE (comm,ierr) 6.2 组和通信因子管理函数运用例子 6.2.1 C 语言例子 #include "mpi.h" #include <stdio.h> #define NPROCS 8 int main(argc,argv) int argc; char *argv[]; { int MPI_Group MPI_Comm rank, new_rank, sendbuf, recvbuf, numtasks, ranks1[4]={0,1,2,3, ranks2[4]={4,5,6,7; orig_group, new_group; new_comm; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &numtasks); if (numtasks!= NPROCS) { printf("must specify MP_PROCS= %d. Terminating.\n",NPROCS); MPI_Finalize(); exit(0); 47

sendbuf = rank; /* Extract the original group handle */ MPI_Comm_group(MPI_COMM_WORLD, &orig_group); /* Divide tasks into two distinct groups based upon rank */ if (rank < NPROCS/2) { MPI_Group_incl(orig_group, NPROCS/2, ranks1, &new_group); else { MPI_Group_incl(orig_group, NPROCS/2, ranks2, &new_group); /* Create new new communicator and then perform collective communications */ MPI_Comm_create(MPI_COMM_WORLD, new_group, &new_comm); MPI_Allreduce(&sendbuf, &recvbuf, 1, MPI_INT, MPI_SUM, new_comm); MPI_Group_rank (new_group, &new_rank); printf("rank= %d newrank= %d recvbuf= %d\n",rank,new_rank,recvbuf); MPI_Finalize(); 6.2.2 Fortran 语言例子 program group include 'mpif.h' integer NPROCS parameter(nprocs=8) integer rank, new_rank, sendbuf, recvbuf, numtasks integer ranks1(4), ranks2(4), ierr integer orig_group, new_group, new_comm data ranks1 /0, 1, 2, 3/, ranks2 /4, 5, 6, 7/ call MPI_INIT(ierr) call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr) call MPI_COMM_SIZE(MPI_COMM_WORLD, numtasks, ierr) if (numtasks.ne. NPROCS) then print *,'Must specify MPROCS= ',NPROCS,' Terminating.' call MPI_FINALIZE(ierr) stop endif sendbuf = rank 48

C ierr) C & & & & ierr) Extract the original group handle call MPI_COMM_GROUP(MPI_COMM_WORLD, orig_group, Divide tasks into two distinct groups based upon rank if (rank.lt. NPROCS/2) then call MPI_GROUP_INCL(orig_group, NPROCS/2, ranks1, new_group, ierr) else call MPI_GROUP_INCL(orig_group, NPROCS/2, ranks2, new_group, ierr) endif call MPI_COMM_CREATE(MPI_COMM_WORLD, new_group, new_comm, ierr) call MPI_ALLREDUCE(sendbuf, recvbuf, 1, MPI_INTEGER, MPI_SUM, new_comm, call MPI_GROUP_RANK(new_group, new_rank, ierr) print *, 'rank= ',rank,' newrank= ',new_rank,' recvbuf= ',recvbuf call MPI_FINALIZE(ierr) end 6.2.3 例子结果输出 rank= 7 newrank= 3 recvbuf= 22 rank= 0 newrank= 0 recvbuf= 6 rank= 1 newrank= 1 recvbuf= 6 rank= 2 newrank= 2 recvbuf= 6 rank= 6 newrank= 2 recvbuf= 22 rank= 3 newrank= 3 recvbuf= 6 rank= 4 newrank= 0 recvbuf= 22 rank= 5 newrank= 1 recvbuf= 22 49

第七章虚拟拓扑 l MPI 为组内的进程转换拓扑结构提供了一个手段, 该拓扑是虚拟的, 它和并行机的物理结构机处理器的分布状况无关 ; l MPI 提供两个主要的拓扑结构 : 笛卡儿和图 ; l 虚拟拓扑在应用程序特定的通信模式中非常有用 - 并行模式与拓扑结构相匹配 ; l 虚拟拓扑建立在组和通信因子之上 ; 7.1 虚拟拓扑函数 7.1.1 MPI_Cart_coords 笛卡尔转换函数, 把进程序号 ( 秩 ) 转换成坐标 : MPI_Cart_coords (comm,rank,maxdims,*coords[]) MPI_CART_COORDS (comm,rank,maxdims,coords(),ierr) 7.1.2 MPI_Cart_create 根据笛卡儿拓扑信息创建一新的通信因子 : MPI_Cart_create (comm_old,ndims,*dims[],*periods, reorder,*comm_cart) MPI_CART_CREATE (comm_old,ndims,dims(),periods,reorder,comm_cart,ierr) 7.1.3 MPI_Cart_get 返回与通信因子相关的笛卡尔拓扑的信息 : MPI_Cart_get (comm,maxdims,*dims,*periods,*coords[]) MPI_CART_GET (comm,maxdims,dims,periods,coords(),ierr) 7.1.4 MPI_Cart_map 返回由 MPI 系统推荐的笛卡尔映象图, 为了更好地将该应用的虚拟通信图映射到物理机器拓扑上, 该调用是全局的 MPI_Cart_map (comm_old,ndims,*dims[],*periods[],*newrank) MPI_CART_MAP (comm_old,ndims,dims(),periods(),newrank,ierr) 7.1.5 MPI_Cart_rank 该函数将逻辑进程坐标转换成进程序号 ( 秩 ), 这些序号由点对点通信的程序使用 : 50

MPI_Cart_rank (comm,*coords[],*rank) MPI_CART_RANK (comm,coords(),rank,ierr) 7.1.6 MPI_Cart_shift 该函数完成笛卡尔移位操作, 根据移位坐标和移位步骤大小 ( 正数或负数 ) 输入条件确定移位操作, 返回移位的源和目的的秩 : MPI_Cart_shift (comm,direction,displ,*source,*dest) MPI_CART_SHIFT (comm,direction,displ,source,dest,ierr) 7.1.7 MPI_Cart_sub 该函数用来将通信因子组分成较低维笛卡尔子网格的子组, 并可为每个子组构造一个具有相关子网格笛卡尔拓扑的通信因子 : MPI_Cart_sub (comm,*remain_dims[],*comm_new) MPI_CART_SUB (comm,remain_dims(),comm_new,ierr) 7.1.8 MPI_Cartdim_get 返回与通信因子相关的笛卡尔结构的维数 这可用来向其他笛卡尔查询函数提供数组的正确大小 : MPI_Cartdim_get (comm,*ndims) MPI_CARTDIM_GET (comm,ndims,ierr) 7.1.9 MPI_Dims_create 该函数帮助用户在每个坐标方向选择一种进程的均衡分布, 这取决于被均衡的组中的进程数以及由用户指定的限制条件 使用该函数的一种可能方法是将所有进程 (MPI_COMM_WORLD 的组大小 ) 分成一个 n 维的拓扑 : MPI_Dims_create (nnodes,ndims,*dims[]) MPI_DIMS_CREATE (nnodes,ndims,dims(),ierr) 7.1.10MPI_Graph_create 返回一个与图拓扑信息相连的新通信因子 : MPI_Graph_create (comm_old,nnodes,*index[],*edges[], reorder,*comm_graph) MPI_GRAPH_CREATE (comm_old,nnodes,index(),edges(), reorder,comm_graph,ierr) 51

7.1.11MPI_Graph_get 该函数返回与通信因子相关联的图拓扑信息 : MPI_Graph_get (comm,maxindex,maxedges,*index[],*edges[]) MPI_GRAPH_GET (comm,maxindex,maxedges,index(),edges(),ierr) 7.1.12MPI_Graph_map 返回由图的拓扑信息印象 : MPI_Graph_map (comm_old,nnodes,*index[],*edges[],*newrank) MPI_GRAPH_MAP (comm_old,nnodes,index(),edges(),newrank,ierr) 7.1.13MPI_Graph_neighbors 返回与指定进程相关的临近节点数组 : MPI_Graph_neighbors (comm,rank,maxneighbors,*neighbors[]) MPI_GRAPH_NEIGHBORS (comm,rank,maxneighbors,neighbors(),ierr) 7.1.14MPI_Graphdims_get 返回图中的节点数和边数 : MPI_Graphdims_get (comm,*nnodes,*nedges) MPI_GRAPHDIMS_GET (comm,nnodes,nedges,ierr) 7.1.15MPI_Topo_test 返回赋与通信因子的拓扑类型 : MPI_GRAPH 图拓扑 MPI_CART 笛卡尔拓扑 MPI_UNDEFINED 没有拓扑 MPI_Topo_test (comm,*top_type) MPI_TOPO_TEST (comm,top_type,ierr) 7.2 笛卡儿虚拟拓扑例子 用 16 个进程建立一个 4*4 的笛卡儿拓扑结构, 每个进程与临近的 4 个进程交换秩 : 52

7.2.1 C 语言例子 #include "mpi.h" #include <stdio.h> #define SIZE 16 #define UP 0 #define DOWN 1 #define LEFT 2 #define RIGHT 3 int main(argc,argv) int argc; char *argv[]; { int numtasks, rank, source, dest, outbuf, i, tag=1, inbuf[4]={mpi_proc_null,mpi_proc_null,mpi_proc_null, MPI_PROC_NULL,, nbrs[4], dims[2]={4,4, periods[2]={0,0, reorder=0, coords[2]; MPI_Request reqs[8]; MPI_Status stats[8]; MPI_Comm cartcomm; %d\n", MPI_Init(&argc,&argv); MPI_Comm_size(MPI_COMM_WORLD, &numtasks); if (numtasks == SIZE) { MPI_Cart_create(MPI_COMM_WORLD, 2, dims, periods, reorder, &cartcomm); MPI_Comm_rank(cartcomm, &rank); MPI_Cart_coords(cartcomm, rank, 2, coords); MPI_Cart_shift(cartcomm, 0, 1, &nbrs[up], &nbrs[down]); MPI_Cart_shift(cartcomm, 1, 1, &nbrs[left], &nbrs[right]); outbuf = rank; for (i=0; i<4; i++) { dest = nbrs[i]; source = nbrs[i]; MPI_Isend(&outbuf, 1, MPI_INT, dest, tag, MPI_COMM_WORLD, &reqs[i]); MPI_Irecv(&inbuf[i], 1, MPI_INT, source, tag, MPI_COMM_WORLD, &reqs[i+4]); MPI_Waitall(8, reqs, stats); printf("rank= %d coords= %d %d neighbors(u,d,l,r)= %d %d %d %d\n", rank,coords[0],coords[1],nbrs[up],nbrs[down],nbrs[left], nbrs[right]); printf("rank= %d inbuf(u,d,l,r)= %d %d %d 53

rank,inbuf[up],inbuf[down],inbuf[left],inbuf[right]); else printf("must specify %d processors. Terminating.\n",SIZE); MPI_Finalize(); 7.2.2 Fortran 语言例子 program cartesian include 'mpif.h' integer SIZE, UP, DOWN, LEFT, RIGHT parameter(size=16) parameter(up=1) parameter(down=2) parameter(left=3) parameter(right=4) integer numtasks, rank, source, dest, outbuf, i, tag, ierr, & inbuf(4), nbrs(4), dims(2), coords(2), & stats(mpi_status_size, 8), reqs(8), cartcomm, & periods(2), reorder data inbuf /MPI_PROC_NULL,MPI_PROC_NULL,MPI_PROC_NULL, & MPI_PROC_NULL/, dims /4,4/, tag /1/, & periods /0,0/, reorder /0/ call MPI_INIT(ierr) call MPI_COMM_SIZE(MPI_COMM_WORLD, numtasks, ierr) & & & ierr) if (numtasks.eq. SIZE) then call MPI_CART_CREATE(MPI_COMM_WORLD, 2, dims, periods, reorder, cartcomm, ierr) call MPI_COMM_RANK(cartcomm, rank, ierr) call MPI_CART_COORDS(cartcomm, rank, 2, coords, ierr) print *,'rank= ',rank,'coords= ',coords call MPI_CART_SHIFT(cartcomm, 0, 1, nbrs(up), nbrs(down), ierr) call MPI_CART_SHIFT(cartcomm, 1, 1, nbrs(left), nbrs(right), ierr) outbuf = rank do i=1,4 dest = nbrs(i) source = nbrs(i) call MPI_ISEND(outbuf, 1, MPI_INTEGER, dest, tag, MPI_COMM_WORLD, reqs(i), ierr) call MPI_IRECV(inbuf(i), 1, MPI_INTEGER, source, tag, MPI_COMM_WORLD, reqs(i+4), 54

enddo call MPI_WAITALL(8, reqs, stats, ierr) print *,'rank= ',rank,' coords= ',coords, ' neighbors(u,d,l,r)= ',nbrs print *,'rank=',rank,' ', ' inbuf(u,d,l,r)= ',inbuf else print *, 'Must specify',size,' processors. Terminating.' endif call MPI_FINALIZE(ierr) end 7.2.3 例子输出结果 rank= 0 coords= 0 0 neighbors(u,d,l,r)= -3 4-3 1 rank= 0 inbuf(u,d,l,r)= -34-31 rank= 1 coords= 0 1 neighbors(u,d,l,r)= -3 5 0 2 rank= 1 inbuf(u,d,l,r)= -3502 rank= 2 coords= 0 2 neighbors(u,d,l,r)= -3 6 1 3 rank= 2 inbuf(u,d,l,r)= -3613... rank= 14 coords= 3 2 neighbors(u,d,l,r)= 10-3 13 15 rank= 14 inbuf(u,d,l,r)= 10-31315 rank= 15 coords= 3 3 neighbors(u,d,l,r)= 11-3 14-3 rank= 15 inbuf(u,d,l,r)= 11-314-3 55

第八章 MPI 运行环境 8.1 MPI 用户程序的编译与连接 Ø C 编译器为 :mpicc Ø Ø Fortran 编译器为 :mpif77 或 mpif90 运行 (MPI 作业提交 ) 见 作业管理用户手册 8.2 例子 8.2.1 例子一 (hello 程序 ) l 编辑代码 % vi hello.c #include mpi.h main(argc,argv) int argc; char **argv; { char msg[20]; int myrank,tag = 99; MPI_Status status; l MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD,&myrank); /*find my rank */ if ( myrank == 0) { strcpy( msg, Hello there ); MPI_Send( msg, strlen(msg), MPI_CHAR, 1, tag, MPI_COMM_WORLD); else if ( myrank == 1) { MPI_Recv(msg, 20, MPI_CHAR, 0,tag, MPI_COMM_WORLD, &status); printf( received %s\n,msg); MPI_Finalize(); 编译连接源代码 % mpicc o hello hello.c 56

生成可执行代码 hello 8.2.2 例子二 ( 一个简单的串行程序转换为并行程序 ) /* 串行程序 */ #include <stdio.h> void main(argc,argv) int argc; char *argv[]; { int A,B,C,D,E,F,G; B = 1; C = 2; F = 3; G = 4; A = B + C; E = F + G; D = A - E; printf("d=%d\n",d); return; /* MPI 并行程序 3 个进程 */ #include "mpi.h" #include <stdio.h> void main(argc,argv) int argc; char *argv[]; { int A,B,C,D,E,F,G; int numprocs,myid; 57

MPI_Init(&argc,&argv); MPI_Comm_size(MPI_COMM_WORLD,&numprocs); MPI_Comm_rank(MPI_COMM_WORLD,&myid); if (numprocs!= 3) { printf("numproces must equal to 3!\n"); MPI_Finalize(); return; if(myid == 1) { B = 1; C = 2; A = B + C; MPI_Send(&A,1,MPI_INT,0,80,MPI_COMM_WORLD); else if (myid ==2) { F = 3; G = 4; E = F + G; MPI_Send(&E,1,MPI_INT,0,80,MPI_COMM_WORLD); else if (myid == 0) { MPI_Recv(&A,1,MPI_INT,1,80,MPI_COMM_WORLD); MPI_Recv(&E,1,MPI_INT,2,80,MPI_COMM_WORLD); D = A - E; printf("d = %d\n",d); MPI_Finalize(); /* MPI 并行程序 2 个进程 */ #include "mpi.h" #include <stdio.h> void main(argc,argv) int argc; char *argv[]; 58

{ int A,B,C,D,E,F,G; int numprocs,myid; MPI_Init(&argc,&argv); MPI_Comm_size(MPI_COMM_WORLD,&numprocs); MPI_Comm_rank(MPI_COMM_WORLD,&myid); if (numprocs!= 2) { printf("numproces must equal to 2!\n"); MPI_Finalize(); return; if (myid == 0) { B = 1; C = 2; A = B + C; MPI_Recv(&E,1,MPI_INT,1,80,MPI_COMM_WORLD); D = A - E; printf("d = %d\n",d); else if (myid == 1) { F = 3; G = 4; E = F + G; MPI_Send(&E,1,MPI_INT,0,80,MPI_COMM_WORLD); MPI_Finalize(); 8.2.3 例子三 ( 计算 π 的串行及并行程序 ) 用数值积分法求 的函数表达式, 就是区间 [0,1] 内与函数曲线 4/(1+X 2 ) 组成的面积, 此面积就是 π 的近似值, 为此先将区间 [0,1] 划分成 N 个等间隔的子区间, 每个子区间的宽度为 1/N; 然后计算出各个子区间中点处的函数值 ; 再将各个子区间面积相加就可以得出 的近似值 59

计算 π 的表达式 : 1 P = 1 + ò 4 dx» å 0 x 2 0 < i 2 0.5 ö i N 1 + ç N è ø 4 * 1 N é ù ê ú P» 4 ê 1 1 1 2 ê æ + N + 0.5 ç + æ + 2 0.5 + æ + 0.5 1 2 + æ N - + ö ú ú ö ö ö ( 1) 0.5 ê1 è N 1 ç N 1 ç N 1 ç N ú ë ø è ø è ø è ø û /* 计算 π 的 C 语言串行程序 */ #include <stdio.h> #include <math.h> #define N 1000000 main() { double local,pi=0.0,w; long i; w = 1.0/N; for ( i = 0; i < N; i++) { local = (i + 0.5) * w; pi = pi + 4.0 / (1.0 + local * local); printf( pi is %f\n,pi*w);

60

#include "mpi.h" #include <stdio.h> #include <math.h> double f(a) double a; { return (4.0 / (1.0 + a*a)); void main(argc,argv) int argc; char *argv[]; { int done = 0, n, myid, numprocs, i; double PI25DT = 3.141592653589793238462643; double mypi, pi, h, sum, x; double startwtime, endwtime; int namelen; char processor_name[mpi_max_processor_name]; 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); startwtime = MPI_Wtime(); MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD); if (n == 0) 61