高性能计算-MPI并行程序开发实战(三)

高性能计算-MPI并行程序开发实战(三)

MPI预定义数据类型

在C语言中,MPI预定义了如下数据类型可以直接使用,MPI预定义数据类型与C语言数据类型对应关系如下所示:

MPI预定义数据类型 相应的C数据类型
MPI_CHAR signed char
MPI_SHORT signed short int
MPI_INT signed int
MPI_LONG signed long int
MPI_UNSIGNED_CHAR unsigned char
MPI_UNSIGNED_SHORT unsigned short int
MPI_UNSIGNED unsigned int
MPI_UNSIGNED_LONG unsigned long int
MPI_FLOAT float
MPI_DOUBLE double
MPI_LONG_DOUBLE long double
MPI_BYTE 无对应类型
MPI_PACKED 无对应类型

MPI_BYTE和MPI_PACKED数据类型没有对应的C语言数据类型。MPI_BYTE类型的的一个值由一个字节,即8个二进制位组成。一个字节不同于一个字符,因为对于字符表示不同的机器,可以用一个以上的字节表示字符。另一方面,在所有的机器上,一个字节有相同的二进制值。
MPI支持上述数据类型以匹配ANSI C的基本数据类型。若宿主语言有附加的数据类型,那么MPI应附加提供相应的数据类型。

附加的MPI数据类型 相应的C数据类型
MPI_LONG_LONG_INT long long int

MPI数据类型匹配和数据转换

MPI类型匹配规则

在MPI消息传递的整个过程中,什么时候要涉及类型匹配?首先看MPI消息传递的过程,如下图所示:
MPI_DATATYPE_MESSAGE_PASSING
MPI的消息传递过程可以分为以下三个阶段:

  • 消息装配 将发送数据从发送缓冲区中取出 加上消息信封等形成一个完整的消息
  • 消息传递 将装配好的消息从发送端传递到接收端
  • 消息拆卸 从接收到的消息中取出数据送入接收缓冲区

在这三个阶段,都需要数据类型匹配。

  • 在消息装配时,发送缓冲区中变量的类型必须和相应的发送操作指定的类型相匹配
  • 在消息传递时 发送操作指定的类型必须和相应的接收操作指定的类型相互匹配
  • 在消息拆卸时 接收缓冲区中变量的类型必须和接收操作指定的类型相匹配

以上指出了在什么时候需要类型匹配。进⼀步的类型匹配到底包括哪些⽅⾯?在MPI中,类型匹配有两个⽅⾯的意思:

  • 宿主语⾔的类型和通信操作所指定的类型相匹配
  • 发送⽅和接收⽅的类型相匹配

对于类型匹配的第一条,在C语⾔中,如int类型要和MPI_INT相对应,float要和MPI_Float相对应。
对于类型匹配的第二条,要求发送⽅和接收⽅对数据类型的指定必须是⼀样的。即发送⽅⽤MPI_INTEGER,则接收⽅也必须使⽤MPI_INTEGER。发送⽅使用MPI_REAL,则接收⽅也必须使用MPI_REAL。对于C语⾔,虽然有些系统中int和long是一样的,但是MPI认为MPI_INT和MPI_LONG是不同的类型,即MPI_INT和MPI_LONG是不匹配的。发送⽅和接收⽅必须同时使⽤MPI_INT或MPI_LONG。

上述类型匹配规则的例外是对于MPI提供的MPI_BYTE和MPI_PACKED。它们可以和任何以字节为单位的数据类型相匹配,包含这些字节的类型是任意的。MPI_TYPE⽤于不加修改地传送内存中的⼆进制值,MPI_PACK⽤于数据的打包(MPI_PACK)和解包(MPI_UNPACK)。

归纳起来,类型匹配规则可以概括为:

  • 有类型数据的通信:发送⽅和接收⽅均使⽤相同的数据类型
  • ⽆类型数据的通信:发送⽅和接收⽅均以MPI_BYTE作为数据类型
  • 打包数据的通信:发送⽅和接收⽅均使⽤MPI_PACKED

数据转换

所谓的数据转换包括两个⽅⾯的意思:

  • 数据类型的转换
  • 数据表示的转换

数据类型的转换是指改变⼀个值的数据类型,⽐如将实型转换为整型。通过舍⼊操作或将整型转换为实型等。
⽽数据表示的转换是指改变⼀个值的⼆进制表示,⽐如⾼字节和低字节顺序的改变。将浮点数从32位表示改变为64位表示等。

由于MPI严格要求类型匹配,所以在MPI中不存在数据类型转换的问题。但是,MPI必须实现数据表示的转换。这是因为MPI的⽬的之⼀是对异构环境的⽀持,在异构系统中,不同的系统其数据的内部表示往往是不同的。因此MPI必须负责实现这些不同表示之间的相互转换。

