第六讲 消息传递编程接口 MPI 三 MPI 数据类型 1
MPI 数据类型 MPI 数据类型定义 MPI 数据类型的大小 上下界 域及相关函数 MPI 新数据类型的创建 提交与释放 MPI 数据的打包与解包 2
MPI 数据类型 MPI 原始数据类型 MPI 消息传递通常只能处理连续存放的同一类型的数据 MPI 自定义数据类型 如果需要发送或接收具有复杂结构的数据时, 可以使用自定义数据类型 使用自定义数据类型的好处 : 有效减少消息传递次数, 增大通信粒度, 同时可以避免或减少消息传递时数据在内存中的拷贝 MPI 的数据类型只用于消息传递! 3
MPI 数据类型定义 MPI 数据类型由两个相同长度的序列组成 : 类型序列和位移序列 {t 1, t 2, t 3,..., t n } {d 1, d 2, d 3,..., d n } t i 的取值为基本数据类型 d i 表示位移, 取值为整数, 以字节为单位 新建的数据类型称为复合数据类型 MPI 数据类型图 {(t 1,d 1 ),(t 2,d 2 ),(t 3,d 3 ),..., (t n,d n )} 这个新的数据类型包含 n 个数据, 其中第 i 个数据的数据类 型为 t i, 该数据离首地址的距离为 d i 4
举例 例 : 设数据类型 mytype 的数据类型图为 {(MPI_REAL,4),(MPI_REAL,12),(MPI_REAL,0)} 则下面的语句 : real A(100)...... call MPI_SEND(A, 1, mytype,... ) 发送的数据为 A(2), A(4), A(1) 5
数据类型的大小 数据类型的大小 该数据类型中包含的数据长度, 即字节数设一个数据了下的类型图为 {(t 1,d 1 ),(t 2,d 2 ),(t 3,d 3 ),..., (t n,d n )} 则它的大小为 sizeof(t 1 ) + sizeof(t 2 ) +... + sizeof(t n ) 例 : 设数据类型 mytype 的数据类型图为 {(real,4),(real,12),(real,0)} 则 mytype 的大小为 12 6
数据类型的上下界 {(t 1,d 1 ),(t 2,d 2 ),(t 3,d 3 ),..., (t n,d n )} 数据类型的下界 : 类型图中的最小位移, 即 数据类型的上界 : 数据类型的域 (extent) : 上界 - 下届 7
数据类型的对界量 原始数据类型的对界量 : 由编译系统决定 复合数据类型的对界量 : 其所包含的基本数据类型的对界量的最大值 地址对界要求 : 一个数据类型在内存中所占的字节数必须是其对界量的整数倍 地址对界修正量 : 使得新建数据类型的域能被其对界量整除的最小非负整数 8
举例 例 : 假设 MPI_DOUBLE_PRECISION 和 MPI_INTEGER 的对界量均为 4,MPI_BYTE 的对界量为 1, 考虑下面的数据类型 {(MPI_DOUBLE_PRECISION,0), (MPI_BYTE,12), (MPI_INTEGER,8)} 4 16 0 对界量为, 上界为, 下界为, 16 3 域为, 地址对界修正量为 9
两个特殊的数据类型 MPI_LB MPI_UB 伪数据类型, 大小为 0 它们的作用 : 人工指定新建数据类型的上下界 若数据类型中含 MPI_LB, 则下界定义为 MPI_LB 的位移的最小值 ; 若数据类型中含 MPI_UB, 则上界定义为 MPI_UB 的位移的最大值 ; 例 : 下面的数据类型的下界为 -4 {(MPI_REAL,4), (MPI_LB,12), (MPI_REAL,0), (MPI_LB,-4)} 10
数据类型查询函数 MPI_TYPE_EXTENT(datatype, extent) MPI_TYPE_SIZE(datatype, size) MPI_TYPE_UB(datatype, displacement) MPI_TYPE_LB(datatype, displacement) 详细用法见相关参考资料 11
新数据类型的创建 新数据类型创建函数 MPI_TYPE_CONTIGUOUS MPI_TYPE_VECTOR MPI_TYPE_HVECTOR MPI_TYPE_INDEXED MPI_TYPE_HINDEXED MPI_TYPE_STRUCT 新数据类型的提交 :MPI_TYPE_COMMIT 若使用新数据类型进行通信, 则必须先提交 过渡数据类型不用提交, 用完后就可直接释放 新数据类型的释放 :MPI_TYPE_FREE 数据的打包和解包 :MPI_PACK MPI_UNPACK 12
MPI_TYPE_CONTIGUOUS MPI_TYPE_CONTIGUOUS(count, oldtype, newtype) 连续复制 : 将原数据类型 oldtype 按顺序依次连续复制后, 得到一个新的数据类型 参数 C F77 IN count 复制个数 IN oldtype 旧数据类型 OUT newtype 新数据类型 int MPI_Type_contiguous(int count, MPI_Datatype oldtype, MPI_Datatype *newtype) MPI_TYPE_CONTIGUOUS(COUNT, OLDTYPE, NEWTYPE, IERR) INTEGER COUNT, OLDTYPE, NEWTYPE, IERR 注 :oldtype 可以是原始数据类型, 也可以是已创建的复合数据类型 13
举例 integer n, type1 parameter (n=100) real a(n)............ call MPI_TYPE_CONTIGUOUS(n,MPI_REAL,type1,ierr) call MPI_TYPE_COMMIT(type1,ierr) call MPI_SENDRECV_REPLACE(a,1,type1,dst,111,src, 111,MPI_COMM_WORLD,status,ierr)...... 上面的消息传递等价于 call MPI_SENDRECV_REPLACE(a,100,MPI_REAL,dst,111, src,111,mpi_comm_world,status,ierr) 14
MPI_TYPE_VECTOR MPI_TYPE_VECTOR(count,blocklen,stride, oldtype, newtype) 创建向量数据类型 : 先连续复制 blocklen 个 oldtype 类型 的数据, 形成一个数据块 ; 再通过等间隔地复制 count 个该 数据块而形成新的数据类型 ; 相邻两个数据块的起始位置的位移相差为 stride*extent(oldtype) 个字节 C F77 int MPI_Type_vector(int count,int blocklen, int stride, MPI_Datatype oldtype, MPI_Datatype *newtype) MPI_TYPE_VECTOR(COUNT, BLOCKLEN, STRIDE, OLDTYPE, NEWTYPE, IERR) INTEGER COUNT,BLOCKLEN,STRIDE, OLDTYPE, NEWTYPE, IERR 15
举例 integer n, type1 parameter (n=100) real A(n,n)............ call MPI_TYPE_VECTOR(n,1,n,MPI_REAL,type1,ierr) call MPI_TYPE_COMMIT(type1,ierr) call MPI_SEND(A,1,type1,dst,...)...... 上面发送的是 A 的第一行 大家看看下面发送的是什么? call MPI_TYPE_VECTOR(n-2,n-2,n,MPI_REAL,type1,ierr) call MPI_TYPE_COMMIT(type1,ierr) call MPI_SEND(A(2,2),1,type1,dst,...) 思考 : 怎样发送 A 的对角线? 16
MPI_TYPE_HVECTOR MPI_TYPE_HVECTOR(count,blocklen,stride, oldtype, newtype) 功能同 MPI_TYPE_VECTOR 唯一区别为这里的 stride 以字节为单位 17
MPI_TYPE_INDEXED MPI_TYPE_INDEXED(count,array_of_blocklens, array_of_disps,oldtype,newtype) 创建索引数据类型 : 该函数生成的新数据类型由 count 个数据块构成, 第 i 个数据块包含 array_of_bloklens(i) 个连续存放的 oldtype, 第 i 个数据块与首地址的偏移量 ( 字节数 ) 为 array_of_disps(i)*extent(oldtype) 可以看作是 MPI_TYPE_VECTOR 的扩展, 区别是每个数据块的长度可以不同, 数据块之间的间隔也可以不同 18
MPI_TYPE_HINDEXED MPI_TYPE_HINDEXED(count,array_of_blocklens, array_of_disps,oldtype,newtype) 功能同 MPI_TYPE_INDEXED 唯一区别为这里的 array_of_disps 以字节为单位 19
MPI_TYPE_INDEXED MPI_TYPE_STRUCT(count,array_of_blocklens, array_of_disps,array_of_types,newtype) 创建结构数据类型 : 与 MPI_TYPE_HINDEXED 的区别在于每个数据块的数据类型可以不同 这里的 array_of_disps 以字节为单位 该函数是最一般的新数据类型的构造函数, 也是使用最广泛的一个, 正确使用此函数在实际应用中非常重要 20
数据类型的提交与释放 新数据类型的提交 MPI_TYPE_COMMIT(newdatatype) 新数据类型的释放 MPI_TYPE_FREE(newdatatype) 21
地址函数 MPI_ADDRESS(location, address) 返回指定变量在内存中的 绝对 地址 C F77 int MPI_Address(void *location, MPI_Aint *address) MPI_ADDRESS(LOCATION, ADDRESS, IERR) <type> LOCATION(*) INTEGER ADDRESS, IERR 22
数据的打包 MPI_PACK(inbuf,incount,datatype,outbuf, outsize, position, comm) 将缓冲区 inbuf 中的 incount 个类型为 datatype 的数据进行打包, 打包后的数据放在缓冲区 outbuf 中 outsize 给出的是 outbuf 的总长度 ( 字节数 ),comm 是发送打包数据时将使用的通信器 position 是打包缓冲区中的位移, 每次打包第一次调用 MPI_PACK 时用户应该将其置为 0, 随后 MPI_PACK 将自动修改它, 使得它总是指向打包缓冲区中尚未使用部分的起始位置 每次调用 MPI_PACK 后的 position 实际上就是已打包数据的总长度 通过连续几次对不同位置的数据进行打包, 就可以将不连续的数据放到一个连续的空间中 23
数据的解包 MPI_UNPACK(inbuf,insize,position,outbuf, outcount,datatype,comm) 是 MPI_PACK 的逆操作 : 它从 inbuf 中拆包 outcount 个类型为 datatype 的数据到 outbuf 中 函数的各项参数与 MPI_PACK 类似, 只不过这里的 inbuf 和 insize 对应于 MPI_PACK 中的 outbuf 和 outsize, 而 outbuf 和 outcount 则对应于 MPI_PACK 中的 inbuf 和 incount 24