繁体   English   中英

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

[英]Python, efficient paralell operation using a dict

首先抱歉我的英语不够完美。

我想,我的问题很容易解释。

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秒)。

我的第一个尝试是使用prange()在cython中执行此操作(因此我可以从cython优化和并行执行中获益),但问题是(一如既往),GIL:在prange()中我可以管理列表和使用cython memviews的元组,但是我不能在dict中插入我的结果。

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

第二个问题是类似的:

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的键

正如您所看到的,我的片段非常简单,可以在并列中运行。

我想尝试使用numba,但可能会因为GIL而相同,我更喜欢使用cython,因为我需要一个二进制代码(这个库可能是商业软件的一部分,所以只允许使用二进制库)。

一般来说我想避免使用c / c ++函数,我希望找到一种方法来管理像dicts / list这样的东西,并且在Python域中尽可能地保留cython性能; 但我对所有建议持开放态度。

谢谢

编辑

由于该方法基本上在数据样本和阈值之间执行外部产品,因此显着增加了所需的存储器,这可能是不期望的。 这里可以找到改进的方法。 我保留这个答案以供将来参考,因为在这个答案中提到了它。

我发现与OP的代码相比,性能提高了~ 20倍。


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

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)}

@ 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()时间很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)

通过使用numpy的矢量化功能,可以实现多项性能改进:

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

在以下测试中,我使用了data.shape == (200000, 3); thresh.shape == (1000,) data.shape == (200000, 3); thresh.shape == (1000,)如OP中所示。 我也省略了对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 ,这可能会降低程序的速度,因为可能需要重新分配内存。 相反,它会在一次尝试中创建每个列表(每个阈值)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM