MPI_Send的缓冲区大小问题

在迁移MPI程序时遇到“fatal error in MPI: other MPI error”错误,发现是由于发送大文件时缓冲区大小问题引起。通过调整缓冲区大小及使用MPI_Bsend的缓冲模式,解决了64KB的限制问题。但发现静态声明的数组大小限制导致段错误,需使用动态声明。

        最近一直在忙程序的搬家问题,其实说起来不能再简单了:就是把在公司调好的一个MPI程序搬到gz的大系统上去跑,本以为没啥问题的一个步骤实现起来却各种问题,既遇到编译的问题(第一篇博客中提到的)并解决之后,程序的运行也出现了错误。错误信息为“fatal error in MPI: other MPI error”,而且死掉的进程都是发送进程,这让我百思不解。

        然后我怀疑了各种问题尝试了各种方式:首先,使用gdb调试,但问题在于由于需要修改-O3的优化选项才能调试MPI程序,这导致了程序跑的奇慢……发火,等得受不了了;然后,我就只好回归了最原始的输出调试,但我又惊奇的发现,本应该输出的信息没有输出或者没有按顺序输出,实在是混淆视听发火,问了贵鑫才知道是因为缓冲区可能不满,所以利用flush解决了问题(语法为:cout<<"输出的内容"<<……<<flush);在输出信息的帮助下,我逐渐将出错的位置锁定在了用MPI_Send发送大文件的一条语句上,因为我发现,发送其他的信息比如文件名,文件大小等小的信息都可以正常接收,但发送文件时就出错,我怀疑可能是buffer大小的问题。因为文件名等信息的buffer设置为100个字节,而发送的文件比较大,采用循环发送的方式,缓冲区大小设置为1MB,之前在公司上跑,这个设置是没有问题的,而且为了减少通信开销我还想往大了改,比如10MB或者更大,但在大系统上跑就出了问题,在那边的维护人员的帮助下,我调整了发送的缓冲区大小,最后发现<64KB的buffer size都可以正常运行,但>=64KB就会报上面的错误了,可能是MPI的具体实现不同所致。

      所以我开始尝试用M