在MPI中,没有限定的数据表示转换的细节。但总的⽬标是希望这样的转换保留整型、逻辑以及字符值不变,⽽把浮点值转为在⽬标系统上能表示的最接近的值。在浮点转换过程中,上溢和下溢可能发⽣。当⼀个值能在⼀个系统中表示⽽不能在另⼀个系统中表示时,整型或字符的转换也可导致异常。在表示转换过程中,⼀个异外发⽣会导致⼀个通信失败,在发送操作、接收操作或两者同时执行时也会发⽣错误。

如果在⼀个消息中发送的⼀个值是⽆类型的,例如:MPI_BYTE类型。那么在接收者存储的字节的⼆进制表示与接收者接收的字节的⼆进制表示⼀样。⽆论发送者和接收者运⾏在同⼀环境或不同环境,这都是正确的。不要求表示转换。当⼀个MPI程序在同构系统中运⾏,其所有进程运⾏在同⼀环境时,没有转换发⽣。两个实型跨度⼤于10的数组进行数据发送其结果是正确的。如果发送者和接收者在不同的环境中执⾏,那么从发送缓冲区取出的10个实型值,存储到接收缓冲区以前被转换为接收者的实型表示,当从发送缓冲区取出的实型元素的个数等于接收缓冲区所存的实型元素的个数时,存储的字节数不必等于接收的字节数。例如,发送者可以使⽤四个字节表示实型数,接收者使⽤⼋个⼦字节表示实型数。既使发送者和接收者在不同的环境运⾏,从发送缓冲区装⼊的四⼗个字节将被存在接收缓冲区,发送的消息与接收的消息有完全相同的⻓度(按字节)和相同的⼆进制表示。如果是不同的类型,或他们类型相同但使⽤不同的数据表示。那么接收缓冲区所存的各位可以译码出不同于发送缓冲区的译码值。数据表示的转换也应⽤于⼀个消息的信封:源,⽬的和标识都需要被转换为整型。

MPI消息

MPI消息的组成

MPI消息包括信封和数据两个部分,信封指出了发送或接收消息的对象及相关信息,⽽数据是本消息将要传递的内容。信封和数据⼜分别包括三个部分,可以⽤⼀个三元组来表示。

信封 <源/⽬ 标识 通信域>
数据 <起始地址 数据个数 数据类型>

以MPI_SEND和MPI_RECV为例,下图给出了它们的信封和数据部分的说明。

MPI_SEND语句的消息信封和消息数据图:
MPI_SEND_MESSAGE_DESCR

MPI_RECV语句的消息信封和消息数据图:
MPI_SEND_MESSAGE_DESCR

在消息信封中除了源/⽬外,为什么还有tag标识?这是因为当发送者发送两个相同类型的数据给同⼀个接收者时,如果没有消息标识,接收者将⽆法区别这两个消息。如下图所示:

tag在MPI消息发送和接收中的作用:
MPI_SEND_MESSAGE_DESCR

任意源和任意标识

⼀个接收操作对消息的选择是由消息的信封管理的,如果消息的信封与接收操作所指定的值source, tag和comm相匹配,那么这个接收操作能接收这个消息。接收者可以给source指定⼀个任意值MPI_ANY_SOURCE,标识任何进程发送的消息都可以接收。即本接收操作可以匹配任何进程发送的消息,但其它的要求还必须满⾜ ⽐如tag的匹配,如果给tag⼀个任意值MPI_ANY_TAG,则任何tag都是可接收的。在某种程度上,类似于统配符的概念。MPI_ANY_SOURCE和MPI_ANY_TAG可以同时使⽤或分别单独使⽤,但是不能给comm指定任意值。如果⼀个消息被发送到接收进程,接收进程有匹配的通信域,有匹配的 source (或其source = MPI_ANY_SOURCE),有匹配的tag(或其tag = MPI_ANY_TAG),那么这个消息能被这个接收操作接收。由于MPI_ANY_SOURCE和MPI_ANY_TAG的存在,导致了发送操作和接收操作间的不对称性。即⼀个接收操作可以接收任何发送者的消息,但是对于⼀个发送操作,则必须指明⼀个单独的接收者。MPI允许发送者=接收者,Source = destination,即⼀个进程可以给⾃⼰发送⼀个消息。但是这种操作要注意死锁的产⽣。

MPI通信域

MPI通信域包括两部分:进程组和通信上下⽂。进程组即所有参加通信的进程的集合。如果⼀共有N个进程参加通信,则进程的编号从0到N-1。通信上下⽂提供⼀个相对独⽴的通信区域。不同的消息在不同的上下⽂中进⾏传递,不同上下⽂的消息互不⼲涉。通信上下⽂可以将不同的通信区别开来。⼀个预定义的通信域MPI_COMM_WORLD由MPI提供,MPI初始化后,便会产⽣这⼀描述⼦。它包括了初始化时可得的全部进程,进程是由它们在MPI_COMM_WORLD组中的进程号所标识。⽤户可以在原有的通信域的基础上,定义新的通信域,通信域为库和通信模式提供⼀种重要的封装机制。他们允许各模式有其⾃⼰的独⽴的通信域,和它们⾃⼰的进程计数⽅案。