MPI命令总结与Fortran和C语言通信详解
1. MPI命令概述
MPI(Message Passing Interface)是一种用于并行计算的标准,提供了一系列用于进程间通信的命令。下面将详细介绍MPI的各种命令及其功能。
1.1 点对点通信
1.1.1 MPI_Sendrecv_replace
该函数将缓冲区的内容发送到目标进程,并从源进程接收数据替换缓冲区的内容。其函数原型如下:
int MPI_Sendrecv_replace(
void *message, /* input/output */
int count, /* input */
MPI_Datatype datatype, /* input */
int dest, /* input */
int sendtag, /* input */
int source, /* input */
int recvtag, /* input */
MPI_Comm comm, /* input */
MPI_Status *status /* output */
)
1.2 集体通信
1.2.1 广播和屏障
-
MPI_Barrier
:阻塞
comm中的所有进程,直到每个进程都调用了该函数。函数原型为:
int MPI_Barrier(
MPI_Comm comm /* input */
)
-
MPI_Bcast
:将根进程(
root)的send_data内容广播到comm中的所有进程,包括根进程本身。函数原型为:
int MPI_Bcast(
void *send_data, /* input/output */
int count, /* input */
MPI_Datatype datatype, /* input */
int root, /* input */
MPI_Comm comm /* input */
)
1.2.2 散射和收集操作
-
MPI_Gather
:将
comm中每个进程的send_data收集到根进程的recv_data中。函数原型为:
int MPI_Gather(
void *send_data, /* input */
int sendcount, /* input */
MPI_Datatype sendtype, /* input */
void *recv_data, /* output */
int recvcount, /* input */
MPI_Datatype recvtype, /* input */
int root, /* input */
MPI_Comm comm /* input */
)
-
MPI_Gatherv
:与
MPI_Gather类似,但允许不同进程的数据具有不同的类型签名。函数原型为:
int MPI_Gatherv(
void *send_data, /* input */
int sendcount, /* input */
MPI_Datatype sendtype, /* input */
void *recv_data, /* output */
int *recvcounts, /* input array */
int *recvoffsets, /* input array */
MPI_Datatype recvtype, /* input */
int root, /* input */
MPI_Comm comm /* input */
)
-
MPI_Scatter
:将根进程的
send_data散射到comm中的每个进程。函数原型为:
int MPI_Scatter(
void *send_data, /* input */
int sendcount, /* input */
MPI_Datatype sendtype, /* input */
void *recv_data, /* output */
int recvcount, /* input */
MPI_Datatype recvtype, /* input */
int root, /* input */
MPI_Comm comm /* input */
)
-
MPI_Scatterv
:与
MPI_Scatter类似,但允许不同进程的数据具有不同的类型签名。函数原型为:
int MPI_Scatterv(
void *send_data, /* input */
int *sendcounts, /* input array */
int *sendoffsets, /* input array */
MPI_Datatype sendtype, /* input */
void *recv_data, /* output */
int recvcount, /* input */
MPI_Datatype recvtype, /* input */
int root, /* input */
MPI_Comm comm /* input */
)
-
MPI_Allgather
:收集
comm中所有进程的send_data到每个进程的recv_data中。函数原型为:
int MPI_Allgather(
void *send_data, /* input */
int sendcount, /* input */
MPI_Datatype sendtype, /* input */
void *recv_data, /* output */
int recvcount, /* input */
MPI_Datatype recvtype, /* input */
MPI_Comm comm /* input */
)
-
MPI_Allgatherv
:与
MPI_Allgather类似,但允许不同进程的数据具有不同的类型签名。函数原型为:
int MPI_Allgatherv(
void *send_data, /* input */
int sendcount, /* input */
MPI_Datatype sendtype, /* input */
void *recv_data, /* output */
int *recvcounts, /* input array */
int *offsets, /* input array */
MPI_Datatype recvtype, /* input */
MPI_Comm comm /* input */
)
-
MPI_Alltoall
:所有进程之间进行全对全的散射和收集操作,每个进程共享其他进程的
send_data。函数原型为:
int MPI_Alltoall(
void *send_data, /* input */
int sendcount, /* input */
MPI_Datatype sendtype, /* input */
void *recv_data, /* output */
int recvcount, /* input */
MPI_Datatype recvtype, /* input */
MPI_Comm comm /* input */
)
-
MPI_Alltoallv
:与
MPI_Alltoall类似,但允许不同进程的数据具有不同的类型签名。函数原型为:
int MPI_Alltoallv(
void *send_data, /* input */
int *sendcounts, /* input array */
int *sendoffsets, /* input array */
MPI_Datatype sendtype, /* input */
void *recv_data, /* output */
int *recvcounts, /* input array */
int *recvoffsets, /* input array */
MPI_Datatype recvtype, /* input */
MPI_Comm comm /* input */
)
1.2.3 归约操作
-
MPI_Reduce
:对
comm中每个进程的segment数据进行归约操作,结果存储在根进程的result中。函数原型为:
int MPI_Reduce(
void *segment, /* input */
void *result, /* output */
int count, /* input */
MPI_Datatype datatype, /* input */
MPI_Op operator, /* input */
int root, /* input */
MPI_Comm comm /* input */
)
-
MPI_Allreduce
:对
comm中每个进程的segment数据进行归约操作,结果存储在每个进程的result中。函数原型为:
int MPI_Allreduce(
void *segment, /* input */
void *result, /* output */
int count, /* input */
MPI_Datatype datatype, /* input */
MPI_Op operator, /* input */
MPI_Comm comm /* input */
)
-
MPI_Op_create
:创建一个用于
MPI_Allreduce的操作。fen是一个指向返回void的函数的指针,commute表示操作数是否可交换。函数原型为:
int MPI_Op_create(
MPI_User_function *fen, /* input */
int commute, /* input */
MPI_Op *operator /* output */
)
-
MPI_Op_free
:释放由
MPI_Op_create定义的操作。函数原型为:
int MPI_Op_free(
MPI_Op *operator /* input/output */
)
-
MPI_Reduce_scatter
:对
comm中每个进程的segment数据进行归约操作,并将结果散射到每个进程的recv_data中。函数原型为:
int MPI_Reduce_scatter(
void *segment, /* input */
void *recv_data, /* output */
int *recvcounts, /* input */
MPI_Datatype datatype, /* input */
MPI_Op operator, /* input */
MPI_Comm comm /* input */
)
-
MPI_Scan
:对
comm中每个进程的segment数据进行部分归约操作,结果存储在每个进程的result中。函数原型为:
int MPI_Scan(
void *segment, /* input */
void *result, /* output */
int count, /* input */
MPI_Datatype datatype, /* input */
MPI_Op operator, /* input */
MPI_Comm comm /* input */
)
1.3 预定义的归约操作
MPI提供了一系列预定义的归约操作,如下表所示:
| MPI操作 | 操作描述 |
| ---- | ---- |
| MPLMAX | 最大值 |
| MPLMIN | 最小值 |
| MPLSUM | 求和 |
| MPLPROD | 乘积 |
| MPLBAND | 布尔与 |
| MPLLAND | 逻辑与 |
| MPLBOR | 布尔或 |
| MPLLOR | 逻辑或 |
| MPLBXOR | 布尔异或 |
| MPLLXOR | 逻辑异或 |
| MPLMAXLOC | 最大值及其位置 |
| MPLMINLOC | 最小值及其位置 |
1.4 支持归约操作的MPI数据类型
MPI还提供了一系列用于归约操作的数据类型,如下表所示:
| MPI数据类型 | 等效的C数据类型 |
| ---- | ---- |
| MPLCHAR | 有符号字符 |
| MPLSHORT | 有符号短整型 |
| MPLINT | 有符号整型 |
| MPLLONG | 有符号长整型 |
| MPLFLOAT | 单精度浮点数 |
| MPLDOUBLE | 双精度浮点数 |
| MPLLONG_DOUBLE | 长双精度浮点数 |
1.5 通信器和通信器组
1.5.1 访问通信器组
-
MPI_Comm_group
:获取给定通信器关联的组。如果
comm是一个跨通信器,则返回本地组。函数原型为:
int MPI_Comm_group(
MPI_Comm comm, /* input */
MPI_Group *group /* output */
)
1.5.2 组比较和操作
-
MPI_Group_compare
:比较两个组
groupl和group2,结果可能为相同、相似或不相等。函数原型为:
int MPI_Group_compare(
MPI_Group groupl, /* input */
MPI_Group group2, /* input */
int *result /* output */
)
-
MPI_Group_difference
:比较两个组
groupl和group2,形成一个新组,该组包含groupl中不在group2中的元素。函数原型为:
int MPI_Group_difference(
MPI_Group groupl, /* input */
MPI_Group group2, /* input */
MPI_Group *group3 /* output */
)
-
MPI_Group_excl
:从
group中排除指定排名(ranks)的元素,形成一个新组group_out。函数原型为:
int MPI_Group_excl(
MPI_Group group, /* input */
int nr, /* input */
int *ranks, /* input array */
MPI_Group *group_out /* output */
)
- MPI_Group_free :释放一个组。函数原型为:
int MPI_Group_free(
MPI_Group group /* input */
)
-
MPI_Group_incl
:根据指定排名(
ranks)从group中选择元素,形成一个新组group_out。函数原型为:
int MPI_Group_incl(
MPI_Group group, /* input */
int nr, /* input */
int *ranks, /* input */
MPI_Group *group_out /* output */
)
-
MPI_Group_intersection
:求两个组
groupl和group2的交集,形成一个新组group_out。函数原型为:
int MPI_Group_intersection(
MPI_Group groupl, /* input */
MPI_Group group2, /* input */
MPI_Group *group_out /* output */
)
- MPI_Group_rank :返回调用进程在组中的排名。函数原型为:
int MPI_Group_rank(
MPI_Group group, /* input */
int *rank /* output */
)
- MPI_Group_size :返回组中元素(进程)的数量。函数原型为:
int MPI_Group_size(
MPI_Group group, /* input */
int *size /* output */
)
-
MPI_Group_union
:求两个组
groupl和group2的并集,形成一个新组group_out。函数原型为:
int MPI_Group_union(
MPI_Group groupl, /* input */
MPI_Group group2, /* input */
MPI_Group *group_out /* output */
)
1.5.3 管理通信器
-
MPI_Comm_compare
:比较两个通信器
comml和comm2,结果可能为相同、全等、相似或不相等。函数原型为:
int MPI_Comm_compare(
MPI_Comm comml, /* input */
MPI_Comm comm2, /* input */
int *result /* output */
)
-
MPI_Comm_create
:从输入的
comm和group创建一个新的通信器comm_out。函数原型为:
int MPI_Comm_create(
MPI_Comm comm, /* input */
MPI_Group group, /* input */
MPI_Comm *comm_out /* output */
)
- MPI_Comm_free :释放一个通信器。函数原型为:
int MPI_Comm_free(
MPI_Comm *comm /* input/output */
)
1.6 通信状态结构
MPI定义了一个通信状态结构
MPI_Status
,用于存储通信操作的状态信息。其定义如下:
typedef struct {
int count;
int MPI_SOURCE;
int MPI_TAG;
int MPI_ERROR;
int private_count;
} MPI_Status;
1.7 定时器、初始化和杂项
1.7.1 定时器
-
MPI_Wtick
:返回
MPI_Wtime的分辨率(精度),单位为秒。函数原型为:
double MPI_Wtick(void)
- MPI_Wtime :返回自上次本地调用该函数以来的挂钟时间,单位为秒。这是一个本地定时器,其他进程的调用不会影响本地计时。函数原型为:
double MPI_Wtime(void)
1.7.2 启动和结束
-
MPI_Abort
:终止
comm中的所有进程,并向调用进程返回一个错误代码。函数原型为:
int MPI_Abort(
MPI_Comm *comm, /* input */
int error_code /* input */
)
- MPI_Finalize :终止当前的MPI线程,并清理MPI分配的内存。函数原型为:
int MPI_Finalize(void)
- MPI_Init :启动MPI。在使用任何其他MPI函数之前,必须调用该函数。函数原型为:
int MPI_Init(
int *argc, /* input/output */
char **arv /* input/output */
)
1.7.3 用户定义函数原型
MPI_User_function
定义了一个用于
MPI_Op_create
的操作的基本模板。其定义如下:
typedef MPI_User_function(
void *invec, /* input vector */
void *inoutvec, /* input/output vector */
int length, /* length of vecs. */
MPI_Datatype datatype /* type of vec elements */
)
2. Fortran和C语言通信
2.1 Fortran和C语言的主要区别
Fortran和C语言在参数传递、数组存储、动态数组、复数类型和指针支持等方面存在一些重要区别。
2.1.1 参数传递
- Fortran通过地址(引用)将所有参数传递给过程(子例程和函数)。例如:
program address
real x
call subr(x)
print *," x=",x
stop
end
subroutine subr(y)
real y
y = 3.14159
return
end
- C语言通过值传递信息给过程,除了数组是通过地址传递。例如:
#include <stdio.h>
main()
{
float x,*z;
float y[1];
void subrNOK(float),subrOK(float*,float*);
subrNOK(x); /* x will not be set */
printf(" x=%e\n",x);
subrOK(y,z); /* y[0],z[0] are set */
printf(" y[0] = %e, z = %e\n",y[0],*z);
}
void subrNOK(x)
float x;
{
x = 3.14159;
}
void subrOK(float *x,float *y)
{
*x = 3.14159;
y[0] = 2.71828;
}
2.1.2 数组存储
-
Fortran数组在第一个索引上是连续存储的,例如
a(2,j)紧跟在a(1,j)之后。 -
C语言数组的第二个索引是“快速”索引,例如
a[i][2]存储在a[i][1]之后。此外,C语言的索引通常从0开始,而Fortran从1开始。
2.1.3 动态数组
- Fortran允许动态维度的数组。例如:
program dims
real x(9)
call subr(x)
print *,x
stop
end
subroutine subr(x)
real x(3,3)
do i=1,3
do j=1,3
x(j,i) = float(i+j)
enddo
enddo
return
end
- C语言不允许自动的动态维度数组,但可以通过宏定义来解决这个问题。例如:
#define am(i,j) *(a+i+lda*j)
void proc(int Ida, float *a)
{
float seed=331.0, ggl(float*);
int i,j;
for(j=0;j<lda;j++){
for(i=0;i<lda;i++){
am(i,j) = ggl(&seed); /* Fortran order */
}
}
#undef am
}
2.1.4 复数类型
-
Fortran支持复数类型,例如
complex z实际上是一个二维数组(Re(z), Im(z))。 -
C语言在大多数情况下不支持复数类型,虽然Cray C和一些版本的Apple开发者工具包
gcc支持复数类型,但没有统一的标准。在处理Fortran的复数数组时,通常将其映射到C语言的二维浮点数数组中。例如,如果Fortran中声明了complex z(m),在C语言中可以使用float z[m][2]来访问,其中Re(z[k]) = z[k-1][0],Im(z[k]) = z[k-1][1]。
2.1.5 指针支持
-
C语言有广泛的指针支持,通过
&x可以获取变量x的地址,通过*px = y可以修改指针px指向的变量的值。 -
Fortran中类似Cray的指针现在比较常见,但
g77等编译器不支持Fortran指针。Fortran 90支持一种复杂的指针构造,但在许多Linux机器上不可用。
2.2 C语言调用Fortran例程
从C语言调用Fortran例程时,通常需要在Fortran例程名后面加下划线(Cray平台除外)。以下是一个示例,展示了如何传递标量和数组参数:
#include <stdio.h>
int main()
{
int n=2;
unsigned char cl='C',c2;
float x[2];
/* NOTE underscore */
void subr_(int*,char*,char*,float*);
subr_(&n,&cl,&c2,x);
printf("n=%d, cl=%c, c2=%c,x=%e, %e\n",
n,cl,c2,x[0],x[1]);
return 0;
}
对应的Fortran例程如下:
subroutine subr(n,cl,c2,x)
integer n
character*1 cl,c2
real x(n)
print *,"in subr: cl=",cl
c2='F'
do i=1,n
x(i) = float(i)
enddo
return
end
在C语言调用Fortran过程时,可能需要一些库,如
libf2c.a
(或
libf2c.so
)、
libftn.a
(或
libftn.so
)等。在Linux平台上,可能需要
libg2c.a
。可以使用
ar t libf2c.a
列出
libf2c.a
中的所有编译模块,使用
run libftn.so
列出
.so
对象中的所有命名模块。由于这些命令可能会产生大量输出,建议使用
grep
进行过滤。
2.3 Fortran调用C例程
从Fortran调用C例程时,同样需要注意命名约定。以下是一个示例:
program foo
integer n
real x(2)
character*1 cl,c2
cl = 'F'
n = 2
call subr(cl,c2,n,x)
print *,"cl=",cl,", c2=",c2,", n=",n,
", x=",x(1),x(2)
end
对应的C例程如下:
#include <stdio.h>
void subr_(char *cl,char *c2,int *n,float *x)
{
int i;
printf("in subr: cl=%c\n",*cl);
*c2 = 'C';
for(i=0;i<*n;i++) x[i] = (float)i;
}
如果Fortran调用C过程,可能需要
libc.a
、
libm.a
或它们的
.so
共享对象变体。同样,可以使用
ar
或
run
命令来确定未满足的外部符号是否在这些库中。在这种情况下,通常最好使用Fortran编译器/链接器将C模块链接到Fortran程序中,例如:
g77 -o try foo.o subr.o
综上所述,MPI提供了丰富的命令集用于并行计算中的进程间通信,而Fortran和C语言在参数传递、数组存储等方面存在差异,在进行跨语言通信时需要注意这些差异并遵循相应的规则。通过合理使用MPI命令和处理Fortran和C语言的通信问题,可以实现高效的并行计算程序。
3. 流程图展示MPI操作流程
3.1 MPI集体通信流程
下面是一个mermaid格式的流程图,展示了MPI集体通信中广播和收集操作的基本流程:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;
A([开始]):::startend --> B(初始化MPI):::process
B --> C{选择操作类型}:::decision
C -->|广播| D(根进程调用MPI_Bcast):::process
D --> E(所有进程接收广播数据):::process
C -->|收集| F(所有进程发送数据):::process
F --> G(根进程调用MPI_Gather):::process
E --> H(处理数据):::process
G --> H
H --> I(结束MPI):::process
I --> J([结束]):::startend
这个流程图展示了MPI集体通信中的广播和收集操作的基本流程。首先,程序开始后初始化MPI环境,然后根据需求选择广播或收集操作。如果选择广播,根进程调用
MPI_Bcast
发送数据,所有进程接收数据;如果选择收集,所有进程发送数据,根进程调用
MPI_Gather
收集数据。最后,所有进程处理数据,结束MPI环境。
3.2 C与Fortran通信流程
下面是一个mermaid格式的流程图,展示了C语言调用Fortran例程的基本流程:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;
A([开始]):::startend --> B(编写C代码):::process
B --> C(编写Fortran代码):::process
C --> D(编译Fortran代码):::process
B --> E(编译C代码):::process
D --> F(链接C和Fortran代码):::process
E --> F
F --> G(运行程序):::process
G --> H{是否成功}:::decision
H -->|是| I([结束]):::startend
H -->|否| J(检查错误):::process
J --> B
这个流程图展示了C语言调用Fortran例程的基本流程。首先,分别编写C代码和Fortran代码,然后分别编译这两种代码。接着,将编译后的代码进行链接,运行程序。如果运行成功,程序结束;如果运行失败,检查错误并重新编写代码。
4. 总结与操作建议
4.1 MPI操作总结
-
点对点通信
:
MPI_Sendrecv_replace用于发送和接收数据并替换缓冲区内容。 -
集体通信
:包括广播、收集、散射、归约等操作,不同的操作函数适用于不同的场景,如
MPI_Bcast用于广播,MPI_Gather用于收集数据。 -
通信器和组操作
:可以对通信器和组进行比较、创建、释放等操作,如
MPI_Comm_compare比较通信器,MPI_Group_create创建组。 -
定时器和初始化
:
MPI_Wtick和MPI_Wtime用于计时,MPI_Init和MPI_Finalize用于启动和结束MPI环境。
4.2 Fortran与C通信总结
- 参数传递 :Fortran通过地址传递参数,C通过值传递参数(数组除外)。
- 数组存储 :Fortran数组在第一个索引上连续存储,C数组的第二个索引是“快速”索引。
- 动态数组 :Fortran允许动态维度数组,C需要通过宏定义解决。
- 复数类型 :Fortran支持复数类型,C在大多数情况下不支持。
- 指针支持 :C有广泛的指针支持,Fortran的指针支持因编译器而异。
4.3 操作建议
4.3.1 MPI操作建议
-
在使用MPI函数之前,务必调用
MPI_Init进行初始化,并在程序结束时调用MPI_Finalize结束MPI环境。 - 对于集体通信操作,确保所有进程都调用相应的函数,避免死锁。
-
在使用自定义归约操作时,使用
MPI_Op_create创建操作,并在不需要时使用MPI_Op_free释放操作。
4.3.2 C与Fortran通信建议
- 在C语言调用Fortran例程时,注意Fortran例程名后加下划线(Cray平台除外)。
-
编译和链接时,确保包含必要的库,如
libf2c.a、libftn.a等。 - 处理Fortran的复数数组时,将其映射到C语言的二维浮点数数组中。
4.4 示例操作步骤
4.4.1 编写和运行MPI程序
- 编写MPI代码 :使用MPI函数编写并行程序,例如:
#include <stdio.h>
#include <mpi.h>
int main(int argc, char **argv) {
int rank, size;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
printf("Hello from process %d of %d\n", rank, size);
MPI_Finalize();
return 0;
}
- 编译代码 :使用MPI编译器编译代码,例如:
mpicc -o hello hello.c
-
运行程序
:使用
mpirun运行程序,例如:
mpirun -np 4 ./hello
4.4.2 C调用Fortran程序
- 编写C代码 :
#include <stdio.h>
int main() {
int n = 2;
unsigned char cl = 'C', c2;
float x[2];
void subr_(int*, char*, char*, float*);
subr_(&n, &cl, &c2, x);
printf("n=%d, cl=%c, c2=%c, x=%e, %e\n", n, cl, c2, x[0], x[1]);
return 0;
}
- 编写Fortran代码 :
subroutine subr(n, cl, c2, x)
integer n
character*1 cl, c2
real x(n)
print *, "in subr: cl=", cl
c2 = 'F'
do i = 1, n
x(i) = float(i)
enddo
return
end
- 编译和链接 :
gfortran -c subr.f90
gcc -c main.c
gcc -o main main.o subr.o -lgfortran
- 运行程序 :
./main
通过以上的总结和操作建议,希望能够帮助开发者更好地使用MPI进行并行计算,以及处理C与Fortran之间的通信问题。在实际应用中,根据具体需求选择合适的函数和方法,确保程序的正确性和高效性。
超级会员免费看
3456

被折叠的 条评论
为什么被折叠?



