MPI常用函數
- MPI_Init(&argc, &argv)
來初始化MPI環境,可能是一些全局變量的初始化。MPI程序的第一個調用,它完成MPI程序所有的初始化工作,所有MPI程序的第一條可執行語句都是這條語句。
- MPI_Comm_rank(communicator, &myid)
來獲取當前進程在通信器中具有的進程號。不同的進程就可以將自身和其它的進程區別開來,實現各進程的並行和協作。
- MPI_Comm_size(communicator, &numprocs)
來獲取通信器中包含的進程數目。不同的進程通過這一調用得知在給定的通信域中一共有多少個進程在並行執行。
- MPI_Finalize()
來結束並行編程環境。之后我們就可以創建新的MPI編程環境了。MPI程序的最后一個調用,它結束MPI程序的運行,它是MPI程序的最后一條可執行語句,否則程序的運行結果是不可預知的。
- int MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
將發送緩沖區中的count個datatype數據類型的數據發送到目的進程,目的進程在通信域中的標識號是dest,本次發送的消息標志是tag,使用這一標志,就可以把本次發送的消息和本進程向同一目的進程發送的其它消息區別開來。發送緩沖區是由count個類型為datatype的連續數據空間組成,起始地址為buf。注意這里不是以字節計數,而是以數據類型為單位指定消息的長度。
- int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int tag,
MPI_Comm comm, MPI_Status *status)
從指定的進程source接收消息,並且該消息的數據類型和消息標識和本接收進程指定的datatype和tag相一致,接收到的消息所包含的數據元素的個數最多不能超過count。
接收緩沖區是由count個類型為datatype的連續元素空間組成,由datatype指定其類型,起始地址為buf。如果一個短於接收緩沖區的消息到達,那么只有相應於這個消息的那些地址被修改。count可以是零,這種情況下消息的數據部分是空的。
通過對status.MPI_SOURCE,status.MPI_TAG和status.MPI_ERROR的引用,就可以得到返回狀態中所包含的發送數據進程的標識,發送數據使用的tag標識和本接收操作返回的錯誤代碼。
虛擬進程
虛擬進程(MPI_PROC_NULL)是不存在的假想進程,在MPI中的主要作用是充當真實進程通信的目或源,引入虛擬進程的目的是為了在某些情況下編寫通信語句的方便。當一個真實進程向一個虛擬進程發送數據或從一個虛擬進程接收數據時,該真實進程會立即正確返回,如同執行了一個空操作。
在很多情況下為通信指定一個虛擬的源或目標是非常方便的,這不僅可以大大簡化處理邊界的代碼,而且使程序顯得簡潔易懂。在捆綁發送接收操作中經常用到這種通信手段。一個真實進程向虛擬進程MPI_PROC_NULL發送消息時會立即成功返回;一個真實進程從虛擬進程MPI_PROC_NULL的接收消息時也會立即成功返回,並且對接收緩沖區沒有任何改變。
進程間的通信需要通過一個通信器來完成。MPI 環境在初始化時會自動創建兩個通信器,一個稱為 MPI_COMM_WORLD,它包含程序中的所有進程,另一個稱為 MPI_COMM_SELF,它是每個進程獨自構成的、僅包含自己的通信器。MPI 系統提供了一個特殊進程號 MPI_PROC_NULL,它代表空進程 (不存在的進程),與 MPI_PROC_NULL 進行通信相當於一個空操 作,對程序的運行沒有任何影響。
-
MPI_SENDRECV(sendbuf,sendcount,sendtype,dest,sendtag,recvbuf,recvcount,
recvtype, source,recvtag,comm,status)發送緩沖區起始地址(可選數據類型)
發送數據的個數(整型)
發送數據的數據類型(句柄)
目標進程標識(整型)
發送消息標識(整型)
接收緩沖區初始地址(可選數據類型)
最大接收數據個數(整型)
接收數據的數據類型(句柄)
源進程標識(整型)
接收消息標識(整型)
通信域(句柄)
返回的狀態(status)
MPI的消息傳遞過程可以分為三個階段:
(1)消息裝配,將發送數據從發送緩沖區中取出,加上消息信封等形成一個完整的消息。
(2)消息傳遞,將裝配好的消息從發送端傳遞到接收端。
(3)消息拆卸,從接收到的消息中取出數據送入接收緩沖區。
在這三個階段,都需要類型匹配:(1)在消息裝配時,發送緩沖區中變量的類型必須和相應的發送操作指定的類型相匹配;(2)在消息傳遞時,發送操作指定的類型必須和相應的接收操作指定的類型相互匹配;(3)在消息拆卸時,接收緩沖區中變量的類型必須和接收操作指定的類型相匹配。
歸納起來,類型匹配規則可以概括為:
有類型數據的通信,發送方和接收方均使用相同的數據類型。
無類型數據的通信,發送方和接收方均以MPI_BYTE作為數據類型。
打包數據的通信, 發送方和接收方均使用MPI_PACKED。
- double MPI_Wtime(void)
返回一個用浮點數表示的秒數, 它表示從過去某一時刻到調用時刻所經歷的時間。
如何計時
double starttime, endtime;
...
starttime = MPI_Wtime()
需計時部分
endtime = MPI_Wtime()
printf("That tooks %f secodes\n", endtime-starttime);
- int MPI_Get_processor_name ( char *name, int *resultlen)
在實際使用MPI編寫並行程序的過程中,經常要將一些中間結果或最終的結果輸出到程序自己創建的文件中,對於在不同機器上的進程,常希望輸出的文件名包含該機器名,或者是需要根據不同的機器執行不同的操作,這樣僅僅靠進程標識rank是不夠的,MPI為此提供了一個專門的調用,使各個進程在運行時可以動態得到該進程所運行機器的名字。MPI_GET_PROCESSOR_NAME調用返回調用進程所在機器的名字。
例子
#include "mpi.h"
#include <stdio.h>
int main(argc, argv)
int argc;
char **argv;
{
int rank, size, i, buf[1];
MPI_Status status;
MPI_Init( &argc, &argv );/*初始化*/
MPI_Comm_rank( MPI_COMM_WORLD, &rank );/*進程號*/
MPI_Comm_size( MPI_COMM_WORLD, &size );/*總的進程個數*/
if (rank == 0) {
for (i=0; i<100*(size-1); i++) {
MPI_Recv( buf, 1, MPI_INT, MPI_ANY_SOURCE,
MPI_ANY_TAG, MPI_COMM_WORLD, &status );/*使用任意源和任意標識接收*/
printf( "Msg=%d from %d with tag %d\n",
buf[0], status.MPI_SOURCE, status.MPI_TAG );
}
}
else {
for (i=0; i<100; i++) {
buf[0]=rank+i;
MPI_Send( buf, 1, MPI_INT, 0, i, MPI_COMM_WORLD );/*發送*/
}
}
MPI_Finalize();/*結束*/
}
講解:
在接收操作中,通過使用任意源和任意tag標識,使得該接收操作可以接收任何進程以任何標識發送給本進程的數據,但是該消息的數據類型必須和接收操作的數據類型相一致。
這里給出了一個使用任意源和任意標識的例子。其中ROOT進程(進程0)接收來自其它所有進程的消息,然后將各消息的內容,消息來源和消息標識打印出來。
規約函數
int MPI_Reduce(
void *input_data, /*指向發送消息的內存塊的指針 */
void *output_data, /*指向接收(輸出)消息的內存塊的指針 */
int count,/*數據量*/
MPI_Datatype datatype,/*數據類型*/
MPI_Op operator,/*規約操作*/
int dest,/*要接收(輸出)消息的進程的進程號*/
MPI_Comm comm);/*通信器,指定通信范圍*/
)
例子(梯形積分)
#include "stdafx.h"
#include <iostream>
#include<math.h>
#include "mpi.h"
using namespace std;
const double a = 0.0;
const double b = 3.1415926;
int n = 100;
double h = (b - a) / n;
double trap(double a, double b, int n, double h)
{
double*x = new double[n + 1];
double*f = new double[n + 1];
double inte = (sin(a) + sin(b)) / 2;
for (int i = 1; i<n + 1; i++) {
x[i] = x[i - 1] + h;
f[i] = sin(x[i]);
inte += f[i];
}
inte = inte*h;
return 0;
}
int main(int argc, char * argv[])
{
int myid, nprocs;
int local_n;
double local_a;
double local_b;
double total_inte;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myid); /* get current process id */
MPI_Comm_size(MPI_COMM_WORLD, &nprocs); /* get number of processes */
local_n = n / nprocs;
local_a = a + myid*local_n*h;
local_b = local_a + local_n*h;
double local_inte = trap(local_a, local_b, local_n, h);
/*
@local_inte:send buffer;
@total_inte:receive buffer;
@MPI_SUM:MPI_Op;
@dest=0,rank of the process obtaining the result.
*/
MPI_Reduce(&local_inte, &total_inte, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
if (myid == 0)
{
printf("integral output is %d", total_inte);
}
MPI_Finalize();
return 0;
}