我试图将一些数据扩展与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。这里发生了什么?
答案 0 :(得分:2)
首先:
我在4核机器上运行此代码,因此原则上对两个群体独立执行计算应该给我大约2倍的加速。
没有。一般来说,与线程数线性成比例的加速将是绝对最佳情况,假设:
在实践中,这些标准永远不会完全达到,所以你绝对不应该自动假设线性加速是可能的。
话虽如此,通过删除中间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
这可能只是意味着并行化所涉及的额外开销(启动两个工作线程,剔除/取消函数及其参数等)大大超过了并行计算两个数组所带来的性能提升。
我想你可能会看到更大的数组在并行化方面有一些好处,其中并行版本在实际执行有用计算时花费的时间相应更多,而不仅仅是设置和杀死工作线程。