joblib在2个内核上进行2次独立计算的并行化比串行慢

时间:2015-01-19 15:39:43

标签: python numpy parallel-processing joblib

我试图将一些数据扩展与numpy并行化,我发现并行化版本比串行版本要长几个数量级,所以我必须犯一些愚蠢的错误。

首先,一些假数据来设置问题:

Ngroups = 1.e6
some_group_property = np.random.uniform(0, 100, Ngroups)
mem1_occupation = np.random.random_integers(0, 5, Ngroups)
mem2_occupation = np.random.random_integers(0, 5, Ngroups)
occupation_list = [mem1_occupation, mem2_occupation]

现在进行连续计算:我将组数据扩展到组成员的数组:

mem1_property = np.repeat(some_group_property, mem1_occupation)
mem2_property = np.repeat(some_group_property, mem2_occupation)

这是并行版本:

import functools
from joblib import Parallel, delayed

def expand_data(prop, occu_list, index):
    return np.repeat(prop, occu_list[index])
exp_data_1argfunc = functools.partial(expand_data, some_group_property, occupation_list)

result = Parallel(n_jobs=2)(delayed(exp_data_1argfunc)(i) for i in range(len(occupation_list)))  

我在4核机器上运行此代码,因此原则上对两个群体独立执行计算应该给我大约2倍的加速。相反,串行计算需要~0.1s,而并行计算需要9s。这里发生了什么?

1 个答案:

答案 0 :(得分:2)

首先:

  

我在4核机器上运行此代码,因此原则上对两个群体独立执行计算应该给我大约2倍的加速。

没有。一般来说,与线程数线性成比例的加速将是绝对最佳情况,假设:

  1. 确定执行代码所需时间的限制因素是所需的CPU时钟周期数(即不是磁盘I / O,内存分配等)。
  2. 您的代码中没有任何部分无法并行化
  3. 由于并行化而没有额外的开销
  4. 在实践中,这些标准永远不会完全达到,所以你绝对不应该自动假设线性加速是可能的。

    话虽如此,通过删除中间functools.partial函数声明,你的并行示例可以加速很多:

    def parallel_1(some_group_property, occupation_list):
        """ original version """
        exp_partial = functools.partial(expand_data, some_group_property,
                                        occupation_list)
        return Parallel(n_jobs=2)(
            delayed(exp_partial)(i)
            for i in range(len(occupation_list))
        )
    
    
    def parallel_2(some_group_property, occupation_list):
        """ get rid of functools.partial """
        return Parallel(n_jobs=2)(
            delayed(expand_1)(some_group_property, occupation_list, i)
            for i in range(len(occupation_list))
        )
    
    
    In [40]: %timeit parallel_1(some_group_property, occupation_list)
    1 loops, best of 3: 7.24 s per loop
    
    In [41]: %timeit parallel_2(some_group_property, occupation_list)
    1 loops, best of 3: 375 ms per loop
    

    在Python multiprocessing中,函数及其参数在发送到工作线程之前被腌制,然后在工作线程中取消它们并执行该函数。我怀疑减速可能与functools.partial物体更难以腌制/捣蛋有关,尽管我真的不确定原因是什么。

    你也可以通过只传递"职业"特定线程所需的数组,而不是包含所有这些数组的列表:

    def parallel_3(some_group_property, occupation_list):
        """ pass only a single occu array to each worker thread """
        return Parallel(n_jobs=2)(
            delayed(np.repeat)(some_group_property, oo)
            for oo in occupation_list
        )
    
    In [44]: %timeit parallel_3(some_group_property, occupation_list)
    1 loops, best of 3: 353 ms per loop
    

    但是,这仍然不能与单线程版本的性能相匹配:

    def serial_version(some_group_property, occupation_list):
        return [np.repeat(some_property_group, oo)
                for oo in occupation_list]
    
    In [46]: %timeit serial_version(some_group_property, occupation_list)
    10 loops, best of 3: 46.1 ms per loop
    

    这可能只是意味着并行化所涉及的额外开销(启动两个工作线程,剔除/取消函数及其参数等)大大超过了并行计算两个数组所带来的性能提升。

    我想你可能会看到更大的数组在并行化方面有一些好处,其中并行版本在实际执行有用计算时花费的时间相应更多,而不仅仅是设置和杀死工作线程。

相关问题