Python,使用dict进行有效的并行操作

时间:2018-07-27 12:54:34

标签: python c++11 parallel-processing cython numba

对不起,我的英语不完美。

我认为我的问题很容易解释。

result={}
list_tuple=[(float,float,float),(float,float,float),(float,float,float)...]#200k tuples
threshold=[float,float,float...] #max 1k values
for tuple in list_tuple:
    for value in threeshold:
    if max(tuple)>value and min(tuple)<value:
        if value in result:
            result[value].append(tuple)
        else:
            result[value]=[]
            result[value].append(tuple) 

list_tuple包含大约200k个元组,我必须非常快地执行此操作(在普通PC上最多2/3秒)。

我的第一个尝试是在cython中使用prange()执行此操作(这样我可以从cython优化和paralell执行中受益),但是问题是(一如既往),GIL:在prange()中我可以使用cython memviews管理列表和元组,但是我无法将结果插入字典中。

在cython中,我还尝试使用c ++ std的unordered_map,但是现在的问题是我无法在c ++中创建数组的向量(这将是我的字典的值)。

第二个问题类似:

list_tuple=[((float,float),(float,float)),((float,float),(float,float))...]#200k tuples of tuples

result={list_tuple[0][0]:[]}

for tuple in list_tuple:
    if tuple[0] in result:
        result[tuple[0]].append(tuple)
    else:
        result[tuple[0]]=[]

这里我还有另一个问题,如果要使用prange(),我必须使用自定义哈希函数来将数组用作c ++ unordered_map的键

您可以看到我的代码段在paralell中运行非常简单。

我想尝试使用numba,但由于GIL可能会相同,并且我更喜欢使用cython,因为我需要一个二进制代码(该库可能是商业软件的一部分,因此仅允许二进制库)

总的来说,我想避免使用c / c ++函数,我希望找到的是一种以cython性能并行管理dicts / lists之类的方法,并尽可能地保留在Python域中;但我愿意接受所有建议。

谢谢

3 个答案:

答案 0 :(得分:0)

编辑

由于该方法基本上在数据样本和阈值之间执行外积,因此会显着增加所需的内存,而这可能是不希望的。 An improved approach can be found here.仍然保留此答案,因为this answer中已提及。

我发现与OP的代码相比,性能的提高是~ 20的一个因素。


这是使用numpy的示例。数据被矢量化,操作也被矢量化。请注意,与OP的示例相反,结果dict包含空列表,因此,如果适当,可能需要额外的清理步骤。

import numpy as np

# Data setup
data = np.random.uniform(size=(200000, 3))
thresh = np.random.uniform(size=1000)

# Compute tuples for thresholds.
condition = (
    (data.min(axis=1)[:, None] < thresh)
    & (data.max(axis=1)[:, None] > thresh)
)
result = {v: data[c].tolist() for c, v in zip(condition.T, thresh)}

答案 1 :(得分:0)

@a_guest的代码:

def foo1(data, thresh):
    data = np.asarray(data)
    thresh = np.asarray(thresh)
    condition = (
       (data.min(axis=1)[:, None] < thresh)
       & (data.max(axis=1)[:, None] > thresh)
       )
    result = {v: data[c].tolist() for c, v in zip(condition.T, thresh)}
    return result

此代码为thresh中的每个项目创建一次字典条目。

OP代码,用default_dict(来自collections)进行了简化:

def foo3(list_tuple, threeshold):
    result = defaultdict(list)
    for tuple in list_tuple:
        for value in threeshold:
            if max(tuple)>value and min(tuple)<value:
                result[value].append(tuple)
    return result

此条目针对符合条件的每个条目更新一次字典条目。

以及他的样本数据:

In [27]: foo1(data,thresh)
Out[27]: {0: [], 1: [[0, 1, 2]], 2: [], 3: [], 4: [[3, 4, 5]]}
In [28]: foo3(data.tolist(), thresh.tolist())
Out[28]: defaultdict(list, {1: [[0, 1, 2]], 4: [[3, 4, 5]]})

时间测试:

In [29]: timeit foo1(data,thresh)
66.1 µs ± 197 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
# In [30]: timeit foo3(data,thresh)
# 161 µs ± 242 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [31]: timeit foo3(data.tolist(),thresh.tolist())
30.8 µs ± 56.4 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

在数组上的迭代比使用列表慢。 tolist()的时间很短; np.asarray用于列表更长。

使用更大的数据样本,array版本更快:

In [42]: data = np.random.randint(0,50,(3000,3))
    ...: thresh = np.arange(50)
In [43]: 
In [43]: timeit foo1(data,thresh)
16 ms ± 391 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [44]: %%timeit x,y = data.tolist(), thresh.tolist() 
    ...: foo3(x,y)
    ...: 
83.6 ms ± 68.6 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

答案 2 :(得分:0)

也可以通过使用numpy的矢量化功能来实现若干性能改进:

  1. 当前为每个阈值重新计算minmax值。相反,它们可以预先计算然后再用于每个阈值。
  2. 对数据样本(list_tuple)的循环是在纯Python中执行的。可以使用numpy对这个循环进行矢量化处理。

在以下测试中,我按照OP中的指示使用了data.shape == (200000, 3); thresh.shape == (1000,)。我还省略了对result dict的修改,因为根据数据,这可能会迅速使内存溢出。

正在应用1。

v_min = [min(t) for t in data]
v_max = [max(t) for t in data]
for mi, ma in zip(v_min, v_max):
    for value in thresh:
        if ma > value and mi < value:
            pass

与OP的代码相比,它的性能提高了~ 5

应用1.和2。

v_min = data.min(axis=1)
v_max = data.max(axis=1)
mask = np.empty(shape=(data.shape[0],), dtype=bool)
for t in thresh:
    mask[:] = (v_min < t) & (v_max > t)
    samples = data[mask]
    if samples.size > 0:
        pass

与OP的代码相比,它的性能提高了~ 30。这种方法的另一个好处是它不包含列表的增量append,这可能会减慢程序的速度,因为可能需要重新分配内存。而是一次尝试创建每个列表(按阈值)。