高级并行程序设计
具有不连续数据发送的程序设计
MPI除了可以发送或接收连续的数据之外,还可以处理不连续的数据,其基本方法有两种:一是允许用户自定义新的数据类型,又称派生数据类型;二是数据的打包与解包,即在发送方将不连续的数据打包到连续的区域,然后发送出去,在接收方将打包后的连续数据解包到不连续的存储空间 。
派生数据类型
为了介绍派生数据类型,首先介绍一种通用的数据类型描述方法–类型图,使用类型图可以用比较精确和形式化的方法来描述各种各样的类型。类型图是一系列二元组的集合,两个数据类型是否相同取决于它们的类型图是否相同。类型图的二元组为如下形式
<基类型 偏移>
则类型图为
类型图={<基类型0 偏移0>,<基类型1 偏移1>,…,<基类型n-1 偏移n-1>}
基类型指出了该类型图中包括哪些基本的数据类型 而偏移则指出该基类型在整个类型 图中的起始位置 。
新数据类型的定义
连续复制类型的生成
MPI_TYPE_CONTIGUOUS(count,oldtype,newtype)
IN count 复制个数(非负整数)
IN oldtype 旧数据类型(句柄)
OUT newtype 新数据类型(句柄)
它得到的新类型是将一个已有的数据类型按顺序依次连续进行复制后的结果。
int MPI_Type_contiguous(int count,MPI_Datatype oldtype, MPI_Datatype *newtype)
向量数据的生成类型
*MPI_TYPE_VECTOR(count,blocklength,stride,oldtype,newtype) *
IN count 块的数量(非负整数)
IN blocklength 每个块中所含元素个数(非负整数)
IN stride 各块第一个元素之间相隔的元素个数(整数)
IN oldtype 旧数据类型(句柄)
OUT newtypr 新数据类型(句柄)
MPI_TYPE_VECTOR是一个更通用的生成器,允许复制一个数据类型到含有相等大小块的空间,每个块通过连接相同数量的旧数据类型的拷贝来获得,块与块之间的空间是旧数据类型的extent的倍数。
- MPI_TYPE_HVECTOR(count,blocklength,stride,oldtype,newtype)
函数MPI_TYPE_HVECTOR和MPI_TYPE_VECTOR基本相同,只是stride不再是元素个数, 而是字节数 。
索引数据类型的生成
*MPI_TYPE_INDEXED(count,array_of_blocklengths,array_of_displacemets,oldtype,newtype) *
IN count 块的数量 整型
IN array_of_blocklengths 每个块中所含元素个数(非负整数数组)
IN array_of_displacements 各块偏移值 (整数数组)
IN oldtype 旧数据类型(句柄)
OUT newtypr 新数据类型(句柄)
MPI_TYPE_INDEXED允许复制一个旧数据类型到一个块序列中(每个块是旧数据类型的一个连接),每个块可以包含不同的拷贝数目和具有不同的偏移.所有的块偏移都是旧数据类型 extent的倍数.
- *MPI_TYPE_HINDEXED(count,array_of_blocklengths,array_of_displacemets,oldtype,newtype) *
函数MPI_TYPE_HINDEXED和MPI_TYPE_INDEXED基本相同,只是array_of_displacements中 的块偏移不再是旧数据类型extent的倍数,而是字节数.
结构数据类型的生成
MPI_TYPE_STRUCT(count,array_of_blocklengths,array_of_displacemets,array_of_types, newtype)
IN count 块的数量 (整数)
IN array_of_blocklengths 每个块中所含元素个数(整数数组)
IN array_of_displacements 各块偏移字节数(整数数组)
IN array_of_types 每个块中元素的类型(句柄数组)
OUT newtypr 新数据类型(句柄)
int MPI_Type_struct(int count,int *array_of_blocklengths, MPI_Aint *array_of_displacements, MPI_Datatype array_of_types , MPI_Datatype *newtype)
新类型的递交和释放
新定义的数据类型在使用之前必须先递交给MPI系统,一个递交后的数据类型可以作为一个基本类型用在数据类型生成器中产生新的数据类型,预定义数据类型不需要递交就可以直接使用。递交操作用于递交新定义的数据类型 注意这里的参数是指向该类型的指针或句柄,而不是定义该类型的缓冲区的内容。
MPI_TYPE_COMMIT(datatype)
INOUT datatype 递交的数据类型(句柄)
MPI_TYPE_FREE(datatype)
MPI_TYPE_FREE调用将以前已递交的数据类型释放,并且设该数据类型指针或句柄为空MPI_DATATYPE_NULL。 由该派生类型定义的新派生类型不受当前派生类型释放的影响。
地址函数
MPI_ADDRESS(location,address)
IN location 内存地址(可选数据类型)
OUT address 相对于位置MPI_BOTTOM的偏移(整型)
一个MPI提供的地址调用MPI_ADDRESS可以返回某一个变量在内存中相对于预定义的地址MPI_BOTTOM的偏移地址。
int MPI_ADdress(void* location, MPI_Aint *address)
与新数据有关的调用
MPI_TYPE_EXTENT(datatype,extent)
IN datatype 数据类型(句柄)
OUT extent 数据类型extent(整型)
MPI_TYPE_EXTENT以字节为单位返回一个数据类型的跨度extent。
int MPI_Type_extent(MPI_Datatype datatype, int *extent)
MPI_TYPE_SIZE(datatype,size)
IN datatype 数据类型(句柄)
OUT size 数据类型大小(整型)
MPI_TYPE_SIZE以字节为单位,返回给定数据类型有用部分所占空间的大小,即跨度减去类型中的空隙后的空间大小。和MPI_TYPE_EXTENT相比,MPI_TYPE_SIZE不包括由于对齐等原因导致数据类型中的空隙所占的空间。
int MPI_Type_size(MPI_Datatype datatype, int *size)
MPI_GET_ELEMENTS status, datatype, count )
IN status 接收操作返回的状态(状态类型)
IN datatype 接收操作使用的数据类型(句柄)
OUT count 接收到的基本元素个数(整型)
MPI_GET_COUNT(status, datatype,count)
以接收操作完成后返回的状态status为参数 ,MPI_CET_COUNT返回的是以指定的数据类型为单位 接收操作接收到的数据的个数;而MPI_GET_ELEMENTS返回的则是以基本的类型为单位的数据的个数,MPI_GET_COUNT 和MPI_GET_ELEMENT对基本数据类型使用时返回值相同。
int MPI_Get_count(MPI_Status * status, MPI_Datatype datatype, int * count)
MPI_TYPE_LB(datatype,displacement)
IN datatype 数据类型(句柄)
OUT displacement 下界的偏移(整数)
MPI_TYPE_UB(datatype,displacement)
MPI提供两个特殊的数据类型,称为伪数据类型上界标记类型MPI_UB和下界标记类型MPI_LB。这两个数据类型不占空间即extent(MPI_LB)= extent(MPI_UB) =0) 他们主要是用来影响数据类型的跨度,从而对派生数据类型产生影响 。
打包与解包
打包(Pack)和解包(Unpack)操作是为了发送不连续的数据,在发送前显式地把数据包装到一个连续的缓冲区,在接收之后从连续缓冲区中解包。
MPI_PACK(inbuf, incount, datatype, outbuf, outcount, position, comm )
IN inbuf 输入缓冲区起始地址(可选数据类型)
IN incount 输入数据项个数(整型)
IN datatype 每个输入数据项的类型(句柄)
OUT outbuf 输出缓冲区开始地址(可选数据类型)
IN outcount 输出缓冲区大小(整型)
INOUT position 缓冲区当前位置(整型)
IN comm 通信域(句柄)
输入缓冲区可以是MPI_SEND允许的任何通信缓冲区。入口参数position的值是输出缓冲区中用于打包的起始地址 ,打包后它的值根据打包消息的大小来增加。出口参数position的值是被打包的消息占用的输出缓冲区后面的第一个地址,通过连续几次对不同位置的消息调用打包操作,就将不连续的消息放到了一个连续的空间。comm参数是将在后面用于发送打包的消息时用的通信域。
int MPI_Pack(void* inbuf, int incount, MPI_datatype, void *outbuf, int outcount, int *position, MPI_Comm comm)
MPI_UNPACK(inbuf, insize, position, outbuf, outcount, datatype, comm )
IN inbuf 输入缓冲区起始(选择)
IN insize 输入数据项数目(整型)
INOUT position 缓冲区当前位置, 字节(整型)
OUT outbuf 输出缓冲区开始(选择)
IN outcount 输出缓冲区大小, 字节(整型)
IN datatype 每个输入数据项的类型(句柄)
IN comm 打包的消息的通信域(句柄)
int MPI_Unpack(void* inbuf, int insize, int *position, void *outbuf, int outcount, MPI_Datatype datatype, MPI_Comm comm)
通过连续几次对已打包的消息调用与打包时相应的解包操作,就可以将连续的消息解开放到一个不连续的空间 comm参数是用于接收消息的通信域。
MPI_RECV和MPI_UNPACK的区别: 在MPI_RECV中,count参数指明的是可以接收的最大项数,实际接收的项数是由接收的消息的长度来决定的。在MPI_UNPACK中,count参数指明实际解包的项数,相应消息的大小是参数position的减少值。
用MPI_PACKED发送的数据可以用任意数据类型来接收,只要它和实际接收到的消息的数据类型相匹配。以任何类型发送的消息(包括MPI_PACKED类型)都可以用MPI_PACKED类型接收,这样的消息于是就可以被调用MPI_UNPACK来解包 。
MPI_PACK_SIZE( incount, datatype, comm, size )
IN incount 指定数据类型的个数(整型)
IN datatype 数据类型(句柄)
IN comm 通信域(句柄)
OUT size 以字节为单位 ,incount个datatype数据类型需要的空间(整型)
MPI_PACK_SIZE调用的返回值是size,表示incount个datatype数据类型需要的空间的大小。该调用返回的是上界 ,而不是一个精确界。这是因为包装一个消息所需要的精确空间可能依赖于上下文。