使用SysV共享内存实现异步MPI

5
我们有一个大型的Fortran/MPI代码库,利用节点上的System-V共享内存段。我们在拥有32个处理器的fat节点上运行,但只有2或4个NIC,并且每个CPU的内存相对较少。因此,我们的想法是设置一个共享内存段,在其中每个CPU都执行其计算(在其SMP数组块中)。然后使用MPI来处理节点间通信,但仅在SMP组中的主节点上进行。该过程是双缓冲的,并且对我们非常有效。
问题出现在我们决定切换到异步通信时。由于节点上只有几个CPU通过MPI进行通信,但所有CPU都可以看到接收到的数组(通过共享内存),因此除非我们执行某种形式的障碍,否则CPU不知道通信CPU何时完成,那么为什么要使用异步通信呢?
理想的、假设的解决方案是将请求标签放在SMP段中,并在需要知道的CPU上运行mpi_request_get_status。当然,请求标签仅在通信CPU上注册,因此它不起作用!另一个提出的可能性是在通信线程上分支一个线程,并在其中使用循环运行mpi_request_get_status,标志参数在共享内存段中,以便所有其他图像都可以看到。不幸的是,这也不是一个选项,因为我们受到约束,不能使用线程库。
我们想到的唯一可行的选择似乎起作用,但感觉像是一个肮脏的黑客。我们在接收缓冲区的上限地址中放入了一个不可能的值,这样一旦mpi_irecv完成,该值就会改变,因此每个CPU都知道何时可以安全地使用缓冲区。这样做是否可以?如果MPI实现可以保证连续传输数据,则似乎只有它能够可靠地工作。由于我们用Fortran编写了这个东西,所以我们的数组是连续的;我想访问也是连续的。
你有什么想法吗?
谢谢, Joly
以下是我正在进行的操作的伪代码模板。没有代码作为参考,所以希望我没有忘记任何重要的事情,但当我回到办公室时,我会确保的...
pseudo(array_arg1(:,:), array_arg2(:,:)...)

  integer,      parameter : num_buffers=2
  Complex64bit, smp       : buffer(:,:,num_buffers)
  integer                 : prev_node, next_node
  integer                 : send_tag(num_buffers), recv_tag(num_buffers)
  integer                 : current, next
  integer                 : num_nodes

  boolean                 : do_comms
  boolean,      smp       : safe(num_buffers)
  boolean,      smp       : calc_complete(num_cores_on_node,num_buffers)

  allocate_arrays(...)

  work_out_neighbours(prev_node,next_node)

  am_i_a_slave(do_comms)

  setup_ipc(buffer,...)

  setup_ipc(safe,...)

  setup_ipc(calc_complete,...)

  current = 1
  next = mod(current,num_buffers)+1

  safe=true

  calc_complete=false

  work_out_num_nodes_in_ring(num_nodes)

  do i=1,num_nodes

    if(do_comms)
      check_all_tags_and_set_safe_flags(send_tag, recv_tag, safe) # just in case anything else has finished.
      check_tags_and_wait_if_need_be(current, send_tag, recv_tag)
      safe(current)=true
    else
      wait_until_true(safe(current))
    end if

    calc_complete(my_rank,current)=false
    calc_complete(my_rank,current)=calculate_stuff(array_arg1,array_arg2..., buffer(current), bounds_on_process)
    if(not calc_complete(my_rank,current)) error("fail!")

    if(do_comms)
      check_all_tags_and_set_safe(send_tag, recv_tag, safe)

      check_tags_and_wait_if_need_be(next, send_tag, recv_tag)
      recv(prev_node, buffer(next), recv_tag(next))
      safe(next)=false

      wait_until_true(all(calc_complete(:,current)))
      check_tags_and_wait_if_need_be(current, send_tag, recv_tag)
      send(next_node, buffer(current), send_tag(current))
      safe(current)=false
    end if

    work_out_new_bounds()

    current=next
    next=mod(next,num_buffers)+1

  end do
end pseudo

理想情况下,我希望在另一个线程中循环运行“check_all_tags_and_set_safe_flags”函数,并在通信过程中使用它。更好的方法是:取消“安全标志”,并使发送/接收操作的句柄可用于从属进程,然后我可以在从属进程上运行“check_tags_and_wait_if_need_be(current, send_tag, recv_tag)”(mpi_wait),而不是“wait_until_true(safe(current))”。


