hol*_*oly 5 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
Run Code Online (Sandbox Code Playgroud)
理想情况下,我本来希望在通信进程的另一个线程的循环中运行"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))".
"......除非我们制定某种障碍,然后为什么要进行异步通信?"
那句话有点混乱.异步通信的目的是重叠通信和计算; 你可以希望在通信进行的同时完成一些真正的工作.但这意味着你现在有两个最终必须同步的任务,因此必须有一些东西在第一个通信阶段结束之前阻止任务,然后再进入第二个计算阶段(或其他任何阶段).
在这种情况下要做什么来很好地实现事情的问题(看起来你现在已经有效,但你正确地关注结果的脆弱性)取决于你是如何进行实现的.你使用单词threads,但是(a)你正在使用sysv共享内存段,如果你有线程你不需要这样做;(b)你被限制不使用线程库,所以你可能是实际上意味着你是在MPI_Init()之后的fork()进程?
我同意Hristo的观点,你最好的选择是使用OpenMP进行节点上的计算分配,并且可能会大大简化你的代码.如果不使用线程库,那么了解更多关于约束的信息会有所帮助.
另一种方法仍然可以避免你必须"滚动自己的"基于流程的通信层,除了MPI之外你将使用节点上的所有进程都是MPI进程,但创建了一些通信器 - 一个用来做全局通信,每个节点一个"本地"通信器.每个节点只有几个进程是实际进行节点外通信的通信器的一部分,而其他进程在共享内存段上工作.然后,您可以使用基于MPI的方法进行同步(等待或屏障)以进行节点上同步.即将到来的MPI3实际上会以这种方式明确支持使用本地共享内存段.
最后,如果你完全受限并决心继续通过你自己的本地节点唯一的IPC实现来做事 - 因为你已经在使用SysV共享内存段,你也可以使用SysV信号量来做同步.当数据准备好进行计算时,你已经在使用自己的(有些微妙的)类似信号量的机制来"标记"; 在这里,您可以使用更强大的,已经编写的信号量来让非MPI进程知道数据何时可以进行计算(以及类似的机制让MPI进程知道其他人何时完成计算).