具有SysV共享内存的异步MPI

时间:2012-05-16 22:34:28

标签: mpi shared-memory fortran90

我们有一个庞大的Fortran / MPI代码库,它使用节点上的system-V共享内存段。我们运行在具有32个处理器的胖节点上,但只有2或4个NIC,每个CPU的内存相对较少;所以我们的想法是建立一个共享内存段,每个CPU在其上执行计算(在其SMP数组块中)。然后,MPI用于处理节点间通信,但仅用于SMP组中的主节点。该程序是双缓冲的,并且对我们很有效。

当我们决定切换到异步通信时出现问题,因为隐藏了一些延迟。由于节点上只有几个CPU通过MPI进行通信,但所有CPU都看到了接收到的阵列(通过共享内存),CPU不知道通信CPU何时完成,除非我们制定了某种障碍,并且为什么要进行异步通信?

理想的假设解决方案是将请求标记放在SMP段中,并在需要知道的CPU上运行mpi_request_get_status。当然,请求标记只在通信CPU上注册,所以它不起作用!另一个提议的可能性是在通信线程上分支线程并使用它在循环中运行mpi_request_get_status,并在共享内存段中使用flag参数,因此所有其他映像都可以看到。不幸的是,这也不是一种选择,因为我们不能使用线程库。

我们提出的唯一可行的选择似乎有效,但感觉就像一个肮脏的黑客。我们在接收缓冲区的上限地址中放置了一个不可能的值,这样一旦mpi_irecv完成,该值就会改变,因此每个CPU都知道它何时可以安全地使用缓冲区。这可以吗?如果可以保证MPI实现连续传输数据,它似乎只能可靠地工作。这几乎听起来令人信服,因为我们已经在Fortran中编写了这个东西,因此我们的数组是连续的;我想,访问也是。

有什么想法吗?

谢谢, 乔利

这是我正在做的事情的伪代码模板。没有把代码作为家里的参考,所以我希望我没有忘记任何重要的事情,但我会确保我什么时候回到办公室......

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))”。

1 个答案:

答案 0 :(得分:5)

“......除非我们制定某种障碍,然后为什么要进行异步通信?”

这句话有点混乱。异步通信的目的是重叠通信和计算;你可以希望在通信进行的同时完成一些真正的工作。但是这意味着你现在有两个任务,最终同步,所以是在第一个通信阶段结束之前阻止任务的东西。进入第二个计算阶段(或其他)。

在这种情况下要做什么来很好地实现事情的问题(看起来你现在已经有效,但你正确地关注结果的脆弱性)取决于你是如何进行实现的。你使用单词threads,但是(a)你正在使用sysv共享内存段,如果你有线程你就不需要这样做;(b)你被限制不使用线程库,所以你可能是实际上意味着你是在MPI_Init()之后的fork()进程吗?

我同意Hristo的观点,你最好的选择是使用OpenMP进行节点上的计算分配,并且可能会大大简化你的代码。了解更多关于不使用线程库的约束将会有所帮助。

另一种方法仍然可以避免你必须“滚动你自己的”基于流程的通信层,除了MPI之外你将使用所有节点上的进程是MPI进程,但是创建一些通信器 - 一个用于进行全局通信,一个用于每个节点的“本地”通信器。每个节点只有几个进程是实际进行节点外通信的通信器的一部分,而其他进程在共享内存段上工作。然后,您可以使用基于MPI的方法进行同步(等待或屏障)以进行节点上同步。即将推出的MPI3实际上会以这种方式明确支持使用本地共享内存段。

最后,如果你完全受限并决心继续通过本身只有你自己的本地节点的IPC实现来做事 - 因为你已经在使用SysV共享内存段,你也可以使用{{ 3}}进行同步。当数据准备好进行计算时,你已经在使用自己的(有些微妙的)类似信号量的机制来“标记”;在这里你可以使用一个更强大的,已经编写的信号量来让非MPI进程知道数据何时可以进行计算(以及类似的机制让MPI进程知道其他人何时完成计算)。 p>