MPI中的设计模式:阻塞发送和正确负载平衡的睡眠根过程

时间:2014-09-14 19:17:27

标签: python mpi load-balancing mpi4py

我使用mpi4py在Python中运行MPI代码,如下所示:

from mpi4py import MPI
import numpy as np
import os

comm = MPI.COMM_WORLD
rank = comm.Get_Rank()
size = comm.Get_Size()

if rank == 0:
  res = np.zeros(2**16)
  jobs = os.listdir('/my/data/dir')
  for i in xrange(len(jobs)):
    proc = (i % (size - 1)) + 1 #lacks load balancing
    buf = load_buf_from_file(job[i])
    #root waits here at 100%
    comm.Send([buf, dtype], dest = proc) #lacks load balancing
    comm.Recv([res, dtype], source = MPI.ANY_SOURCE)
    save_result_to_file(res)
else:
  buf = np.zeros(2**16)
  comm.Recv([buf, dtype], source = 0)
  res = do_lots_of_work(buf)
  comm.Send([res, dtype], dest = 0)

我注意到根进程总是忙(CPU为100%)。我更喜欢根进程休眠,直到工作进程准备好接收下一条消息。 MPI编程中有哪些模式可以促进这种行为?也许根过程也应该起作用了?

此设计中的另一个缺陷如下:如果工作程序proc 4在3之前完成,那么4必须等待3完成才能从root获取新消息以继续工作。有关如何设计始终尝试将下一条消息发送到空闲进程的根进程的任何建议?这对我来说基本没问题,因为接收消息的第一个过程通常是第一个完成的过程。但是,如果每个消息的工作负载发生变化,则情况并非总是如此。

谢谢, 凯文

4 个答案:

答案 0 :(得分:3)

关于第一个问题,关于排名0在Comm.Recv中的cpu使用情况。这是一个实施问题。 MPICH(可能还有许多其他人)在紧密的poling循环中等待事件,以便最大限度地减少延迟。

你的第二个问题:如果工作单位不规范,如何平衡工作量。答案是非阻塞操作。 (Isend,Irecv等)。

一个可能的工作流程可能是这样的:

  • 排名0有一个工作单位队列
  • 排名0向每个客户发布非阻止发送
  • 当客户想要工作时,它从服务器接收并发回准备好的消息
  • 服务器获取就绪消息并发送工作单元。
  • 服务器还会为最终的"我完成"发出非阻塞接收。消息。
  • 当任何客户端完成后,它会发出“我完成了”,给我更多信息"消息
  • 服务器将下一个工作单元发送到队列中。

答案 1 :(得分:0)

我通过在从根发送之前向MPI例程添加更多逻辑来解决此问题:

if i > size - 1:
  #probe for response, and send next message to proc that responds first
  #sleep 1 second between probing different procs
  r = 1
  while not comm.Iprobe(source = r):
    time.sleep(1)
    r = (r % (size - 1)) + 1
  res = comm.Recv([res, dtype], source = r)
  proc = r
else:
  #initialize the sends in serial (proc 1, ..., size-1)
  proc = i + 1

在MPI中还有另一种方法吗?

答案 2 :(得分:0)

我刚做了类似于your answer的事情,但我可以提供几种选择。我还发布了simplified version of my code

首先,您可以通过侦听任何工作进程的第一个响应来提高响应速度。为此目的,有一个特殊的源值MPI.ANY_SOURCE。使用状态对象来确定消息来自哪个实际来源。

# Set this to 0 for maximum responsiveness, but that will peg CPU to 100%
sleep_seconds = 0.1
if sleep_seconds > 0:
    while not comm.Iprobe(source=MPI.ANY_SOURCE):
        time.sleep(sleep_seconds)

status = MPI.Status()
result = comm.recv(source=MPI.ANY_SOURCE, status=status)
logging.info('Received %r from rank %d', result, status.Get_source())

我做了一些搜索,发现a busy wait is what you want如果您没有与其他任务共享处理器。在这种情况下,您只需在我的代码段中将sleep_seconds设置为0,或者直接调用recv()。我们有时不得不分享我们的环境,所以我要进行民意调查。

JörgBornschein的mpi4py-examples包括Task Pull example,它在一组工人中分配不同长度的任务。我认为用上面的代码片段替换他对recv()的调用会给你一个很好的解决方案。

我有一些额外的皱纹,我的一些工作人员发起了多线程任务,所以我不想超额认购我的处理器。我发布了a multithreading gist,知道每个任务将启动多少线程,并为额外的线程留出额外的工作人员。例如,如果任务将在四个线程上运行,则主服务器将等待,直到同一主机上有四个工作程序可用,然后它将任务传递给其中一个工作程序,并使其他三个空闲,直到任务完成

答案 3 :(得分:0)

或许使用一个排名作为分配作业的服务器最适合负载平衡:

#!/usr/bin/env python
import mpi4py

import numpy as np
import os
import time

from mpi4py import MPI

comm = MPI.COMM_WORLD
rank = comm.Get_rank()
size = comm.Get_size()
root = 0

if rank == root:
  for i in range(50):
    d = comm.recv(source = MPI.ANY_SOURCE)
    comm.send(i, dest = d)
  for i in range(size-1):
    d = comm.recv(source = MPI.ANY_SOURCE)
    comm.send(-1, dest = d)
    print('Closing', d)
else:
    while True:
        comm.send(rank, root)
        job = comm.recv(source = root)
        if job < 0: break

        print('Rank {} Job {}'.format(rank, job))
        time.sleep(np.random.random()%10)