4
你对MPI传输消息的方式的假设并非完全正确。通常会将大的消息分成多个独立的块进行发送,并且这些块可能以任意顺序到达(例如,Open MPI + 多个网络互连)。这仅取决于MPI实现,而不是程序所编写的语言。你不能使用线程库来解决问题听起来很糟糕,因为这听起来像一个适合使用混合MPI / OpenMP解决方案的好候选者。 - Hristo Iliev
1个回答

5
这句话有点混乱。异步通信的目的是重叠通信和计算,希望在通信进行时能完成一些实际的工作。但这意味着你现在有两个任务,最终必须同步,所以必须有某种阻塞机制,以便在第一通信阶段结束之前阻止任务进行第二计算阶段(或其他操作)。
在这种情况下,如何实现良好的操作(似乎你现在的操作可行,但你正确地担心结果的脆弱性)取决于你如何进行实现。你使用了“线程”这个词,但是(a)你正在使用sysv共享内存段,如果你使用线程就不需要这样做了,而且(b)你受到限制不能使用线程库,因此你可能是在MPI_Init()后fork()进程?
我同意Hristo的看法,你最好的选择几乎肯定是使用OpenMP进行节点内的计算分配,这将大大简化你的代码。更多地了解你不使用线程库的约束条件将有所帮助。
另一种方法仍然避免了你必须“自己编写”的基于进程的通信层,同时还要使用MPI,那就是让所有进程都成为MPI进程,但创建几个通信器——一个用于全局通信,每个节点一个“本地”通信器。每个节点只有几个进程是实际参与到节点间通信的通信器中,其他进程在共享内存段上工作。然后你可以使用MPI的方法进行同步(Wait或Barrier)进行节点内同步。即将发布的MPI3实际上将对这种方式使用本地共享内存段提供一些明确的支持。
最后,如果你绝对要坚持通过自己的本地节点IPC实现来完成操作,那么既然你已经在使用SysV共享内存段,那么你可以使用SysV信号量来进行同步。你已经使用了自己的(有些脆弱的)类似信号量的机制来“标记”何时数据准备好进行计算;在这里,你可以使用一个更加稳健、已经编写好的信号量来让非MPI进程知道何时数据准备好进行计算(并使用类似的机制让MPI进程知道其他进程何时完成计算)。

感谢您澄清这一点;我很感激即使是在异步通信中,我们也需要等待。我的担心是,一个进程可能已经准备好切换到安全缓冲区,但正在等待通信进程达到轮询请求标记并通知从属缓冲区的安全状态的点;这是一种二阶等待,不仅仅是等待通信完成。 - holyjoly
我想我应该澄清一下线程的情况;我们目前没有使用任何线程库或进程分叉,只是在节点上使用纯MPI和SysV。我提到这个只是因为我认为线程是正确的方法,尽管出于以下原因我不能这样做。OpenMP应该是理想的选择,但不幸的是,要有效地使用它,我们需要重新设计代码的重要部分,而其他开发人员不愿意让它被零星地使用,仅用于生成MPI请求标记轮询线程,或者(更明智的做法)甚至使用OpenMP来处理 - holyjoly
共享内存/直接循环。给出的原因是OpenMP分支线程速度较慢,因此要么有人重写底层通信架构,将OpenMP放在MPI层级之上,要么我们不愿意承担延迟成本。Posix线程是另一个选择,但我们(不幸地)被绑定支持Microsoft Windows,其中Unix子系统是可选的,可能会在Windows 8中消失。总的来说,我不认为我能够在开发者中赢得线程库争论。 - holyjoly
实际上,我所想要的只是在通信过程中另开一个线程来轮询请求标签,或者以一种允许我在其他进程中轮询它们的方式将这些请求标签传递给其他进程。我认为我可能过高地估计了问题,也许通过连续在循环中的几个点轮询请求标签,并通过任何手段告知从属进程,可能问题不大。这似乎只是可能有一个更优雅的解决方案。你提到了进程分叉,但我印象中 MPI 强烈反对这种做法? - holyjoly
1
注释用于小注释;对问题的更新应在回答的编辑中进行。我同意有时会很尴尬。请注意,我并不是在“推荐”分叉,我只是在问你是否这样做了。恐怕我仍然不理解你的代码实际上正在做什么,也许使用一些伪代码编辑您的问题会有所帮助。 - Jonathan Dursi
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接