在将结果附加到字典的for循环上使用python多处理

在将结果附加到字典的for循环上使用python多处理,python,python-3.x,dictionary,multiprocessing,Python,Python 3.x,Dictionary,Multiprocessing,所以我看了多处理模块的文档,也看了这里提出的其他问题,没有一个问题与我的情况类似,因此我开始了一个新问题 为了简单起见,我有一段代码: # simple dataframe of some users and their properties. data = {'userId': [1, 2, 3, 4], 'property': [12, 11, 13, 43]} df = pd.DataFrame.from_dict(data) # a function that gene

所以我看了多处理模块的文档,也看了这里提出的其他问题,没有一个问题与我的情况类似,因此我开始了一个新问题

为了简单起见,我有一段代码:

# simple dataframe of some users and their properties.
data = {'userId': [1, 2, 3, 4],
        'property': [12, 11, 13, 43]}
df = pd.DataFrame.from_dict(data)

# a function that generates permutations of the above users, in the form of a list of lists
# such as [[1,2,3,4], [2,1,3,4], [2,3,4,1], [2,4,1,3]]
user_perm = generate_permutations(nr_perm=4)

# a function that computes some relation between users
def comp_rel(df, permutation, user_dict):
    df1 = df.userId.isin(permutation[0])
    df2 = df.userId.isin(permutation[1])
    user_dict[permutation[0]] += permutation[1]
    return user_dict


# and finally a loop: 
user_dict = defaultdict(int)
for permutation in user_perm:
    user_dict = comp_rel(df, permutation, user_dict)    
我知道这段代码现在没有什么意义,但我只是写了一个小示例,它与我正在处理的实际代码的结构非常接近。该用户dict最终应该包含userid和一些值

我有实际的代码,它运行良好,给出了正确的dict和所有内容,但是。。。它在单个线程上运行。而且速度非常慢,记住我还有15个线程完全空闲

我的问题是,如何使用python的多处理模块来更改last for循环,并能够在所有可用的线程/内核上运行?我看了文档,不太容易理解

编辑:我正在尝试将池用作:

p = multiprocessing.Pool(multiprocessing.cpu_count())
p.map(comp_rel(df, permutation, user_dict), user_perm)
p.close()
p.join()
但是,这会中断,因为我正在使用该行:

user_dict = comp_rel(df, permutation, user_dict) 

在最初的代码中,我不知道在池完成后这些字典应该如何合并。

有两个部分需要分开-首先是计算本身,它正在为某个用户ID计算一些值。第二个是累加步骤,将该值添加到用户dict结果中

您可以将计算本身分离,以便它返回一个id、value的元组,并通过多处理将其散开,然后在主线程中累积结果:

from multiprocessing import Pool
from functools import partial
from collections import defaultdict

# We make this a pure function that just returns a result instead of mutating anything
def comp_rel(df, perm):
    ...
    return perm[0], perm[1]

comp_with_df = partial(comp_rel, df) # df is always the same, so factor it out
with Pool(None) as pool: # Pool(None) uses cpu_count automatically
    results = pool.map(comp_with_df, user_perm)

# Now add up the results at the end:
user_dict = defaultdict(int)
for k, v in results:
    user_dict[k] += v
或者,您也可以将Manager.dict对象直接传递到处理函数中,但这会稍微复杂一点,可能不会给您带来任何额外的速度

根据@masklin的建议,这里有一个稍微好一点的方法来避免内存开销:

user_dict = defaultdict(int)
with Pool(None) as pool:
    for k, v in pool.imap_unordered(comp_with_df, user_perm):
        user_dict[k] += v

这样,我们就可以在结果完成时将其相加,而不必首先将它们全部存储在中间列表中

在评论中进行简短讨论后,我决定使用以下方法发布解决方案:


它的工作原理与@tzaman相同,但它提供了处理异常的可能性。此外,该模块中还有更多有趣的功能,请检查。

您肯定需要了解。@OlvinRoght我知道有一些锁,但这也说明:然而,一些扩展模块,无论是标准的还是第三方的,设计为在执行压缩或哈希等计算密集型任务时释放GIL。多道处理似乎就是这样一个模块。@OlvinRoght假设他确实使用多道处理,那将不是一个真正的问题,尽管他说他有15个线程空闲,他的意思是cores@GPhilo,据我所知,我的机器有4个内核,每个内核有4个线程。如果我使用htop,我会看到16个自由线程。我们谈论的是线程还是内核?多处理模块中的示例展示了如何做到这一点:您可以使用一个池来触发每个comp_rel调用。由于您正在启动多个python进程,因此GIL不会成为问题。此外,由于结果的顺序似乎一点也不重要,您可能希望无序使用imap_,并在池中进行累积。这样,您就可以在结果生成时使用它们,python不需要大的重新排序缓冲区来按顺序返回元素。@Masklin with可能是一个更好的选择。它看起来像是一个低得多的级别的过程:在imap_无序的情况下,只有一次用户代码传递,当_完成时,您必须首先提交所有任务,可能会跟踪返回的期货,然后在完成时处理您从中获得的期货。实际上总共有2行代码。这是imap_无序的LOC的两倍。此外,它也不需要像partialpartial这样的hack,它是一种字面上没有意义的hack。您可以在处理完成后立即使用结果,以便。。。就像imap_无序,但仍然没有那么好?@Qubix这个答案的全部目的是不让用户dict成为一个参数。您只返回每个单独计算的结果,然后在主线程中创建dict。抛出comp_rel缺少1个必需的位置参数:“user_dict”,是否有方法像原始代码一样将用户dict馈送到那里?@Qubix,如果max_workers没有或没有提供,请引用docs中的话,它将默认为机器上的处理器数量。谢谢,但我有一个问题,comp_rel需要使用df、perm和user_dict参数。如何将其添加到executor.submit行?@Qubix,只需再添加一个位置参数executor.submitcomp\u rel,df,perm,user\u dict。@Qubix,future.result返回的值与comp\u rel完全相同。在示例中,函数返回2个值,这就是为什么我将其解包为k,v=future.result。如果你的函数有不同的返回值,你应该使用补丁代码
import concurrent.futures
from collections import defaultdict

def comp_rel(df, perm):
    ...
    return perm[0], perm[1]

user_dict = defaultdict(int)
with concurrent.futures.ProcessPoolExecutor() as executor:
    futures = {executor.submit(comp_rel, df, perm): perm for perm in user_perm}
    for future in concurrent.futures.as_completed(futures):
        try:
            k, v = future.result()
        except Exception as e:
            print(f"{futures[future]} throws {e}")
        else:
            user_dict[k] += v