最近,当我使用多处理模块和 mpi4py 作为通信工具来测量并行应用程序的性能时,我观察到了一种奇怪的效果。
该应用程序对数据集执行进化算法。除求值外,大多数操作都是按顺序完成的。在应用所有进化算子之后,所有个体都需要接收新的适应度值,这是在评估期间完成的。基本上它只是对浮点数列表(python 的)执行的数学计算。在评估之前,数据集由 mpi 的 scatter 或 python 的 Pool.map 分散,然后是并行评估,然后数据通过 mpi 的收集或再次通过 Pool.map 机制返回。
我的基准测试平台是运行 Ubuntu 11.10 的虚拟机(virtualbox),Core i7(4/8 核)上具有 Open MPI 1.4.3、8 GB RAM 和 SSD 驱动器。
我发现真正令人惊讶的是,我获得了不错的加速,但是取决于通信工具,经过一定的进程阈值后,性能变得更差。可以通过下面的图片来说明。
y 轴 - 处理时间
x 轴 - 处理
颜色的 nr - 每个个体的大小(浮动的 nr)
1)使用多处理模块 - Pool.map
2) 使用 mpi - 分散/聚集
3) 两张图片相互重叠
起初我认为这是超线程的错,因为对于大型数据集,它在达到 4 个进程(4 个物理核心)后会变慢。但是,它在多处理情况下也应该可见,但不是。我的另一个猜测是 mpi 通信方法远不如 python 有效,但我觉得很难相信。
有人对这些结果有任何解释吗?
添加:
毕竟,我开始相信这是超线程的错误。我在具有核心 i5(2/4 核心)的机器上测试了我的代码,并且 3 个或更多进程的性能更差。我想到的唯一解释是我正在使用的 i7 没有足够的资源(缓存?)来计算与超线程同时进行的评估,并且需要安排超过 4 个进程在 4 个物理内核上运行。
然而有趣的是,当我使用 mpi htop 显示所有 8 个逻辑核心的完全利用,这应该表明上述陈述是不正确的。另一方面,当我使用 Pool.Map 时,它并没有完全利用所有内核。它最多使用一个或 2 个,其余的只是部分使用,同样不知道它为什么会这样。明天我将附上显示此行为的屏幕截图。
我没有在代码中做任何花哨的事情,它真的很简单(我没有给出整个代码不是因为它是秘密的,而是因为它需要安装额外的库,如 DEAP。如果有人真的对这个问题感兴趣并准备好了要安装 DEAP,我可以准备一个简短的示例)。MPI 的代码有点不同,因为它不能处理人口容器(从列表继承)。当然有一些开销,但没什么大不了的。除了我在下面显示的代码之外,其余部分是相同的。
池.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 核)上进行了一些测试,结果如下:
我还找到了一台具有 2 个至强(2x 6/12 内核)的机器并重复了基准测试:
现在我有 3 个相同行为的示例。当我在比物理内核更多的进程中运行我的计算时,它开始变得更糟。我相信是因为资源不足,同一物理核心上的进程无法同时执行。