<think>我们讨论的是Fortran中MPI_SendMPI_Recv的使用方法。根据引用[1][3][4]中提供的信息,特别是关于MPI_Sendrecv的说明,但用户明确要求使用MPI_SendMPI_Recv。在Fortran中使用MPI_SendMPI_Recv的基本步骤:1.初始化MPI环境(MPI_Init)2.获取当前进程的rank和总进程数(MPI_Comm_rank,MPI_Comm_size)3.根据rank决定发送或接收行为(通常用个进程发送,另个进程接收)4.使用MPI_Send发送消息5.使用MPI_Recv接收消息(注意:发送和接收的tag要匹配,通信器要致)6.最后终止MPI环境(MPI_Finalize)注意:MPI_SendMPI_Recv是阻塞操作。如果不小心设计通信模式,容易导致死锁。例如,如果两个进程同时先发送再接收,则可能双方都在等待对方接收而无法继续,形成死锁。此时可以考虑使用MPI_Sendrecv(如引用[3][4]所述)或非阻塞通信。下面我们给出个简单的Fortran示例:两个进程(rank0和rank1)之间交换个整数和个实数数组。注意:实际代码中需要错误检查(ierr),这里为了简洁省略。示例代码:```fortranprogramsend_recv_exampleusempiimplicitnoneinteger::ierr,rank,sizeinteger::send_tag=100,recv_tag=100integer,parameter::num_ints=1,num_reals=5integer::status(MPI_STATUS_SIZE)integer::send_int,recv_intreal::send_real_array(num_reals),recv_real_array(num_reals)integer::dest,source!初始化MPIcallMPI_Init(ierr)callMPI_Comm_rank(MPI_COMM_WORLD,rank,ierr)callMPI_Comm_size(MPI_COMM_WORLD,size,ierr)!检查进程数是否为2if(size<2)thenif(rank==0)print*,'Thisexamplerequiresatleast2processes.'callMPI_Finalize(ierr)stopendif!初始化要发送的数据send_int=rank*10!每个进程发送不同的整数值send_real_array=real(rank)!数组每个元素设置为当前进程的rank(实数)!设置目标进程:进程0发送给进程1,进程1发送给进程0if(rank==0)thendest=1source=1elsedest=0source=0endif!开始通信:注意,如果两个进程都先发送,可能死锁。所以我们让进程0先发送再接收,进程1先接收再发送。if(rank==0)then!进程0先发送整数,然后发送实数数组callMPI_Send(send_int,num_ints,MPI_INTEGER,dest,send_tag,MPI_COMM_WORLD,ierr)callMPI_Send(send_real_array,num_reals,MPI_REAL,dest,send_tag+1,MPI_COMM_WORLD,ierr)!然后接收来自进程1的整数和实数数组callMPI_Recv(recv_int,num_ints,MPI_INTEGER,source,recv_tag,MPI_COMM_WORLD,status,ierr)callMPI_Recv(recv_real_array,num_reals,MPI_REAL,source,recv_tag+1,MPI_COMM_WORLD,status,ierr)else!进程1先接收来自进程0的整数和数组callMPI_Recv(recv_int,num_ints,MPI_INTEGER,source,send_tag,MPI_COMM_WORLD,status,ierr)callMPI_Recv(recv_real_array,num_reals,MPI_REAL,source,send_tag+1,MPI_COMM_WORLD,status,ierr)!然后发送自己的整数和数组callMPI_Send(send_int,num_ints,MPI_INTEGER,dest,recv_tag,MPI_COMM_WORLD,ierr)callMPI_Send(send_real_array,num_reals,MPI_REAL,dest,recv_tag+1,MPI_COMM_WORLD,ierr)endif!打印接收到的数据print*,'Rank',rank,'receivedinteger:',recv_int,'andrealarray:',recv_real_arraycallMPI_Finalize(ierr)endprogramsend_recv_example```代码解释:-该程序需要恰好两个进程(或至少两个,但只有rank0和rank1会进行通信)。-我们定义了个整数和个实数数组作为发送数据。每个进程将自己的rank乘以10作为整数,并将个实数数组的所有元素设置为当前rank(实数)。-进程0先发送两个消息(个整数和个数组)给进程1,然后等待接收进程1发来的消息。-进程1则先等待接收进程0的消息,然后再发送自己的消息。-这样安排通信顺序避免了死锁:进程0先发送后接收,进程1先接收后发送。-注意:我们使用了两个不同的消息标签(tag)来区分整数和数组消息(send_tag和send_tag+1)。接收时使用相应的标签。编译和运行:```bashmpifort-osend_recv_examplesend_recv_example.f90mpirun-n2./send_recv_example```输出可能如下:```Rank0receivedinteger:10andrealarray:1.01.01.01.01.0Rank1receivedinteger:0andrealarray:0.00.00.00.00.0```注意事项:1.如果通信顺序不当,比如两个进程都先执行发送操作,则会发生死锁。因为双方都在等待对方接收消息,而双方又都卡在发送操作(阻塞发送需要对方开始接收才能完成)。所以必须设计好通信顺序,或者使用非阻塞通信(MPI_Isend,MPI_Irecv)或组合操作(如MPI_Sendrecv)。2.消息标签(tag)和通信器(comm)必须匹配。同个通信中,发送方和接收方要使用相同的标签和通信器。3.MPI_Recv中的status参数(在Fortran中是整型数组,大小至少为MPI_STATUS_SIZE)可以用来获取消息的详细信息,如实际接收到的数据数量、发送者rank等。4.数据类型要匹配:发送整数数组用MPI_INTEGER,发送实数数组用MPI_REAL(或根据实际情况选择,如双精度用MPI_DOUBLE_PRECISION)。如果需要在多个进程间进行更复杂的通信,请务必小心设计以避免死锁,或者考虑使用非阻塞通信或集体通信操作(如引用[2]中的MPI_Gather)[^2]。###相关问题1.如何避免在MPI_SendMPI_Recv中发生死锁?2.如何在Fortran中使用MPI_Sendrecv函数避免死锁?(如引用[3][4]所述)3.MPI_IsendMPI_Irecv非阻塞通信的使用方法?4.如何在多对多通信中使用MPI_SendMPI_Recv?</think> 下面详细介绍在Fortran中使用MPI_SendMPI_Recv进行消息传递的方法,结合您提供的最新引用信息。 ### Fortran中MPI_SendMPI_Recv使用方法 #### 1. **函数原型** ```fortran ! MPI_Send (发送消息) call MPI_Send(sendbuf, sendcount, sendtype, dest, sendtag, comm, ierr) ! MPI_Recv (接收消息) call MPI_Recv(recvbuf, recvcount, recvtype, source, recvtag, comm, status, ierr) ``` **参数说明**: | 参数 | 类型 | 说明 | |-------------|-----------------|-----------------------------------------------------------------------------------------| | `sendbuf` | 任意类型 | 发送数据缓冲区起始地址 | | `sendcount` | INTEGER | 发送数据个数 | | `sendtype` | MPI_Datatype | 发送数据类型(如`MPI_INTEGER`, `MPI_REAL`, `MPI_DOUBLE_PRECISION`) | | `dest` | INTEGER | **目标进程rank号** (0到size-1) | | `sendtag` | INTEGER | **发送消息标签** (0-32767),用于标识消息类型 | | `comm` | MPI_Comm | 通信器(通常用`MPI_COMM_WORLD`) | | `ierr` | INTEGER | 错误返回码 | | `recvbuf` | 任意类型 | 接收数据缓冲区 | | `recvcount` | INTEGER | **最大接收数据个数** (实际接收数可通过status获取) | | `source` | INTEGER | **源进程rank号** (`MPI_ANY_SOURCE`表示接收任意源) | | `recvtag` | INTEGER | **接收消息标签** (`MPI_ANY_TAG`表示接收任意标签) | | `status` | INTEGER数组 | 返回状态信息,数组大小至少为`MPI_STATUS_SIZE` (包含实际接收数、源rank等信息) [^1][^4] | #### 2. **基本通信流程** ```mermaid flowchart TD A[进程A] -->|MPI_Send| B[进程B] B[进程B] -->|MPI_Recv| A[进程A] ``` #### 3. **完整示例代码** ```fortran program basic_send_recv use mpi implicit none integer :: ierr, rank, size, dest, source, send_tag = 100 integer, parameter :: buffer_size = 5 real :: send_buffer(buffer_size), recv_buffer(buffer_size) integer :: status(MPI_STATUS_SIZE) ! 初始化MPI环境 call MPI_Init(ierr) call MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr) call MPI_Comm_size(MPI_COMM_WORLD, size, ierr) ! 检查进程数 if (size < 2) then if (rank == 0) print*, '需要至少2个进程' call MPI_Finalize(ierr) stop end if ! 初始化发送数据 (每个进程数据不同) send_buffer = rank * 10.0 + [(i, i = 1, buffer_size)] ! 确定通信对象 if (rank == 0) then dest = 1 ! rank0发送到rank1 source = 1 ! rank0从rank1接收 else dest = 0 ! rank1发送到rank0 source = 0 ! rank1从rank0接收 end if ! 通信阶段 (注意避免死锁) if (rank == 0) then call MPI_Send(send_buffer, buffer_size, MPI_REAL, dest, send_tag, MPI_COMM_WORLD, ierr) print*, 'Rank', rank, ': 已发送数据', send_buffer call MPI_Recv(recv_buffer, buffer_size, MPI_REAL, source, send_tag, MPI_COMM_WORLD, status, ierr) print*, 'Rank', rank, ': 接收到数据', recv_buffer else call MPI_Recv(recv_buffer, buffer_size, MPI_REAL, source, send_tag, MPI_COMM_WORLD, status, ierr) print*, 'Rank', rank, ': 接收到数据', recv_buffer call MPI_Send(send_buffer, buffer_size, MPI_REAL, dest, send_tag, MPI_COMM_WORLD, ierr) print*, 'Rank', rank, ': 已发送数据', send_buffer end if call MPI_Finalize(ierr) end program basic_send_recv ``` #### 4. **关键注意事项** 1. **标签匹配** 发送和接收操作的标签必须匹配(上例中均使用`send_tag=100`) 2. **死锁避免** - 示例中采用**交替通信顺序**(rank0先发后收,rank1先收后发) - 替代方案:使用`MPI_Sendrecv`(引用[3][4])或非阻塞通信`MPI_Isend`/`MPI_Irecv` 3. **缓冲区大小** `recvcount`应 >= 实际接收数据量,通过`status`获取实际接收数: ```fortran integer :: actual_count call MPI_Get_count(status, MPI_REAL, actual_count, ierr) ``` 4. **通配符使用** ```fortran ! 接收任意源、任意标签的消息 call MPI_Recv(buf, count, datatype, MPI_ANY_SOURCE, MPI_ANY_TAG, comm, status, ierr) ``` #### 5. **编译执行** ```bash # 编译 mpifort -o send_recv basic_send_recv.f90 # 运行 (2个进程) mpirun -np 2 ./send_recv ``` **输出示例**: ``` Rank 0 : 已发送数据 [10.0, 11.0, 12.0, 13.0, 14.0] Rank 1 : 接收到数据 [10.0, 11.0, 12.0, 13.0, 14.0] Rank 1 : 已发送数据 [20.0, 21.0, 22.0, 23.0, 24.0] Rank 0 : 接收到数据 [20.0, 21.0, 22.0, 23.0, 24.0] ``` ### 死锁问题深度分析 当进程间存在复杂依赖时,单纯使用`MPI_Send`/`MPI_Recv`容易导致死锁。此时应考虑: 1. **使用MPI_Sendrecv** ```fortran call MPI_Sendrecv(sendbuf, sendcount, sendtype, dest, sendtag, & recvbuf, recvcount, recvtype, source, recvtag, & comm, status, ierr) ``` 此函数将发送和接收合并为原子操作,避免顺序依赖导致的死锁[^3][^4] 2. **非阻塞通信** 使用`MPI_Isend`和`MPI_Irecv`配合`MPI_Wait`: ```fortran integer :: request(2) call MPI_Isend(sendbuf, sendcount, sendtype, dest, sendtag, comm, request(1), ierr) call MPI_Irecv(recvbuf, recvcount, recvtype, source, recvtag, comm, request(2), ierr) call MPI_Waitall(2, request, MPI_STATUSES_IGNORE, ierr) ``` ### 消息传递与数据收集的对比 | 操作 | 特点 | 适用场景 | |--------------|------------------------------------|-------------------------| | `MPI_Send/Recv` | 点对点通信 | 任意两进程间数据交换 | | `MPI_Gather` | 收集**相同长度**数据块到根进程 | 汇总计算结果[^2] | | `MPI_Gatherv` | 收集**不同长度**数据块 | 非均匀数据收集[^2] |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值