【问题标题】:Python, efficient paralell operation using a dictPython,使用字典的高效并行操作
【发布时间】:2019-01-04 14:32:04
【问题描述】:

首先对不起我的英语不完美。

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

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 元组,我必须非常快地执行此操作(在普通电脑上最多 2/3 秒)。

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

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

如你所见,我的 sn-ps 并行运行非常简单。

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

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

谢谢

【问题讨论】:

  • 您可以查看numpy,因为您的问题可以向量化。首先计算每个元组的阈值索引,然后您可以根据索引创建字典。这将产生显着的加速。
  • multiprocessing 可以并行化此任务 - docs.python.org/2/library/multiprocessing.html
  • 代替result={}, .../lines/of/code..result[value]=[]; result[value].append(tuple),你可以使用result=defaultdict(list)..../lines/of/code....result[value].append(tuple)defaultdictcollections 模块中可用 - from collections import defaultdict
  • @a_guest 我不知道矢量化是什么,我认为您在谈论en.wikipedia.org/wiki/Array_programming 您能更好地说明 numpy 如何帮助我解决矢量化问题吗? numpy 能解决我的多核问题吗?
  • @Shiva 是的,我考虑过这个选项,但我认为不能将多处理本机库与 cython 一起使用,并且使用 cython 单核(因此使用 GIL、dict、ecc)的速度比使用更多只是多线程中的python。对于你是对的集合字典,我会使用它。

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


【解决方案1】:

编辑

由于这种方法基本上是在数据样本和阈值之间执行外积,它会显着增加所需的内存,这可能是不希望的。 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)}

【讨论】:

  • 您的代码可能会加快tuples condition 的计算速度,但它不会向量化字典的创建。通常对数组的迭代比对列表的迭代慢(这也适用于zip 迭代)。
  • @hpaulj 你认为 dict 创建是这里的瓶颈吗? OP 的代码包含 two explicit Python for 循环约 200,000,000 次迭代,它们都被矢量化,然后减少到 one for 循环组成约 1,000 次迭代(注意因子是样本数,即&gt;&gt; 阈值数)。即使数组迭代比列表迭代慢,这对于这里执行的少数迭代也几乎没有作用。也请随意比较两种解决方案的性能以说服自己。
  • 我添加了一些比较。对于您的小示例,OP 更快,但对于更大的情况,您的更快。
【解决方案2】:

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

【讨论】:

  • 性能增益将随着数据样本的数量而增加,因为这是完全矢量化的循环。 thresh 上的循环保持不变,并添加了该循环的矢量化版本。根据 OP thresh.size == 1000,因此增加的开销很小。当然,如果样本数量也很少,那么开销(包括转换)占主导地位。但是由于data.shape == (200000, 3)(根据OP),这个循环的性能增益占主导地位。与您的“大数据”示例相比,我预计性能提升会更加显着。
【解决方案3】:

还可以通过使用numpy 的矢量化功能来实现一些性能改进:

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

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

申请 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 的性能提升。这种方法的另一个好处是它不包含列表的增量appends,这可能会降低程序的速度,因为可能需要重新分配内存。相反,它会在一次尝试中创建每个列表(每个阈值)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-01
    相关资源
    最近更新 更多