在这三个阶段,没有看到类型匹配:在发送者缓存中的每个变量类型必须匹配该发送操作为输如指定的类型;由发送操作指定的类型必须匹配由接收操作指定的类型;在接收缓存中的每个变量的类型必须匹配由接收操作为指定的类型。不能观察到这三个规则的程序是错误的。
为了更精确地定义,我们必须处理两个问题:宿主语言的类型和通讯操作指定的类型匹配;发送者和接收者的类型匹配。
如果发送和接收操作使用同一类型名字,那么这个发送和接收操作类型匹配(第二阶段)。即,MPI_INTEGER匹配MPI_INTEGER,
MPI_REAL匹配MPI_REAL, 等等。对这个规则, 有一个例外, 将在3.13节中讨论, 类型MPI_PACKED能匹配任何其它类型。
如果由通讯操作所使用的数据类型名字相应于宿主程序变量的基本类型,那么宿主程序中该变量的类型匹配通讯操作所指定的类型。例如,类型名为MPI_INTEGER的输入匹配Fortran的变量类型INTEGER。在3.2.2节给出Fortran和C的这种对照表。对最后这个规则,有两个例外:类型为名为MPI_BYTE或MPI_PACKED的一个输入能用于匹配任何存储字节(在一个字节地址的机器上),
不论包含这个字节的变量类型是什么。类型MPI_PACKED用于发送己显式打包的数据,
或接收将被显式拆包的数据,看3.13节。类型MPI_BYTE允许我们传送不变的存储中的一个字节的二进制值。
总之,类型匹配规则分为下面三个部分。 .
无类型值的通讯(例如, MPI_BYTE数据类型), 发送者和接收者使用MPI_BYTE类型。在这种情况下,对发送者和接收者程序的相应输入类型无要求,也不要求他们类型一样。
.
IF(rank.EQ.0) THEN
CALL MPI_SEND(a(1), 10, MPI_REAL, 1, TAG, comm, ierr)
ELSE
CALL MPI_RECV(b(1), 15, MPI_REAL, 0, tag, comm, status, ierr)
END IF
如果a和b是尺寸大于等于10的实型数组,则这个程序是正确的。(在Fortran中,既使a或b的尺寸小于10,
使用这个程序可能是正确的: 例如, 当a(1)能等价于一个含10个元素的数组。)
例子3.2 发送者和接收者不指定匹配类型。
CALL MPI_COMM_RANK(comm, rank, ierr)
IF(rank.EQ.0) THEN CALL MPI_SEND(a(1), 10, MPI_REAL, 1, tag, comm, ierr)
ELSE CALL MPI_RECV(b(1), 40, MPI_BYTE, 0, tag, comm, status, ierr) END
IF 这个程序是错误的, 因为发送者和接收者没提供匹配的数据类型参数。 例子3.3
发送者和接收者指定无类型值的通讯。 CALL MPI_COMM_RANK(comm, rank, ierr)
IF(rank.EQ.0) THEN CALL MPI_SEND(a(1), 40, MPI_BYTE, 1, tag, comm, ierr)
ELSE CALL MPI_RECV(b(1), 60, MPI_BYTE, 0, tag, comm, status, ierr) END
IF 无论a和b的尺寸和类型如何, 这个程序是正确的( 这个程序导致存储器存取越界)。
给用户的建议.如果类型为MPI_BYTE的一个缓存作为一个参数传送给MPI_SEND,那么MPI将发送起始地址为buf连续存储空间的数据。当数据布局不是用户所希望的类型时,这可能导致不希望的结果。例如,一些Fortran编译器把类型为CHARACTER的变量作为一个结构来实现,
它包含字符长度和对实际串的指针。在这个环境中,使用MPI_BYTE类型的发送和接收一个Fortran的CHARACTER型变量,
将没有所希望的转换字符串的结果。因为这个,建议用户可能时使用有类型的通讯。(给用户的建议结束。)
类型MPI_CHARACTER 类型MPI_CHARACTER匹配Fortran的类型为CHARACTER的一个变量中的一个字符,而不是存储在这个变量中的全部字符。类型为CHARACTER的Fortran得变量或子串好象字符数组一样被传送。在下面的例子中解释这个。
例子3.4 传送Fortran的CHARACTER类型 CHARACTER*10 a CHARACTER*10 b CALL MPI_COMM_RANK(comm,
rank, ierr) IF (rank.EQ.0) THEN CALL MPI_SEND(a, 5, MPI_CHARACTER, 1, tag,
comm, ierr) ELSE CALL MPI_RECV(b(6:10), 5, MPI_CHARACTER, 0, tag, comm,
status, ierr) END IF 进程1中的字符串b的最后五个字符被进程0中的字符串a的前五个字符替代。
基本原理. 另一个选择是对于MPI_CHARACTER类型匹配任意长度的一个字符。 这有时出现错误。
Fortran的一个字符变量是定长的字符串, 没有特别的终结符号。关于怎样表 示字符、怎样存储他们的长度没有固定的约定。有些编译器把一个字符参数作 为一对参数传送给一个程序,其中之一是保存这个串的地址,另一个是保存串 的长度。考虑下面的情况,把导出数据类型(看3.12节)定义的一个通讯缓存传
送给一个通讯调用。如果这个通讯缓存包含CHARACTER类型的变量, 那么关于 他们的长度将不被传送给MPI程序。
这个问题迫使我们给MPI调用提供显式的字符长度的信息。你可以给类型 MPI_CHARACTER
加一个长度参数, 但这并不方便, 通过定义一个适当的导出数 据类型能得到同样的功能。(基本定理结束)。
给用户的建议. 有些编译器把Fortran中CHARACTER类型的参数作为一个结构
传给实际串一个长度和一个指针。在这样的环境中,MPI调用为得到这个串须 间接引用这个指针。(给用户的建议结束)。
3.3.2 数据转换 MPI的目的之一是支持异构环境的并行编译。在一个异构环境的通讯可以要求数据转换。我们使用下面术语。
type conversion ( 类型转换 )改变一个值的数据类型, 例如,通过给一个实型四舍五入成为一个整型。
representation conversion ( 表示转换 )改变一个值的二进制表示, 例如,从十六进制浮点到IEEE浮点。
类型匹配规则意谓着MPI通讯不承担类型转换。另一方面,MPI要求, 当在一个数据类型值使用不同表示的环境中传送有类型值时,
进行一个表示转换。MPI没指定表示转换的规则。希望这样的转换保留整型、逻辑或字符值,
而把浮点值转为在目标系统上能表示的最接近的值。 在浮点转换过程,上溢和下溢可能发生。当一个值能在一个系统中表示而不能在另一个系统中表示时,整型或字符的转换也可导致异常。在表示转换过程中,一个异外发生会导致一个通讯失败。在发送操作、或接收操作或两者也都发生错误。
如果在一个消息中发送的一个值是无类型的 (例如,MPI_BYTE类型), 那么在接收者存储的字节的二进制表示与接收者载入的字节的二进制表示一样。无论发送者和接收者运行在同一环境或不同环境,这都是正确的。不要求表示转换。(
注意,当转换MPI_CHARACTER类型或MPI_CHAR类型值时, 表示转换可以发生, 例如,
从一个EBCDIC编码到ASCII编码)。 当一个MPI程序在同构系统中运行, 其所有进程运行在同一环境时,
没有转换发生。 考虑三个例子,3.1-3.3。假定a和b是实型的尺寸大于10的数组,
第一个程序是正确的。如果发送者和接收者在不同的环境中执行,那么从发送缓存取出的10个实型值,
在存到接收缓存以前被转换为接收者的实型表示。当从发送缓存取出的实型元素的个数等于接收缓存所存的实型元素的个数时,存储的字节数不必等于载入的字节数。例如,发送者可以使用四个字节表示实型数,接收者使用八个子字节表示实型数。
第二个程序是错误的,它的动作无定义。 第三个程序是正确的。既使发送者和接收者在不同的环境运行,从发送缓存装入的四十个字节将被存在接收缓存。发送的消息与接收的消息有完全相同的长度(
按字节)和相同的二进制表示。如果a和b是不同的类型, 或他们类型相同但使用不同的数据表示,
那么接收缓存所存的各位可以译码出不同于发送缓存译码值。 数据表示的转换也应用于一个消息的信封:源,目的和标识需要被转换为整型。
给实现者的建议. 现在的定义没有要求消息携带数据类型的信息。发送者和接收者都提供完全的数据类型信息。在一个异构环境中,你能使用独立于机器的编码如XDR,
或接收者把发送者的表示转为它自己的表示, 或发送者完成这个转换。 为允许系统检查发送者和接收者之间的数据类型不匹配,可以给消息加附加的类型信息。在一个较慢的但较安全的调试模式下,这是特别有用的。(给用户的建议结束)。
MPI不要求支持语言间的通讯。如果由一个C语言进程发送的消息而由一个Fortran语言的进程接收,
或相反, 那么这个程序的行为无动作定义。 基本原理. MPI没有处理语言间的通讯,
因为C语言的类型与Fortran语言的类型的一致性没有被同意的标准。因此,混合语言的MPI程序没有接口。(基本原理结束)。
给实现者的建议. MPI实现者通过允许Fortran程序使用“C MPI类型”例如,MPI_INT,
MPI_CHAR, 等等, 允许C程序使用Fortran类型, 想支持语言间通讯。(给实现者的建议结束)。
3.4 通讯模式 在3.2.1节描述的发送调用是阻塞的: 它直到消息数据和信封已被安全取出和存储才返回以便发送者自由存取和重写发送缓存。消息可能被直接拷贝到匹配的接收缓存,或被拷贝到一个暂时的系统缓存。
消息缓存退耦发送和接收操作。只要消息被缓存起来,既使接收者没有执行匹配接收,一个阻塞发送能完成。另一方面,消息缓存是昂贵的,因为它承担附加的存储器到存储器拷贝,而且它要求为缓存分配存储空间。MPI提供几个通讯模式选择,
使你控制通讯协议的选择。 在3.2.1节描述的发送调用使用标准的通讯模式。在这种模式中,由MPI决定正在出发的消息是否被缓存。MPI可以缓存正出发的消息。这时,在引用一个匹配接收以前,发送调用可以完成。另一方面,缓存空间可以得不到,或因性能原因,
MPI可以选择不缓存正出发的消息。这时,发送调用直到一个匹配接收登入并且数据被移入接收者才完成。
所以,无论一个匹配接收是否登入,一个标准模式的发送能开始。它可以在匹配接收登入以前完成。标准模式的发送是非局部的:发送操作成功的完成依赖于一个匹配接收的发生。
基本原理. MPI不愿规定标准发送是否是缓存的原因是希望达到可移植性程序。因为虽着消息尺寸的增大,
任何系统将用完缓存资源, 有些实现想提供较少的缓存,在标准模式下, MPI负责使程序不依赖系统缓存。缓存可以提高一个正确程序的性能,但它不影响程序的结果。如果用户希望保证一定数量的缓存,可以使用3.6节提供的缓存系统,
和缓存模式发送。(基本原理结束)。 有三个附加的通讯模式。 无论一个匹配接收是否已登入,能够开始一个“缓存模式”的发送操作。它可以在一个匹配接收登入已前完成。但是,不象标准发送,这个操作是局部的,它的完成不依赖一个匹配接收的发生。因此,如果执行一个发送而没有匹配接收登入,那么MPI必须缓存正出发的消息,
以便允许发送调用完成。如果没有充足的缓存空间,一个错误将发生。可得到的缓存空间数量由用户控制--看3.6节。为使缓存模式有效可以要求用户分配缓存。
无论一个匹配接收是否登入,能开始“同步模式”的一个发送操作。但是,只有一个匹配接收登入,接收操作已开始接收同步发送的消息时,发送操作将成功完成。所以,一个同步发送的完成不表示发送缓存能被再使用,但是表明接收者已到达执行的某一点,即它已开始执行匹配接收。如果发送和接收都是阻塞操作,那么同步模式的使用提供同步语义:两个进程在通讯时聚会以前,一个通讯不能完成。在这个模式下执行的一个发送是非局部的。
只要匹配接收已登入,可以开始一个“准备好通讯”模式的发送。否则,这个操作是错误的,其结果是无定义的。在某些系统,这允许移出一个信号交换式操作,并导致提高性能。发送操作的完成不依赖一个匹配接收的状态,只表明发送缓存能被再使用。准备好模式的一个发送操作,与一个标准发送操作或一个同步发送操作有相同的语义;只是发送者给系统提供附加的信息(
即:一个匹配接收已登入 ),能节省一些额外开销。因此,在一个正确的程序中,一个准备好发送能被一个标准发送替代,对程序的动作无影响而对性能有影响。
为三个附加的通讯模式提供三个附加的发送函数。由一个字母前缀表示通讯模式:B
用于“缓存模式”,S 用于“同步模式”,R 用于“准备好模式”。 MPI_BSEND(buf,
count, datatype, dest, tag, comm) IN buf 发送缓存的初始地址(选择型) IN
count 发送缓存中元素的个数(整型) IN datatype 每个发送缓存元素的数据类型(句柄)
IN dest 目标进程号(整型) IN tag 消息标识(整型) IN comm 通讯组(句柄) int
MPI_Bsend(void* buf, int count, MPI_Datatype datatype, int dest, int tag,
MPI_Comm comm) MPI_BSEND(BUF, COUNT, DATATYPE, DEST, TAG, COMM, IERROR)
BUF(*) INTEGER COUNT, DATATYPE, DEST, TAG, COMM, IERROR 以缓存模式发送。
MPI_SSEND(buf, count, datatype, dest, tag, comm) IN buf 发送缓存的初始地址(选择型)
IN count 发送缓存中元素的个数(整型) IN datatype 每个发送缓存元素的数据类型(句柄)
IN dest 目标进程号(整型) IN tag 消息标识(整型) IN comm 通讯组(句柄) int
MPI_Ssend(void* buf, int count, MPI_Datatype datatype, int dest, int tag,
MPI_Comm comm) MPI_SSEND(BUF, COUNT, DATATYPE, DEST, TAG, COMM, IERROR)
BUF(*) INTEGER COUNT, DATATYPE, DEST, TAG, COMM, IERROR) 以同步模式发送。
MPI_RSEND(buf, count, datatype, dest, tag, comm) IN buf 发送缓存的初始地址(选择型)
IN count 发送缓存中元素的个数(整型) IN datatype 每个发送缓存元素的数据类型(句柄)
IN dest 目标进程号(整型) IN tag 消息标识(整型) IN comm 通讯组(句柄) int
MPI_Rsend(void* buf, int count, MPI_Datatype datatype, int dest, int tag,
MPI_Comm comm) MPI_RSEND(BUF, COUNT, DATATYPE, DEST, TAG, COMM, IERROR)
BUF(*) INTEGER COUNT, DATATYPE, DEST, TAG, COMM, IERROR 以准备好模式发送。
只有一个接收操作,能和任何发送操作模式匹配。在上节描述的接收操作是阻塞的:只有接收缓存包含新接收消息时,
它返回。在匹配的发送已完成以前,一个接收能完成( 当然, 只在匹配发送已开始后,
它能完成。) 在MPI的多线索实现中, 系统可以再调度一个阻塞发送或接收操作的线索,
并调度在同一地址执行的另一线索。这时,到通讯完成时,用户才负责存取或修改一个通讯缓存。否则,计算的结果是无定义的。
基本原理. 当一个发送缓存正在被使用时, 我们禁止读取这个发送缓存, 既使这个发送操作可能不改变这个缓存的内容。这似乎比要求的更严格,但在某些系统,这可减少功能损失,并允许高性能--考虑由DMA发动完成的数据转换情况,DMA发动是和主处理器非紧耦的高缓存。(基本原理结束)。
给实现者的建议. 因为在一个匹配接收登入以前, 一个同步发送不能完成, 所以你将不能正常暂存这样操作发送的消息。
只要可能,为标准发送在阻塞发送者上,选择缓存。编程者通过使用同步发送模式,在一个匹配接收发生以前能为阻塞发送者发优选信号。
下面概述各种通讯模式的一个可能的通讯协议。 ready send(准备好发送):
只要一可能, 消息就被发送。 synchronous send(同步发送): 发送者发一个“请求发送”的消息。接收者存储这个请求。当一个匹配接收登入时,接收者发回一个“允许发送”的消息,这时接收者发送消息。
standard send: 第一个协议用于短的消息, 第二个协议用于长的消息。 buffered
send: 发送者把消息拷贝到一个缓存, 然后以非阻塞方式发送它(使用与标准发送同样的协议)
为了流控制和错误发现, 需要附加的控制信息。当然,有许多其他可能的协议。 准备好发送被作为一个标准发送被实现。这时,准备好发送没有优点(或缺点)。
一个标准发送能作为一个同步发送被实现。这时,不需要数据缓存。但是,许多(大多数?)用户希望一些缓存。
在多线索环境, 一个阻塞通讯的执行只阻塞执行线索, 允许线索调度器再调度这个线索并调度另一个执行线索。(给用户的建议结束)。
3.5 点对点通讯的语义 在这节描述点对点通讯, 一个有效的MPI实现保证它的一般特性。
Order 消息是“非超过的”:如果一个发送者给同一目的连续发送两个消息,,并且匹配同一接收,而且如果第一个消息仍挂起,那么这个接手操作不能接收第二个消息。如果一个接收者连续登入两个接收并匹配同一消息,而且如果第一个接收仍挂起,那么这个消息不能完成第二个接收操作。这要求发送到接收的匹配设施。如果进程是单线索的,并且不使用任意值MPI_ANY_SOURCE的接收,那么它保证消息传送代码是确定的。(后面描述的一些调用,
例如:MPI_CANCEL或MPI_WAITANY, 是非确定性的附加源)。 如果一个进程是单线索执行的,那么这个进成执行的任何两个通讯是排序的。另一方面,如果进程是多线索的,那么线索执行的语义在由两个不同线索执行的两个发送操作之间可以不定义相对顺序。既使物理上一操作先于另一个,逻辑上操作是并发的。这时,被发送的两个消息以任何顺序被接收。类似地,如果逻辑上是并发的两个接收操作连续接收两个被发送的消息,那么这两个消息能在顺序上匹配被接收的两个消息。
例子3.5 一个“非超过的”消息例子。 CALL MPI_COMM_RANK(comm, rank, ierr)
IF (rank.EQ.0) THEN CALL MPI_BSEND(buf1, count, MPI_REAL, 1, tag, comm,
ierr) CALL MPI_BSEND(buf2, count, MPI_REAL, 1, tag, comm, ierr) ELSE !
rank.EQ.1 CALL MPI_RECV(buf1, count, MPI_REAL, 0, MPI_ANY_TAG, comm, status,
ierr) CALL MPI_RECV(buf2, count, MPI_REAL, 0, tag, comm, status, ierr)
END IF 由第一个发送者发送的消息必须被第一个接收者接收, 而由第二个发送者发送者发送的消息必须被第二个接收者接收。
Progress 如果一对匹配的发送和接收已被在两个进程上初始化, 那么至少这两个操作之一将独立于系统的其他动作而完成:
除非接收被另一个消息满足并完成, 这个发送操作将完成; 除非在同一目标进程登入的另一匹配进程使用这个发送的消息,这个接收操作将完成。
例子3.6 混在一起的匹配的一对发送和接收。 CALL MPI_COMM_RANK(comm, rank,
ierr) IF (rank.EQ.0) THEN CALL MPI_BSEND(buf1, count, MPI_REAL, 1, tag1,
comm, ierr) CALL MPI_SSEND(buf2, count, MPI_REAL, 1, tag2, comm, ierr)
ELSE ! rank.EQ.1 CALL MPI_RECV(buf1, count, MPI_REAL, 0, tag2, comm, status,
ierr) CALL MPI_RECV(buf2, count, MPI_REAL, 0, tag1, comm, status, ierr)
END IF 两进程首先引用他们的第一个通讯调用。因为进程0的第一个发送使用缓存模式,
所以无论进程1的状态如何, 它必须完成。因为没有匹配接收登入,消息将被拷贝到缓存空间。(如果得不到足够的缓存空间,
那么程序将失败。)然后引用第二个发送。在这点,使发送和接收操作匹配,两操作必须完成。进程1引用它的第二个接收调用,这个调用将被缓存的消息满足。注意,进程1以消息发送的相反顺序接收消息。
Fairness MPI不保证通讯处理中的公正性。假设一个发送被登入。则目标进程可能重复登入匹配这个发送的接收,但仍没有接收这个消息,因为每次被另一个发送的消息超过。类似地,假设一个多线索登入一个接收。那么匹配这个接收的消息可能被重复接收,但仍没有满足这个接收,因为它被这个结点登入的其他接收超过(其他执行线索)。这种情况下,由用户负责阻止。
Resource limitations( 资源限制 )任何挂起的通讯操作使用有限的系统资源。当缺少资源而阻止一个MPI调用执行时,
可能发生错误。一个高质量实现给“准备好模式”或“同步模式”的每个挂起发送,给每个挂起接收使用(小的)定数资源。但是,缓存空间用于存储标准模式发送的消息,并且当得不到匹配接收时,必须用于存储以缓存模式发送的消息。在许多系统中,缓存得到的空间将远远小于程序数据存储空间。那么,容易编写超出可得缓存空间的程序。
MPI允许用户给以缓存模式发送的消息提供缓存空间。MPI也为这个缓存的使用指定详细的操作模型。要求一个MPI实现不比这个模型更坏。当用户使用缓存发送时,这使他们避免缓存溢出。在3.6节描述缓存的分配和使用。
由于缺少缓存空间而不能完成的一个缓存发送操作是错误的。当检测到这种情况时,可引起程序非正常结束的一个错误被通知。另一方面,由于缺少缓存空间而不能完成的一个标准发送将阻塞,等待缓存空间空出或一个匹配接收登入。在许多情况下,这个动作是受欢迎的。考虑这样一种情况,一个生产者重复生产新值并把他们发送给一个使用者。假设生产者产生值比使用者使用的快。如果使用缓存发送,将导致一个缓存溢出。给这个程序加一个附加的同步以阻止这种情况发生。如果使用标准发送,那么生产者将被自动停止,就象当缓存空间得不到时,它的发送操作将阻止。
有些情况,缺少缓存空间导致死索情况。由下面的例子解释。 例子3.7 一个消息的交换。
CALL MPI_COMM_RANK(comm, rank, ierr) IF (rank.EQ.0) THEN CALL MPI_SEND(sendbuf,
count, MPI_REAL, 1, tag, comm, ierr) CALL MPI_RECV(recvbuf, count, MPI_REAL,
1, tag, comm, status, ierr) ELSE ! rank.EQ.1 CALL MPI_RECV(recvbuf, count,
MPI_REAL, 0, tag, comm, status, ierr) CALL MPI_SEND(sendbuf, count, MPI_REAL,
0, tag, comm, ierr) END IF 既使得不到数据缓存空间, 这个程序将成功。在这个例子中,用一个同步发送替代这个标准发送操作。
例子3.8 打算交换消息。 CALL MPI_COMM_RANK(comm, rank, ierr) IF (rank.EQ.0)
THEN CALL MPI_RECV(recvbuf, count, MPI_REAL, 1, tag, comm, status, ierr)
CALL MPI_SEND(sendbuf, count, MPI_REAL, 1, tag, comm, ierr) ELSE ! rank.EQ.1
CALL MPI_RECV(recvbuf, count, MPI_REAL, 0, tag, comm, status, ierr) CALL
MPI_SEND(sendbuf, count, MPI_REAL, 0, tag, comm, ierr) END IF 第一个进程的接收操作必须在它的发送以前完成,
并只要第二个进程的匹配发送操作被执行, 就能完成。第二个进程的接收操作必须在它的发送以前完成,并只要第一个进程的匹配发送被执行,就能完成。这个程序将总是死锁。其他的发送模式也是一样。
例子3.9 依赖缓存的一个交换。 CALL MPI_COMM_RANK(comm, rank, ierr) IF (rank.EQ.0)
THEN CALL MPI_SEND(sendbuf, count, MPI_REAL, 1, tag, comm, ierr) CALL MPI_RECV(recvbuf,
count, MPI_REAL, 1, tag, comm, status, ierr) ELSE ! rank.EQ.1 CALL MPI_SEND(sendbuf,
count, MPI_REAK, 0, tag, comm, status, ierr) END IF 由每个进程发送的消息必须在发送操作返回以前和接收操作开始以前被拷贝出来。为完成这个程序,至少被发送的两个消息之一必须被缓存。所以,只要通讯系统至少缓存count个数据,
这个程序就能成功。 给用户的建议. 当使用标准发送操作时, 那么由于没有缓存空间两个进程被阻塞时,
一个死锁可发生。如果使用同步模式,同样将发生死锁。如果使用缓存模式,而没有足够空间,那么这个程序将也不能完成。但不是一个死锁情况,而是一个缓存溢出错误。
如果程序完成不要求消息缓存,那么这个程序是“安全”的。你能用同步发送替代这个程序中的全部发送,这个程序仍将正确地完成。这个守恒的编程风格提供了最好的移植性,因为程序的完成不依赖缓存空间的数量或使用的通讯协议。
许多编程者喜欢更多的选择余地,而使用3.9例子中的“不安全”的编程风格。这时,标准发送的使用可提供性能和坚固之间的最好的一致性:高质量的实现将提供充足的缓存以便“一般实际的”程序将不死锁。缓存发送模式能用于程序要求更多缓存,或编程者希望更多控制的情况。这种模式也用于调试目的,就象缓存溢出情况比死锁情况易于诊断。
3.7节描述的非阻塞消息传送操作能被用于避免必须缓存正出发的消息。这阻止由于缺少缓存空间造成的死锁,通过允许计算和通讯的重叠,避免分配缓存和拷贝消息到缓存的额外负担,提高了性能。(给用户的建议结束)。
3.6 缓存分配和使用方法 一个用户可指定一个缓存用于缓存以缓存模式发送的消息。由发送者完成缓存。
MPI_BUFFER_ATTACH( buffer, size) IN buffer 初始缓存地址(选择类型) IN size
按字节计数缓存尺寸(整型) int MPI_Buffer_attach( void* buffer, int size)
MPI_BUFFER_ATTACH( BUFFER, SIZE, IERROR) BUFFERR(*) INTEGER SIZE,
IERROR 给MPI在用户存储空间提供一个缓存, 用于缓存正出发的消息。这个缓存只能用于以缓存模式发送的消息。同一时间一个进程只能联结一个缓存。
MPI_BUFFER_DETACH( buffer, size) OUT buffer 初始缓存地址(选择类型) OUT
size 安字节计数缓存尺寸(整型) int MPI_Buffer_detach( void** buffer, int*
size) MPI_BUFFER_DETACH( BUFFER, SIZE, IERROR) BUFFER(*) INTEGER
SIZE, IERROR 解出与MPI当前联结的缓存。这个操作将阻塞,一直到当前在缓存中全部消息已被传送完。在这个函数返回时,用户可以再使用或解出分配这个缓存所占用的空间。
这节的语句描述了MPI缓存模式发送的行为。当没有缓存被联结时,MPI以零尺寸缓存与进程联结。
在指定的缓存空间, 使用一个环型的连续空间分配策略, MPI必须为正出发的消息提供多缓存,
正出发的消息数据被发送进程缓存。下面我们概述一个定义这个策的模型实现。MPI可以提供更多的缓存,
可使用比下面描述的更好的缓存分配算法。另一方面,当下面描述的简单的缓存分配器用完空间时,MPI可发错误信号。特别是,如果与进程没有显式联结的缓存,那么任何缓存型发送可引起一个错误。 MPI不给标准模式发送的缓存提供查寻或控制机制。生产商将给他们的实现提供这样的信息。
基本原理. 缓存通讯有一个广泛的可能实现的范围: 缓存可以在发送者完成,在接收者完成或两者都完成;缓存能被指定给一对发送-接收,或由所有通讯共享;可以实存或虚存实现缓存;它可以用指定的存储器或由其他进程共享的存储器;缓存空间可以被静态地分配或被动态地修改;等等。给和所有这些选择兼容的缓存提供查寻和控制的可移植机制,并提供有意义的信息,这似乎是不容易的。(基本原理结束)。
3.6.1 缓存模式的模型实现 模型实现使用3.13节描述的打包和拆包函数, 以及3.7节描述的非阻塞通讯函数。
我们假设保留挂起消息输入(PME)的一个环形队列。每个输入包含一个通讯请求句柄,用于识别一个挂起的非阻塞发送,还包含一个指向下一个输入的指针及被打包的消息数据。输入被存在缓存中的连续位置。在队尾和队头之间可得到空闲空间。
一个缓存发送的调用导致下列代码的执行。 . 从头到尾顺序扫描PME队列,
删出已完成通讯的所有输入, 直到第一个未完成请求的输入; 修改队头指向这个输入。
. 计算存储新消息输入需要的字节数n (用MPI_PACK_SIZE计算打包消息的长度加上请求句柄和指针的空间)。
. 在缓存中找到下一个n字节长的连续空闲空间(跟在队尾的空间, 或如果队尾太接近缓存末尾,
缓存的起始空间)。如果空间没有找到,那么提出缓存溢出错。 . 给在连续空间的PME队尾加这个新输入,
包含请求句柄, 下一个指针和打包的消息数据; MPI_PACK用于打包数据。 .
邮出打包数据的非阻塞发送( 标准模式 )。 . 返回 3.7 非阻塞通讯 通过重叠通讯和计算在许多系统能提高性能。由一个智能通讯控制器自动地执行通讯的系统是真实的。轻-重线索是取得这种重叠的一种机制。导致好性能的一个可选的机制是使用非阻塞通讯。一个阻塞发送开始调用初始化这个发送操作,但不完成它。在这个消息被从这个发送缓存拷出以前,这个发送开始调用将返回。需要一个独立的“发送完成”调用完成这个通讯,例如,检验从发送缓存拷出的数据。用适当的硬件,在发送被初始化后和它完成以前,来自发送者存储的数据转换可以和在发送者完成的计算同时进行。类似地,一个非阻塞“接收开始调用”初始化这个接收操作,
但不完成它。在一个消息被存入这个接收缓存以前,这个调用将返回。须要一个独立的“接收完成”调用完成这个接收操作,并检验被接收到这个接收缓存的数据。用适当的硬件,在接收操作初始化后和它完成以前,到接收者存储的数据转换可以和计算同时进行。非阻塞接收的使用虽着信息较早地在接收缓存位置被提供,也可以避免系统缓存和存储器到存储器拷贝。
非阻塞发送开始调用能使用与阻塞发送一样的四种模式: 标准, 缓存, 同步和准备好模式。这些具有同样的意义。无论一个匹配接收是否已登入,能开始除“准备好”以外的所有模式的发送;只要一个匹配接收已登入,就能开始一个非阻塞“准备好”发送。在所有情况下,发送开始调用是局部的:无论其它进程的状态如何,它立刻返回。如果这个调用使得一些系统资源用完,那么它将失败并返回一个错误代码。高质量的MPI实现应保证这种情况只在“病态”时发生。即,一个MPI实现将能支持大数量挂起非阻塞操作。
当数据已被从发送缓存拷出时,这个发送完成调用返回。它可以带有附加的意义,这取决于发送模式。
如果发送模式是“同步的”,那么只有一个匹配接收已开始这个发送才能完成。即,一个接收已被登入,并已和这个发送匹配。这时,这个发送完成调用是非局部的。注意,在接收完成调用发生以前,如果一个同步、非阻塞发送和一个非阻塞接收匹配,
它可以完成。(发送者一“知道”转换将结束,它就能完成,但在接收者“知道”转换将结束以前)。
如果发送模式是“缓存”,并没有挂起接收,那么消息必须被缓存。这时,发送完成调用是局部的,而且无论一个匹配接收的状态如何,它必须成功。
如果发送模式是标准的,同时这个消息被缓存,那么在一个匹配接收发生以前,发送结束调用可以返回。另一方面,发送完成直到一个匹配接收发生才可以完成,并且这个消息已被拷到接收缓存。
非阻塞发送能被用阻塞接收匹配,反过来也可以。 给用户的建议. 一个发送操作的完成,
对于标准模式可以被延迟, 对于同部模式必须延迟, 直到一个匹配接收登入。这两种情况下非阻塞发送的使用允许发送者提前于接收者进行,以便在两进程的速度方面,计算更容忍波动。
缓存和准备好模式中的非阻塞发送有一个更有限的影响。一可能一个非阻塞发送将返回,而一个阻塞发送将在数据被从发送者存储拷出后返回。只要在数据拷贝能和计算同时的情况下,非阻塞发送的使用有优点。
消息发送模式隐含着由发送者初始化通讯。当发送者初始化通讯(数据被直接移到接收缓存,
并不要求排队一个挂起发送请求) 时,如果一个接收已登入,这个通讯一般将有较低的额外负担。但是,只在匹配发送已发生后,一个接收操作能完成。当非阻塞接收等待发送时,没有阻塞接收,它的使用允许得到较低的通讯额外负担。(给用户的建议结束)。
3.7.1 通讯对象 非阻塞通讯使用隐含的“请求”对象,识别通讯操作,并用结束它的操作匹配初始化这个通讯的操作。这些是系统对象,由一个句柄存取。一个请求对象识别一个通讯操作的各种特性,例如发送模式,和它联结的通讯缓存,它的上下文,用于一个发送的标识和目的参数,或用于一个接收的标识和源参数。此外,这个对象存储关于这个挂起通讯操作状态的信息。
3.7.2 通讯初始化 我们使用与阻塞通讯一样的命名约定: 用于缓存(buffered)、同步(synchronous)、或准备好(ready)模式的一个前缀B,
S, 或R。此外,前缀I(immediate)表示这个调用是非阻塞的。 MPI_ISEND(buf, count,
datatype, dest, tag, comm, request) IN buf 发送缓存的起始地址(选择类型)
IN count 发送缓存中元素的个数(整型) IN datatype 每个发送缓存元素的数据类型(句柄)
IN dest 目的进程号(整型) IN tag 消息标识(整型) IN comm 通讯组(句柄) OUT
request 通讯请求(句柄) int MPI_Isend(void* buf, int count, MPI_Datatype
datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request) MPI_ISEND(BUF,
COUNT, DATATYPE, DEST, TAG, COMM, REQUEST, IERROR) BUF(*) INTEGER
COUNT, DATATYPE, DEST, TAG, COMM, REQUEST, IERROR 开始一个标准模式的非阻塞发送。
MPI_IBSEND(buf, count, datatype, dest, tag, comm, request) IN buf 发送缓存的起始地址(选择类型)
IN count 发送缓存中元素的个数(整型) IN datatype 每个发送缓存元素的数据类型(句柄)
IN dest 目的进程号(整型) IN tag 消息标识(整型) IN comm 通讯组(句柄) OUT
request 通讯请求(句柄) int MPI_Ibsend(void* buf, int count, MPI_Datatype,
datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request) MPI_IBSEND(BUF,
COUNT, DATATYPE, DEST, TAG, COMM, REQUEST, IERROR) BUF(*) INTEGER
COUNT, DATATYPE, DEST, TAG, COMM, REQUEST, IERROR) 开始一个缓存模式的非阻塞发送。
MPI_ISSEND(buf, count, datatype, dest, tag, comm, request) IN buf 发送缓存的起始地址(选择类型)
IN count 发送缓存中元素的个数(整型) IN datatype 每个发送缓存元素的数据类型(句柄)
IN dest 目的进程号(整型) IN tag 消息标识(整型) IN comm 通讯组(句柄) OUT
request 通讯请求(句柄) int MPI_Issend(void* buf, int count, MPI_Datatype,
datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request) MPI_ISSEND(BUF,
COUNT, DATATYPE, DEST, TAG, COMM, REQUEST, IERROR) BUF(*) INTEGER
COUNT, DATATYPE, DEST, TAG, COMM, REQUEST, IERROR 开始一个同步模式的非阻塞发送。
MPI_IRSEND(buf, count, datatype, dest, tag, comm, request) IN buf 发送缓存的起始地址(选择类型)
IN count 发送缓存中元素的个数(整型) IN datatype 每个发送缓存元素的数据类型(句柄)
IN dest 目的进程号(整型) IN tag 消息标识(整型) IN comm 通讯组(句柄) OUT
request 通讯请求(句柄) int MPI_Irsend(void* buf, int count, MPI_Datatype
datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request) MPI_IRSEND(BUF,
COUNT, DATATYPE, DEST, TAG, COMM, REQUEST, IERROR) BUF(*) INTEGER
COUNT, DATATYPE, DEST, TAG, COMM, REQUEST, IERROR 开始一个准备好模式非阻塞发送。
MPI_IRECV(buf, count, datatype, source, tag, comm, request) OUT buf 接收缓存的起始地址(选择类型)
IN count 接收缓存中元素的个数(整型) IN datatype 每个接收缓存元素的数据类型(句柄)
IN source 源进程号(整型) IN tag 消息标识(整型) IN comm 通讯组(句柄) OUT
request 通讯请求(句柄) int MPI_Irecv(void* buf, int count, MPI_Datatype
datatype, int source, int tag, MPI_Comm comm, MPI_Request *request) MPI_IRECV(BUF,
COUNT, DATATYPE, SOURCE, TAG, COMM, REQUEST, IERROR) BUF(*) INTEGER
COUNT, DATATYPE, SOURCE, TAG, COMM, REQUEST, IERROR 开始一个非阻塞接收。
这些调用分配一个通讯请求对象,把它和请求句柄(参数request)连接。之后,这个请求能被用于查寻这个通讯的状态或等待它的完成。
一个非阻塞发送调用表示系统可以开始从发送缓存拷贝数据。在一个非阻塞发送操作被调用之后,到发送完成时,发送者才能存取这个发送缓存的任何部分。
一个非阻塞接收调用表示系统可以开始把数据写入接收缓存。在一个非阻塞接收操作被调用以前,到接收完成时,这个接收者才能存取这个接收缓存的任何部分。
3.7.3 通讯完成 函数MPI_WAIT和MPI_TEST用于完成一个非阻塞通讯。一个操作的完成表示现在这个发送者可自由地修改在发送缓存(这个发送操作本身离开不改变的发送缓存的内容)中的数据。它不表示消息已被接收,而它可以被通讯子系统缓存。但是,如果使用一个同步模式的发送,那么发送操作的完成表示一个匹配接收被初始化了,并且这个匹配的接收最终将接收这个消息。
一个接收操作的完成表示这个接收缓存包含被接收的消息,接收者可以自由存取它,并设置状态对象。它不表示这个匹配发送操作已被完成(但当然表示这个发送已被初始化)。
我们将使用下面术语。一个null句柄是有值MPI_REQUEST_NULL的一个句柄。如果请求没有和任何正出发的通讯(看3.9节)联结,那么对它的一个持续的请求和这个句柄是不活动的(inactive)。如果一个句柄既不是null也不是不活动的,
那么它是活动的。 MPI_WAIT(request, status) INOUT request 请求(句柄) OUT
status 状态对象(状态类型) int MPI_Wait(MPI_Request *request, MPI_Status
*status) MPI_WAIT(REQUEST, STATUS, IERROR) INTEGER REQUEST, STATUS(MPI_STATUS_SIZE),
IERROR 当由request识别的操作完成时, 对MPI_WAIT调用返回。如果和这个请求联结的通讯对象已由一个非阻塞发送或接收调用创建,那么这个对象由对MPI_WAIT的调用解出分配并设置请求句柄为MPI_REQUEST_NULL。MPI_WAIT是一个非局部的操作。 调用在status中返回关于已完成操作的信息。一个接收操作的状态对象的内容能被以3.2.5节描述的方法存取。一个发送操作的状态对象可以被一个对MPI_TEST_CANCELLED(看3.8节)的调用来查寻。
允许用一个null或不活动的request参数调用MPI_WAIT。这种请况下,这个操作立刻返回。状态参数设为返回tag
= MPI_ANY_TAG, source = MPI_ANY_SOURCE,也内部地被构成, 以便对MPI_GET_COUNT和MPI_GET_ELEMENTS的调用返回count=0。
基本原理. 这和一个长度列表在功能上等价于MPI_WAITALL, 并加一些优雅。 以这种方式设置状态以便阻止由于无效信息的存取造成的错误。
在一个MPI_IBSEND后的MPI_WAIT的成功返回隐含着用户的发送缓存能被再使用
----例如, 数据已被发送出去或已被拷到与MPI_BUFFER_ATTACH联结的一个缓 存。注意,这时,我们不再取消这个发送(看3.8节)。如果从来没有一个匹配 接收登入,那么缓存不能被自由存取。这多少表明了对MPI_CANCEL(总能释放
被提交给通讯子系统的程序空间)所叙述目的的计数。(基本原理结束)。 给实现者的建议.
在一个多线索环境, 对MPI_WAIT的一个调用应仅阻止调用 线索, 允许线索调度者调度另一个执行线索。(给实现的建议结束)。
MPI_TEST(request, flag, status) INOUT request 通讯请求(句柄) OUT flag 如果操作完成则为真(逻辑型)
OUT status 状态对象(状态类型) int MPI_Test(MPI_Request,*request, int *flag,
MPI_Status *status) MPI_TEST(REQUEST, FLAG, STATUS, IERROR) LOGICAL FLAG
INTEGER REQUEST, STATUS(MPI_STATUS_SIZE), IERROR 如果由request识别的操作被完成,
那么对MPI_TEST的一个调用返回flag=true(真)。这时,状态变量设为包含关于已完成操作的信息;如果由一个非阻塞发送或接收创建这个通讯,那么它被解出分配并设请求句柄为MPI_REQUEST_NULL。否则,
这个调用返回flag = false(假)。这时,状态对象的值无定义。MPI_TEST是一个局部操作。
一个接收操作的返回状态对象带有能以3.2.5节描述方式存取的信息。一个发送操作的状态对象带有被对MPI_TEST_CANCELLED(看3.8)的调用存取的信息。
允许用一个null或不活动的request参数调用MPI_TEST。这时操作返回flag =false(假)。
函数MPI_WAIT和MPI_TEST能被用于完成发送和接收。 给用户的建议.
非阻塞MPI_TEST调用的使用允许用户在一个单个执行的线索 内调用可选择的行为。一个事件驱动的线索调度器能与对MPI_TEST的周期调用
竟争。(给用户的建议结束)。 例子3.10 非阻塞操作和MPI_WAIT简单的使用方法。
CALL MPI_COMM_RANK(comm, rank, ierr) IF(rank.EQ.0) THEN CALL MPI_ISEND(a(1),
10, MPI_REAL, 1, tag, comm, request, ierr) ****给屏蔽延迟作一些计算****
CALL MPI_WAIT(request, status, ierr) ELSE CALL MPI_IRECV(a(1), 15, MPI_REAL,
0, tag, comm, request, ierr) ****给屏蔽延迟作一些计算**** CALL MPI_WAIT(request,
status, ierr) END IF 如果不为等待所联结通讯的完成, 使用下面的操作能解出分配一个请求对象。
MPI_REQUEST_FREE(request) INOUT request 通讯请求(句柄) int MPI_Request_free(MPI_Request
*request) MPI_REQUEST_FREE(REQUEST, IERROR) INTEGER REQUEST, IERROR 为解出分配标记请求对象,
并设request为MPI_REQUEST_NULL。和这个请求联结一个正出发的通讯将被允许完成。只在它完成后,这个请求将被解出分配。
基本原理. 因为在发送方的性能和方便的原因, 提供了MPI_REQUEST_FREE机制。(基本原理结束)。
给用户的建议. 一旦对MPI_REQUEST的一个调用释放一个请求, 用对MPI_WAIT或MPI_TEST的调用检查所联结通讯的成功完成,
这是不可能的。如果在通讯期间一个错误后来发生,那么错误代码不能被返回给用户-这样的错误必须作为致命的。当使用MPI_REQUEST_FREE时,
怎样知道何时操作已被完成的问题出现。依赖程序的逻辑,程序用其他方法知道某个操作已被完成,这使MPI_REQUEST_FREE的使用成为实际。例如,当程序的逻辑是这样的,即接收者给被发送的消息发送一个回答时,释放激活的发送请求--回答的到达通知发送者发送已完成并能再使用这个发送缓存。当接收缓存没有方法检验这个接收已被完成,接收缓存不能被再使用时,一个激活的接收请求将不被释放。(给用户的建议结束)。
例如 3.11 使用MPI_REQUEST_FREE的一个例子。 CALL MPI_COMM_RANK(MPI_COMM_WORLD,
rank) IF(rank.EQ.0) THEN DO i=1, n CALL MPI_ISEND(outval, 1, MPI_real,
1, 0, req, ierr) CALL MPI_REQUEST_FREE(req, ierr) CALL MPI_IRECV(inval,
1, MPI_REAL, 1, 0, req, ierr) CALL MPI_WAIT(req, status, ierr) END DO ELSE
! rank.EQ.1 CALL MPI_IRECV(inva, 1, MPI_REAL, 0, 0, req, ierr) CALL MPI_WAIT(req,
status) DO I=1, n-1 CALL MPI_ISEND(outval, 1, MPI_REAL, 0, 0, req, ierr)
CALL MPI_REQUEST_FREE(req, ierr) CALL MPI_IRECV(inval, 1, MPI_REAL, 0,
0, req, ierr) CALL MPI_WAIT(req, status, ierr) END DO CALL MPI_ISEND(outval,
1, MPI_REAL, 0, 0, req, ierr) CALL MPI_WAIT(req, status) END IF 3.7.4 非阻塞通讯的语义
我们通过适当地扩展3.5节的定义来定义非阻塞通讯的语义。 Order(顺序) 按着调用初始化通讯的执行顺序安排非阻塞通讯操作的顺序。给非阻塞通讯用使用顺序的定义扩展3.5节的非赶超请求。
例子3.12 非阻塞操作排序的消息。 CALL MPI_COMM_RANK(comm, rank, ierr) IF
(RANK.EQ.0) THEN CALL MPI_ISEND(a, 1, MPI_REAL, 1, 0, comm, r1, ierr) CALL
MPI_ISEND(B, 1, MPI_REAL, 1, 0, comm, r2, ierr) ELSE ! rank.EQ.1 CALL MPI_IRECV(a,
1, MPI_REAL, 0, MPI_ANY_TAG, comm, r1, ierr) CALL MPI_IRECV(b, 1, MPI_REAL,
0, 0, comm, r2, ierr) END IF CALL MPI_WAIT(r1,status) CALL MPI_WAIT(r2,status)
进程0的第一个发送将匹配进程1的第一个接收, 既使在进程1执行接收以前, 两个消息已被发送。
Progress(进度) 除非这个发送被另一个接收满足, 如果一个匹配发送已被开始,
那么完成一个接收的对MPI_WAIT的一个调用将最终结束并返回。特别,如果这个匹配发送是非阻塞的,那么这个接收将完成,既使完成这个发送的发送者不执行调用。类似地,除非这个接收被另一个发送满足,
如果一个匹配接收已被开始, 一个完成发送的对MPI_WAIT的一个调用将最终返回,
既使没有完成这个接收的调用执行。 例子 3.13 进度语义的一个解释。 CALL MPI_COMM_RANK(comm,
rank, ierr) IF (rank.EQ.0) THEN CALL MPI_SSEND(a, 1, MPI_REAL, 1, 0, comm,
ierr) CALL MPI_SEND(b, 1, MPI_REAL, 1, 1, comm, ierr) ELSE ! rank.EQ.1
CALL MPI_IRECV(a, 1, MPI_REAL, 0, 0, comm, r, ierr) CALL MPI_RECV(b, 1,
MPI_REAL, 0, 1, comm, ierr) CALL MPI_WAIT(r, status, ierr) END IF 在一个正确的MPI实现中,
这个代码将不死索。进程0的第一个同步发送必须在进程1登入这个匹配接收后完成,
既使进程1还没有得到“完成等待调用”。所以,进程0将继续和执行第二个发送,
允许进程1完成执行。 如果一个完成一个接收的MPI_TEST用同一参数被重复地调用,
并且一个匹配发送已开始, 那么这个调用将最终返回flag = 真, 除非这个发送由另一个接收满足。如果完成一个发送的MPI_TEST用同一个参数重复地被调用,
并且一个匹配接收已被开始, 那么这个调用将最终返回flag = 真, 除非这个接收由另一个发送满足。
3.7.5 多路完成 能等待一个表中的任何、一些或全部操作的完成是方便的, 而不必等待一个特别消息。对MPI_WAITANY或MPI_TESTANY的一个调用能被用于等待几个操作之一的完成。对MPI_WAITALL或MPI_TESTALL的一个调用能被用于等待一个表中的所有挂起操作。对MPI_WAITSOME或MPI_TESTSOME的一个调用能被用于完成一个表中的所有操作。
MPI_WAITANY(count, array_of_requests, index, status) IN count 表长(整型)
INOUT array_of_requests 请求数组(句柄数组) OUT index 被完成操作的句柄索引(整型)
OUT status 状态对象(状态类型) int MPI_Waitany(int count, MPI_Request *array_of_requests,
int *indes, MPI_Status *status) MPI_WAITANY(COUNT, ARRAY_OF_REQUESTS, INDEX,
STATUS, IERROR) INTEGER COUNT, ARRAY_OF_REQUESTS(*), INDEX, STATUS(MPI_STATUS_SIZE), IERROR
阻止直到与数组联结的操作之一已被完成。如果多于一个操作能终结,那么这个是被任意选择的。在index中返回这个数组的那个请求的索引,
在status中返回正完成通讯的状态。(C语言中, 数组下标从0开始, Fortran中下标从1开始)如果一个非阻塞通讯操作已分配这个请求,那么解出分配并设置请求句柄为MPI_REQUEST_NULL。
这个array_of_requests表可以包含null或不活动句柄。如果这个表包含非活动句柄(表长0或所有输入是null或非活动),那么这个调用用index=MPI_UNDEFINED。
MPI_WAITANY(count, array_of_requests, index, status)的执行与MPI_WAIT(&array_of_requests[i],status)的执行有相同的作用,
这个i是index的返回值。有包含活输入的一个数组的MPI_WAITANY等价于MPI_WAIT。
MPI_TESTANY(count, array_of_requests, index, flag, status) IN count 表长(整型)
INOUT array_of_requests 请求数组(句柄数组) OUT index 被完成操作的索引,
或MPI_UNDEFINED如果没有被 完成的(整型) OUT flag 真, 如果一个操作完成(逻辑型)
OUT status 状态对象(状态类型) int MPI_Testany(int count, MPI_Request *array_of_requests,
int *index, int *flag, MPI_Status *status) MPI_TESTANY(COUNT, ARRAY_OF_REQUESTS,
INDEX, FLAG, STATUS, IERROR) LOGICAL FLAG INTEGER COUNT, ARRAY_OF_REQUESTS(*),
INDEX, STATUS(MPI_STATUS_SIZE), IERROR 检测与激活句柄联结的一个或没有操作的完成。前一种情况,它返回flag
= 真, 在index中返回这个数组中这个请求的下标, 并在status中返回那个操作的状态;
如果一个非阻塞通讯调用分配这个请求, 那么解出这个请求并设句柄为MPI_REQUEST_NULL。(在C语言中,
数组下标从0开始, Fortran中从1开始)在后一种情况, 它返回flag = 假, 在index中返回一个值MPI_UNDEFINED和status为无定义。这个数组可以包含null或不活动句柄。如果数组不包含活动句柄,那么这个调用立刻返回,flag
= 假, index = MPI_UNDEFINED, 和status无定义。 MPI_TESTANY(count, array_of_requests,
index, status)的执行与MPI_TEST(&array_of_requests[i], flag, status)
的执行有同样的作用, 对i=0,1 ,..., count-1, 以任意顺序, 直到一个调用返回flag
= 真, 或全部失败。在前一种情况,index设为i的最后值, 在后一种情况, 它被设为MPI_UNDEFINED。有包含活动输入的一个数组的MPI_TESTANY是等价于MPI_TEST。
MPI_WAITALL( count, array_of_requests, array_of_statuses) IN count
表长(整型) INOUT array_of_requests 请求数组(句柄数组) OUT array_of_statuses
状态对象的数组(状态数组) int MPI_Waitall(int count, MPI_Request *array_of_requests,
MPI_Status *array_of_statuses) MPI_WAITALL(COUNT, ARRAY_OF_REQUESTS, ARRAY_OF_STATUSES,
IERROR) INTEGER COUNT, ARRAY_OF_REQUESTS(*) INTEGER ARRAY_OF_STATUSES(MPI_STATUS_SIZE,*),
IERROR 阻止到与表中的活动句柄联结的所有通讯操作完成为止, 返回所有这些操作的状态(这包括表中没有活句柄的情况)。两个数组有相同的有效输入数。在数组array_of_statuses中的第i个输入设为第i个操作的返回状态。由非阻塞通讯操作创建的请求被解出分配,数组中相应的句柄设为MPI_REQUEST_NULL。这个表可以包含null或非活动句柄。这个调用在每个这样输入状态中返回tag
= MPI_ANY_TAG,source =MPI_ANY_SOURCE,并且每个状态输入也被构成,以便调用MPI_GET_COUNT和MPI_GET_ELEMENTS,
返回count = 0。 对于i = 0, ..., count-1,以任意顺序执行MPI_WAITALL(count,array_of_requests,array_of_statuses),
与MPI_WAIT(&array_of_request[i],&array_of_statuses[i])的执行有相同的作用。有一个长度的数组的MPI_WAITALL等价于MPI_WAIT。
MPI_TESTALL(count, array_of_requests, flag, array_of_statuses) IN count
表长(整型) INOUT array_of_requests 请求数组(句柄数组) OUT flag (逻辑型)
OUT array_of_statuses 状态对象数组(状态数组) int MPI_Testall(int count,
MPI_Request *array_of_requests, int *flag, MPI_Status *array_of_statuses)
MPI_TESTALL(COUNT, ARRAY_OF_REQUESTS, FLAG, ARRAY_OF_STATUSES, IERROR)
LOGICAL FLAG INTEGER COUNT, ARRAY_OF_REQUESTS(*), ARRAY_OF_STATUSES(MPI_STATUS_SIZE,*),
IERROR 如果与在数组中活动句柄联结的所有通讯都已完成, 返回flag = 真(这包括表中没有活动句柄的情况)。这时,每个相应于一个活动句柄请求的状态输入设为相应通讯的状态;如果由一个非阻塞通讯调用已分配一个请求,那么解出分配,并设句柄为MPI_REQUEST_NULL。相应于null和非活动句柄的每个状态输入设为返回tag
= MPI_ANY_TAG, source = MPI_ANY_SOURCE, 并被安装以便对MPI_GET_COUNT和MPI_GET_ELEMENTS的调用返回count
= 0。 否则,返回flag = false(假), 不修改请求, 状态输入的值无定义。这是一个局部操作。
MPI_WAITSOME(incount,array_of_requests,outcount,array_of_indices, array_of_statuses)
IN incount 请求数组的长度(整型) INOUT array_of_requests 请求数组(句柄数组)
OUT outcount 已完成请求的数目(整型) OUT array_of_indices 已完成操作的下标数组(整型数组)
OUT array_of_statuses 已完成操作的状态数组(状态数组) int MPI_Waitsome(intincount,MPI_Request
*array_of_request, int *outcount,int *array_of_indices, MPI_Status *array_of_statuses)
MPI_WAITSOME(INCOUNT, ARRAY_OF_REQUESTS, OUTCOUNT, ARRAY_OF_INDICES, ARRAY_OF_STATUSES,
IERROR) INTEGER INCOUNT, ARRAY_OF_REQUESTS(*), OUTCOUNT, ARRAY_OF_INDICES(*),
ARRAY_OF_STATUSES(MPI_STATUS_SIZE,*), IERROR 等待直到表中与活句柄联结的至少一个操作已完成。在outcount中返回来自已完成的表array_of-requests的请求数目。在数组array_of_indices的第一个outcount位置返回这些操作的下标(在数组array_of_requests中的下标;
C中从0开始下标, Fortran中从1开始下标)。在数组array_of_status的第一个outcount位置返回这些已完成操作的状态。如果已完成的一个请求已被一个非阻塞通讯调用分配,那么它被解出分配并设所联结的句柄为MPI_REQUEST_NULL。
如果表中不包含活动句柄, 那么这个调用立刻以outcount = 0 返回。 MPI_TESTSOME(incount,array_of_requests,outcount,array_of_indices, array_of_statuses)
IN incount 请求数组的长度(整型) INOUT array_of_requests 请求数组(句柄数组)
OUT outcount 已完成请求的数目(整数) OUT array_of_indices 已完成操作的下标数组(整型数组)
OUT array_of_statuses 已完成操作的状态对象数组(状态数组) int MPI_Testsome(int
incount, MPI_Request *array_of_requests, int *outcount, int*array_of_indices,
MPI_Status *array_of_statuses) MPI_TESTSOME(INCOUNT, ARRAY_OF_REQUESTS,
OUTCOUNT, ARRAY_OF_INDICES, ARRAY_OF_STATUSES, IERROR) INTEGER INCOUNT,
ARRAY_OF_REQUESTS(*), OUTCOUNT,ARRAY_OF_INDICES(*), ARRAY_OF_STATUSES(MPI_STATUS_SIZE,*),
IERROR 除了它立刻返回,行为象MPI_WAITSOME。如果没有操作被完成,它返回outcount
= 0。 MPI_TESTSOME是一个局部操作, 它立刻返回, 而如果传送给它至少包含一个活句柄的一个表时,
MPI_WAITSOME将阻塞直到一个通讯完成为止。两个调用完成一个合理的请求:如果在一个请求表中重复出现的一个接收请求传送给MPI_WAITSOME或MPI_TESTSOME,
并且一个匹配操作已被登入, 那么接收将最终成功, 除非发送被另一个接收满足;
发送操作类似。 给用户的建议. MPI_TESTSOME的使用比MPI_TESTANY的使用更有效。前者返回所有关于已完成通讯的消息,后者,为完成每个通讯要求一个新的调用。
有多路客户的一个服务能使用MPI_WAITSOME, 以免不服务任何客户。客户用服务请求给服务器发送消息。拥有一个接收请求的服务器为每个客户调用MPI_WAITSOME,
处理所用已完成的请求。如果用MPI_WAITANY调用替代, 那么当一个客户的请求总先存在时,
另一个客户不能被服务。(给用户的建议结束)。 给实现者的建议. MPI_TESTSOME尽可能与许多挂起通讯一样将完成。(给实现者的建议结束)。
例子3.14 客户-服务代码(不服务能发生)。 CALL MPI_COMM_SIZE(comm, size, ierr)
CALL MPI_COMM_RANK(comm, rank, ierr) IF(rank > 0) THEN ! 客户代码 DO
WHILE(.TRUE.) CALL MPI_ISEND(a, n, MPI_REAL, 0, tag, comm, request, ierr)
CALL MPI_WAIT(request, status, ierr) END DO ELSE ! rank=0 -- 服务器代码
DO i=1, size-1 CALL MPI_IRECV(a(1,i), n, MPI_REAL, 0, tag, comm, request_list(i),
ierr) END DO DO WHILE(.TRUE.) CALL MPI_WAITANY(size-1, request_list, index,
status, ierr) CALL DO_SERVICE(a(1,index)) !handle one message CALL MPI_IRECV(a(1,
index), n, MPI_REAL, 0, tag, comm, request_list(index), ierr) END DO END
IF 例子3.15 使用MPI_WAITSOME的同样的代码。 CALL MPI_COMM_SIZE(comm, size,
ierr) CALL MPI_COMM_RANK(comm, rank, ierr) IF(rank > 0) THEN ! 客户代码
DO WHILE(.TRUE.) CALL MPI_ISEND(a, n, MPI_REAL, 0, tag, comm, request,
ierr) CALL MPI_WAIT(request, status, ierr) END DO ELSE ! rank=0 -- 服务器代码
DO i=1, size-1 CALL MPI_IRECV(a(1,i), n, MPI_REAL, 0, tag, comm, request_list(i),
ierr) END DO DO WHILE(.TRUE.) CALL MPI_WAITSOME(size, request_list, numdone,
index_list, status_list, ierr) DO i=1, numdone CALL DO_SERVICE(a(1, index_list(i)))
CALL MPI_IRECV(a(1, index_list(i)), n, MPI_REAL, 0, tag, comm, request_list(i),
ierr) END DO END DO END IF_