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

用于在Python中更新共享字典的多处理模块,python,python-multiprocessing,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包含大约4000万个元组,有没有办法使用多重处理来加速这个过程 谢谢首先,不要在每次迭代中检查dict.keys中的t

我正在创建一本字典,如下所示:

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
包含大约4000万个元组,有没有办法使用多重处理来加速这个过程


谢谢

首先,不要在每次迭代中检查
dict.keys
中的
tup
的成员身份,这是一个非常糟糕的主意,你可以使用
collections.defaultdict()
来实现这个更具python风格的目标:

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

for tup in y:
    tup=tuple(sorted(tup))
    test_dict[tup]=+1
第二,如果您想使用并发,您可能想使用多线程或多处理,但关于多线程,由于多线程不能一次执行一个字节码,并且不能像BDS算法那样从两侧遍历元组

但对于多处理,您将遇到另一个问题,即从每个核心访问一个共享内存,并立即处理它,以获取更多信息阅读此答案

那么现在的诀窍是什么呢

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


另一种方法是使用协程和子例程,正如回答中提到的那样,Greg Ewing对如何使用从的收益率来使用协程来构建诸如调度程序或许多参与者模拟之类的东西有一个很好的理解。

编辑:答案编辑为线程安全

这个模块使它变得简单

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

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
将元组列表拆分为一个小组,然后用于处理所有组

## 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.780300941秒内生成10000000个数据--
---多线程处理只需1.79116892815秒--
---单线程处理时间为2.650104587秒--


因为您希望增加计数(而不是简单地创建新的键/值对),所以字典不是线程安全的,除非您在每次更新前后获取一个信号量并在更新后释放它——所以我认为您不会获得任何总体速度增益,事实上它可能会更慢

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

此外,如果您使用collections.Counter,它将为您进行计数,并支持合并,并且有一种最常用的方法(n)返回计数最高的n个键。

您可以采用一种方法

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})
这个想法是:

  • 将您的输入列表拆分为块
  • 将每个区块馈送到单独的进程,该进程将独立计算计数
  • 通过减少操作将所有部分计数合并在一起

  • 编辑:使用
    块(map(frozenset,y),NUM_进程)
    来解释无序对。

    如果您想获得忽略顺序的计数,请使用带有计数器的
    frozenset

    from collections import Counter
    
    print(Counter(map(frozenset, y)))
    
    使用另一个答案中的
    元组

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

    如果使用多处理应用相同的逻辑,显然会快得多,即使没有它,也比使用多处理提供的要好。

    对于池处理,您不会显示每个count dict中的结果是如何从单个进程返回到一起的-这是如何工作的?我在全局上下文中声明
    count dict
    并让所有线程访问它,这有点作弊。它不是线程安全的,但在这里不会造成伤害。我在回答中添加了一条建议。这是一个相当大的问题,因为每个进程实际上都有自己的计数-您的代码是某个事物的基准,似乎并不是问题的完整答案。您的pool.map代码是否使每个进程的元组进程成为元组的完整列表?这有什么帮助?过程中的代码\u tuple不会迭代元组,只是对其进行排序(!)并将整个元组添加到字典中……这应该是公认的答案,@mrucci version不适用于无序元组。我的只是展示了“如何”而没有扩展更好的方法。没错,我没有注意到无序元组的要求。但是问题是关于“一种使用多处理的方法”,没有人愿意提供关于如何并行化他想做的事情的实际例子。谢谢,我使用了这个答案,因为我想加快实现的速度