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

时间:2013-06-11 21:52:25

标签: python multiprocessing mpi evolutionary-algorithm

当我使用多处理模块和mpi4py作为通信工具测量并行应用程序的性能时,我发现了一个奇怪的效果。

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

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

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

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

1)使用多处理模块 - Pool.map enter image description here

2)使用mpi - Scatter / Gather enter image description here

3)两张照片都在彼此之上 enter image description here

起初我认为它是超线程的错误,因为对于大型数据集,它在达到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.
    # ...

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)
        # ...

已添加2: 正如我之前提到的,我在i5机器上进行了一些测试(2/4核心),结果如下: enter image description here

我还发现了一台2 xeon(2x 6/12核心)的机器并重复了基准测试: enter image description here

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

1 个答案:

答案 0 :(得分:6)

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

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

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

其中一些内容已经讨论in this thread.

<强>更新 ipc-benchmark项目试图弄清楚不同通信类型在不同系统上的表现。 (多核,多处理器,共享内存)尤其是它如何影响虚拟化机器!

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