使用mpi而不是多处理模块时,python中的并行应用程序变得慢得多

Mic*_*hal 20 python mpi multiprocessing evolutionary-algorithm

最近,当我使用多处理模块和mpi4py作为通信工具测量并行应用程序的性能时,我观察到了一种奇怪的效果.

该应用程序对数据集执行进化算法.除评估外,大多数操作都是按顺序完成的.在应用所有进化运算符之后,所有个体都需要接收新的适应值,这在评估期间完成.基本上它只是在浮点列表(python之类)上执行的数学计算.在评估之前,数据集由mpi的scatter或python的Pool.map分散,然后进行并行评估,之后数据通过mpi的聚集或Pool.map机制返回.

我的基准平台是运行Ubuntu 11.10的虚拟机(虚拟机),在Core i7(4/8内核)上运行Open MPI 1.4.3,8 GB内存和SSD驱动器.

我发现真正令人惊讶的是,我获得了一个很好的加速,但是根据通信工具,在一定的进程阈值之后,性能变得更糟.它可以通过下面的图片来说明.

y轴 - 处理时间
x轴 - 过程
颜色的nr - 每个个体的大小(浮动的nr)

1)使用多处理模块 - Pool.map 在此输入图像描述

2)使用mpi - Scatter/Gather 在此输入图像描述

3)两张照片在彼此的顶部 在此输入图像描述

起初我认为它是超线程的错误,因为对于大型数据集,它在达到4个进程(4个物理内核)后变得更慢.但是它应该在多处理情况下也可见,而事实并非如此.我的另一个猜测是mpi通信方法比python方法效果差得多,但我发现很难相信.

有没有人对这些结果有任何解释?

添加:

毕竟我开始相信这是超线程故障.我在具有核心i5(2/4核心)的机器上测试了我的代码,并且在3个或更多进程中性能更差.我想到的唯一解释是,我使用的i7没有足够的资源(缓存?)来与超线程同时计算评估,需要安排4个以上的进程在4个物理内核上运行.

然而有趣的是,当我使用mpi htop显示完全利用所有8个逻辑核心时,这应该表明上述语句是不正确的.另一方面,当我使用Pool.Map时,它并没有完全利用所有核心.它使用一个或2个最大值而其余部分仅使用部分,再次不知道它为什么会这样.明天我将附上显示此行为的屏幕截图.

我没有在代码中做任何花哨的东西,它真的很简单(我不是因为它的秘密,而是因为它需要安装DEAP之类的其他库而不是给出整个代码.如果有人真的对这个问题感兴趣并准备好了安装DEAP我可以准备一个简短的例子).MPI的代码有点不同,因为它无法处理填充容器(从列表继承).当然有一些开销,但没什么大不了的.除了我在下面显示的代码,其余部分是相同的.

Pool.map:

def eval_population(func, pop):
    for ind in pop:
        ind.fitness.values = func(ind)

    return pop

# ...
self.pool = Pool(8)
# ...

for iter_ in xrange(nr_of_generations):
    # ...
    self.pool.map(evaluate, pop) # evaluate is really an eval_population alias with a certain function assigned to its first argument.
    # ...
Run Code Online (Sandbox Code Playgroud)

MPI - 分散/聚集

def divide_list(lst, n):
    return [lst[i::n] for i in xrange(n)]

def chain_list(lst):
    return list(chain.from_iterable(lst))

def evaluate_individuals_in_groups(func, rank, individuals):
    comm = MPI.COMM_WORLD
    size = MPI.COMM_WORLD.Get_size()

    packages = None
    if not rank:
        packages = divide_list(individuals, size)

    ind_for_eval = comm.scatter(packages)
    eval_population(func, ind_for_eval)

    pop_with_fit = comm.gather(ind_for_eval)

    if not rank:
        pop_with_fit = chain_list(pop_with_fit)
        for index, elem in enumerate(pop_with_fit):
            individuals[index] = elem

for iter_ in xrange(nr_of_generations):
        # ...
        evaluate_individuals_in_groups(self.func, self.rank, pop)
        # ...
Run Code Online (Sandbox Code Playgroud)

补充2: 正如我之前提到的,我在i5机器上进行了一些测试(2/4核心),结果如下: 在此输入图像描述

我还发现了一台2 xeon(2x 6/12核心)的机器并重复了基准测试: 在此输入图像描述

现在我有3个相同行为的例子.当我在比物理内核更多的进程中运行计算时,它开始变得更糟.我相信这是因为由于缺乏资源,同一物理核心上的进程无法同时执行.

Jen*_*man 6

MPI实际上是为了进行节点间通信而设计的,因此可以通过网络与其他机器通信.与例如线程相比,在同一节点上使用MPI会导致每个必须发送的消息产生很大的开销.

mpi4py为每条消息制作一份副本,因为它针对的是分布式内存使用情况.如果您的OpenMPI未配置为使用sharedmemory进行内部节点通信,则此消息将通过内核的tcp堆栈发送,然后返回,以便传递给另一个进程,这将再次增加一些开销.

如果您只打算在同一台机器上进行计算,则无需在此处使用mpi.

其中一些在此主题中讨论.

更新IPC-标杆项目试图让某种意义上说出来的通信类型不同的系统上有什么不同表现.(多核,多处理器,共享内存)尤其是它如何影响虚拟化机器!

我建议在虚拟机上运行ipc-benchmark,并发布结果.如果他们看起来像这个基准测试,它可以让你深入了解tcp,套接字和管道之间的区别.