【问题标题】:MPI send-receive issue in FortranFortran 中的 MPI 发送接收问题
【发布时间】:2018-04-25 04:06:33
【问题描述】:

我目前开始为科学应用开发并行代码。我必须从 p0 到 p1 以及从 p1 到 p0 交换一些缓冲区(我正在处理器边界之间创建幽灵点)。

这个示例代码可以总结错误:

program test
use mpi
implicit none

integer id, ids, idr, ierr, tag, istat(MPI_STATUS_SIZE)
real sbuf, rbuf

call mpi_init(ierr)

call MPI_COMM_RANK(MPI_COMM_WORLD,id,ierr)

if(id.eq.0) then
ids=0
idr=1
sbuf=1.5
tag=id
else    
ids=1
idr=0
sbuf=3.5
tag=id
endif

call mpi_send(sbuf,1,MPI_REAL,ids,tag,MPI_COMM_WORLD,ierr)

call mpi_recv(rbuf,1,MPI_REAL,idr,tag,MPI_COMM_WORLD,istat,ierr)

call mpi_finalize(ierr)
return
end

这有什么问题?

【问题讨论】:

  • 欢迎来到 SO。你的问题不是很清楚。请仔细阅读How to Ask 并在您的问题中添加minimal reproducible example
  • 你好祖蓝,抱歉我的问题不太清楚,我会尽量简化一下。我有 2 个进程(rank=0 和 rank=1)。我需要交换一个从 0 到 1 的向量,同时还要交换一个从 1 到 0 的向量。我该如何进行这种通信?
  • 欢迎。你的代码太不完整了。我们需要看看我们可以编译和测试的东西。包括所有的变量声明等等。
  • 我建议你在这里阅读:stackoverflow.com/questions/10017301/…。您必须了解阻塞和非阻塞操作之间的区别。

标签: fortran mpi


【解决方案1】:

一开始使用 MPI 进行编码可能会很困难,您最好完成编写示例代码的步骤。您发布的示例代码由于死锁而挂起。两个进程都忙于MPI_SEND-ing,并且在MPI_RECV-ed 之前发送无法完成。所以代码卡住了。

解决这个问题有两种常见的方法。

按特定顺序发送和接收

这是简单易懂的解决方案。对您的发送和接收操作进行编码,这样没有人会被卡住。对于您的 2 进程测试用例,您可以这样做:

if (id==0) then

   call mpi_send(sbuf,1,MPI_REAL,ids,tag,MPI_COMM_WORLD,ierr)
   call mpi_recv(rbuf,1,MPI_REAL,idr,tag,MPI_COMM_WORLD,istat,ierr)
else
   call mpi_recv(rbuf,1,MPI_REAL,idr,tag,MPI_COMM_WORLD,istat,ierr)
   call mpi_send(sbuf,1,MPI_REAL,ids,tag,MPI_COMM_WORLD,ierr)
endif

现在,进程 1 接收 first,因此永远不会出现死锁。这个特定的例子是不可扩展的,但有各种循环结构可以提供帮助。您可以想象一个例程将数据从每个进程发送到每个其他进程:

do sending_process=1,nproc
   if (id == sending_process) then
      ! -- I am sending
      do destination_process = 1,nproc
         if (sending_process == destination_process) cycle
         call MPI_SEND ! Send to destination_process
      enddo
    elseif
       ! -- I am receiving
       call MPI_RECV ! Receive from sending_process
    endif
 enddo

这工作得相当好并且很容易理解。我向初学者推荐这种结构。

但是,对于真正的大问题,它有几个问题。您发送的消息数量等于进程数平方,这可能会使大型网络过载。此外,根据您的操作,您可能不需要将数据从每个进程发送到每个其他进程。 (鉴于您提到了幽灵,我怀疑这对您来说是正确的。)您可以修改上述循环以仅在需要数据时发送,但对于这些情况,有更好的选择。

使用非阻塞 MPI 操作

对于多核问题,这通常是最佳解决方案。我建议坚持简单的MPI_ISENDMPI_IRECV。在这里,您开始所有必要的发送和接收,然后等待。 在这里,我使用了一些已经设置好的列表结构,它定义了每个进程所需目的地的完整列表。

! -- Open sends
do d=1,Number_Destinations
   idest = Destination_List(d)

   call MPI_ISEND ! To destination d
enddo

! -- Open receives
do s=1,Number_Senders
   isend = Senders_List(s)
   call MPI_IRECV ! From source s
enddo

call MPI_WAITALL

此选项可能看起来更简单,但它不是。您必须事先设置所有必要的列表,并且缓冲区大小和数据对齐存在各种潜在问题。尽管如此,它通常是 big 代码的最佳答案。

【讨论】:

  • 亲爱的罗斯,非常感谢!这是一个非常好的答案。我明天会努力听从你的指导!好吧,我正在为流体动力学设置代码(串行效果很好)。所以原则上,每个处理器都应该知道它的邻居单元,并且每个时间步,这个幽灵单元必须用来自邻居分区的速度 ecc 的新值进行更新。我认为你们中的一些结构应该适合这项工作。
  • 我也从事 CFD 工作。您的目标是什么问题规模(单元和处理器数量)?
  • 是非结构化网格的代码(网格的数据结构与openFoam相同)。我的目标通常是 3D 不稳定流,所以假设最多 64 个 proc 和百万个单元的顺序 :) 这是一个个人项目,我计划将它用于未来的项目。但首先我需要一个并行化:)
  • 我怀疑第一个选项最适合您。它更容易调试和监控性能,并且它的局限性对于您的问题规模来说应该不是什么大问题。
【解决方案2】:

正如 Vladimir 所指出的,您的代码太不完整,无法提供明确的答案。

话虽如此,这可能是一个众所周知的错误。

MPI_Send() 可能会阻塞。从务实的角度来看,MPI_Send() 在发送 short 消息时可能会立即返回,但在发送 large 消息时可能会阻塞。注意 smalllarge 取决于您的 MPI 库、您使用的互连以及其他运行时参数。 MPI_Send() 可能会阻塞,直到在另一端发布 MPI_Recv()

您的MPI_Send()MPI_Recv() 似乎在同一个代码块中,因此您可以尝试使用MPI_Sendrecv() 一次性完成。 MPI_Sendrecv() 将在后台发出非阻塞发送,因此如果您的问题确实是 MPI_Send() 死锁,这将有所帮助。

【讨论】:

  • 我已经放了一个示例代码来重现问题。使用 mpi_sendrecv 我再次得到一个块。谢谢
  • 你的标签不匹配,所以你的测试只能死锁。
  • 好的,但是由于我是 mpi 的菜鸟,您能否提供一个示例(如果您有时间)?同时,我将尝试处理您的建议!谢谢
  • 您可以为所有通信使用唯一标签
  • WRT 到 Sendrecv 我不同意。根据我的经验,与单独的发送和接收相比,MPI_Sendrecv 通常会简化代码,并避免异步消息可能带来的混乱,尤其是对于初学者而言。但这是基于意见,所以我不会多说。当然,您必须始终像 MPI_Send 和 MPI_Recv 阻塞一样进行编码。
猜你喜欢
  • 2018-12-04
  • 2018-08-30
  • 2012-03-13
  • 2018-03-12
  • 2017-03-04
  • 2013-04-15
  • 1970-01-01
  • 2018-07-21
  • 2011-01-16
相关资源
最近更新 更多