用于在Python中更新共享字典的多处理模块

时间:2015-12-11 10:06:53

标签: python python-multiprocessing

我正在创建一个字典如下:

y=[(1,2),(2,3),(1,2),(5,6)]

dict={}

for tup in y:
    tup=tuple(sorted(tup))
    if tup in dict.keys():
        dict[tup]=dict[tup]+1
    else:
        dict[tup]=1

但是我的实际y包含大约4千万个元组,有没有办法使用多处理来加速这个过程?

由于

5 个答案:

答案 0 :(得分:3)

如果您想要忽略订单的计数,请使用带有计数器的frozenset

from collections import Counter

print(Counter(map(frozenset, y)))

使用其他答案中的tuples

In [9]: len(tuples)
Out[9]: 500000

In [10]: timeit Counter(map(frozenset, tuples))
1 loops, best of 3: 582 ms per loop

使用冻结集将意味着(1, 2)(2,1)将被视为相同:

In [12]: y = [(1, 2), (2, 3), (1, 2), (5, 6),(2, 1),(6,5)]

In [13]: from collections import Counter

In [14]: 

In [14]: print(Counter(map(frozenset, y)))
Counter({frozenset({1, 2}): 3, frozenset({5, 6}): 2, frozenset({2, 3}): 1})

如果使用多处理应用相同的逻辑,它显然会快得多,即使它没有击败使用多处理提供的内容。

答案 1 :(得分:2)

您可以遵循MapReduce方法。

from collections import Counter
from multiprocessing import Pool

NUM_PROCESSES = 8

y = [(1,2),(2,3),(1,2),(5,6)] * 10

## http://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks-in-python
def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in xrange(0, len(l), n):
        yield l[i:i+n]

## map
partial_counters = Pool(NUM_PROCESSES).map(Counter, chunks(y, NUM_PROCESSES))

## reduce
reduced_counter = reduce(Counter.__add__, partial_counters)

## Result is:
## Counter({(1, 2): 20, (5, 6): 10, (2, 3): 10})

这个想法是:

  1. 将输入列表拆分为块
  2. 将每个块提供给一个独立的进程,该进程将独立计算计数
  3. 通过减少操作将所有部分计数合并在一起。
  4. 编辑:使用chunks(map(frozenset, y), NUM_PROCESSES)来计算无序对。

答案 2 :(得分:0)

首先,不是在每次迭代中检查tupdict.keys的成员资格,而是一个非常糟糕的想法,您可以使用collections.defaultdict()来实现更加pythonic的目标:

from collections import defaultdict
test_dict = defaultdict(lambda:1)

for tup in y:
    tup=tuple(sorted(tup))
    test_dict[tup]=+1

其次,如果你想使用并发,你可能想要使用多线程或多处理,但是关于多线程,由于GIL多线程不能一次执行一个字节码,你无法遍历你的元组双方都喜欢BDS算法。

但是对于多处理,你会遇到另一个问题,即从每个核心访问一个共享内存并立即处理它以获取更多信息,请阅读此答案https://stackoverflow.com/a/30132673/2867928

那么现在的诀窍是什么?

一种方法是将列表划分为小块,并使用多线程将您的函数应用于指定的部分。

另一种方法是使用coroutins和子程序,正如答案中提到的那样,Greg Ewing有一个great demonstration如何使用yield来使用协同程序来构建调度程序或多行为者模拟之类的东西。

答案 3 :(得分:0)

修改:编辑回答为线程安全的答案

multiprocessing模块让它变得简单。

只需重构代码即可在函数中完成处理:

def process_tuple(tuples):
    count_dict = {}
    for tuple_ in tuples:
        tuple_=tuple(sorted(tuple_))
        if tuple_ in count_dict:
            count_dict[tuple_] += 1
        else:
            count_dict[tuple_] = 1
    return count_dict

将元组列表拆分为小组,然后使用map处理所有组。

## http://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks-in-python
def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in xrange(0, len(l), n):
        yield l[i:i+n]

# cut tuples list into 5 chunks
tuples_groups = chunks(tuples, 5)
pool = Pool(5)
count_dict = {}
# processes chunks in parallel
results = pool.map(process_tuple, tuples_groups)
# collect results
for result in results:
    count_dict.update(result)

multiprocessing.Pool将处理线程之间的分配。

完整示例+基准:

import time
import random

start_time = time.time()
tuples = []
x,y = (100000, 10)
for i in range(x):
    tuple_ = []
    for j in range(y):
        tuple_.append(random.randint(0, 9))
    tuples.append(tuple(tuple_))

print("--- %s data generated in %s seconds ---" % (x*y, time.time() - start_time))



def process_tuple(tuples):
    count_dict = {}
    for tuple_ in tuples:
        tuple_=tuple(sorted(tuple_))
        if tuple_ in count_dict:
            count_dict[tuple_] += 1
        else:
            count_dict[tuple_] = 1
    return count_dict

from multiprocessing import Pool

start_time = time.time()

## http://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks-in-python
def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in xrange(0, len(l), n):
        yield l[i:i+n]

# cut tuples list into 5 chunks
tuples_groups = chunks(tuples, 5)
pool = Pool(5)
count_dict = {}
# processes chunks in parallel
results = pool.map(process_tuple, tuples_groups)
# collect results
for result in results:
    count_dict.update(result)

print("--- Multithread processed in %s seconds ---" % (time.time() - start_time))    



start_time = time.time()
count_dict = {}
for tuple_ in tuples:
    tuple_=tuple(sorted(tuple_))
    if tuple_ in count_dict:
        count_dict[tuple_] += 1
    else:
        count_dict[tuple_] = 1

print("--- Single thread processed in %s seconds ---" % (time.time() - start_time))
  

---在32.7803010941秒内生成10000000数据---
  ---多线程处理1.79116892815秒---
  ---单线程在2.65010404587秒处理---

答案 4 :(得分:0)

因为你想增加计数(不是简单地创建新的键/值对),所以字典不是线程安全的,除非你获得每个更新周围的信号量并在之后释放它 - 所以我不认为你将获得任何整体速度增益,实际上它可能会更慢。

如果您要对此进行线程化,那么每个线程最好更新自己的字典,然后在每个线程完成时合并结果,这样就毫无疑问是线程安全性。但是因为它可能是CPU限制的,所以你应该使用多处理而不是线程 - 多处理可以利用你所有的CPU核心。

另外,如果你使用collections.Counter,它会为你做计数,并支持合并,并且有一个有用的方法most_common(n)返回n个最高计数的键。