我们有一个很大的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编写的,所以我们的数组是连续的;我想访问也是连续的。
有什么想法吗?
谢谢,乔利
这是我正在做的事情的伪代码模板。家里没有代码作为参考,所以我希望我没有忘记任何重要的东西,但我会确保当我回到办公室的时候……
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))“。
发布于 2012-05-18 20:27:49
"...unless我们设置了某种屏障,那么为什么要进行异步通信呢?“
这句话有点混乱。异步通信的目的是重叠通信和计算;这样您就有希望在通信进行的同时完成一些实际的工作。但这意味着你现在有两个最终必须同步的任务,所以有,它会在第一个通信阶段结束时阻止任务进入第二个计算阶段(或其他阶段)。
在这种情况下,如何很好地实现这些内容的问题取决于您是如何实现的(看起来您现在得到的东西是有效的,但您有理由担心结果的脆弱性)。你使用了线程这个词,但是(a)你使用的是sysv共享内存段,如果你有线程,你就不需要这么做了,(b)你被限制不能使用线程库,所以你的意思是你实际上是在MPI_Init()之后使用fork()进程吗?
我同意Hristo的观点,您最好的选择几乎肯定是使用OpenMP进行节点上的计算分发,这可能会极大地简化您的代码。了解更多关于不使用线程库的约束将会有所帮助。
另一种方法是让节点上的所有进程都是MPI进程,但创建几个通信器-一个用于全局通信,每个节点一个“本地”通信器。这种方法仍然可以避免您在MPI之外使用的基于进程的通信层。每个节点只有几个进程是通信器的一部分,该通信器实际执行节点外通信,其他进程在共享内存段上工作。然后,您可以使用基于MPI的同步方法(Wait或Barrier)进行节点上同步。即将发布的MPI3实际上将明确支持以这种方式使用本地共享内存段。
最后,如果您完全被约束并决定继续通过本质上是您自己的仅限本地节点的进程间通信实现来执行操作-因为您已经在使用SysV共享内存段,那么您不妨使用SysV semaphores来进行同步。您已经在使用自己的(有些微妙的)类信号量机制来“标记”数据何时准备好进行计算;在这里,您可以使用一个更健壮的、已经编写的信号量,让非MPI进程知道数据何时准备好进行计算(以及一个类似的机制,让MPI进程知道其他进程何时完成了计算)。
https://stackoverflow.com/questions/10627715
复制相似问题