首页 > 解决方案 > Python for 循环:正确实现多处理

问题描述

以下 for 循环是迭代模拟过程的一部分,是计算时间的主要瓶颈:

import numpy as np

class Simulation(object):

    def __init__(self,n_int):
        self.n_int = n_int

    def loop(self):

        for itr in range(self.n_int):        
            #some preceeding code which updates rows_list and diff with every itr
            cols_red_list = []
            rows_list = list(range(2500)) #row idx for diff where negative element is known to appear
            diff = np.random.uniform(-1.323, 3.780, (2500, 300)) #np.random.uniform is just used as toy example 

            for row in rows_list:
                col =  next(idx for idx, val in enumerate(diff[row,:]) if val < 0)
                cols_red_list.append(col)
            # some subsequent code which uses the cols_red_list data    
sim1 = Simulation(n_int=10)
sim1.loop()

因此,我尝试使用multiprocessing包将其并行化,以减少计算时间:

import numpy as np
from multiprocessing import  Pool, cpu_count
from functools import partial

def crossings(row, diff):
    return next(idx for idx, val in enumerate(diff[row,:]) if val < 0)

class Simulation(object): 
    def __init__(self,n_int):
        self.n_int = n_int

    def loop(self):        
        for itr in range(self.n_int): 
            #some preceeding code which updates rows_list and diff with every
            rows_list = list(range(2500))
            diff = np.random.uniform(-1, 1, (2500, 300))

            if __name__ == '__main__':
                num_of_workers = cpu_count()
                print('number of CPUs : ', num_of_workers)
                pool = Pool(num_of_workers)
                cols_red_list = pool.map(partial(crossings,diff = diff), rows_list)
                pool.close()
                print(len(cols_red_list))
            # some subsequent code which uses the cols_red_list data 

sim1 = Simulation(n_int=10)
sim1.loop()

不幸的是,与顺序代码相比,并行化要慢得多。因此我的问题是:在那个特定的例子中,我是否正确使用了 multiprocessing 包?是否有其他方法可以并行化上述 for 循环?

标签: pythonmultithreadingloopsmultiprocessing

解决方案


免责声明:当您试图通过并行化减少代码的运行时间时,这并不能严格回答您的问题,但它可能仍然是一个很好的学习机会。

作为黄金法则,在转向多处理以提高性能(执行时间)之前,应该首先优化单线程情况。

您的

rows_list = list(range(2500))

将数字生成02499(即range)并将它们存储在内存中(list),这需要时间来分配所需的内存和实际写入。然后,您只需按可预测的顺序从内存中读取它们(这也需要时间),每次只使用一次这些可预测的值:

for row in rows_list:

loop当您重复执行此操作时,这与您的函数的运行时特别相关( for itr in range(n_int):)。

相反,请考虑仅在需要时生成数字,而不需要中间存储(这在概念上消除了访问 RAM 的任何需要):

for row in range(2500):

其次,除了共享相同的问题(对内存的不必要访问)之外,还有以下内容:

diff = np.random.uniform(-1, 1, (2500, 300))
# ...
    col =  next(idx for idx, val in enumerate(diff[row,:]) if val < 0)

在我看来,在数学(或逻辑)层面上是可以优化的。

您要做的是通过将随机变量(该col索引)定义为“我第一次在 [-1;1] 中遇到小于 0 的随机变量”来获取随机变量(该索引)。但请注意,确定在 [-α;α] 上具有均匀分布的随机变量是否为负,与在 {0,1} 上具有随机变量(a bool)相同。

因此,您现在使用bool的是 s 而不是floats,而且您甚至不必进行比较 ( val < 0),因为您已经有了一个布尔值。这可能会使代码更快。使用与 for 相同的想法rows_list,您可以bool仅在需要时生成它;测试它直到它是True(或者False,选择一个,这显然无关紧要)。通过这样做,您只生成bool所需数量的随机 s,而不是更多也不是更少(顺便说一句,如果行中的所有 300 个元素都是负数,您的代码中会发生什么?;)):

for _ in range(n_int):
    cols_red_list = []
    for row in range(2500):
        col = next(i for i in itertools.count() if random.getrandbits(1))
        cols_red_list.append(col)

或者,使用列表理解:

cols_red_list = [next(i for i in count() if getrandbits(1))
                 for _ in range(2500)]

我敢肯定,通过适当的统计分析,您甚至可以将该col随机变量表示为 [0; limit[,让你计算得更快。

请首先测试单线程实现的“优化”版本的性能。如果运行时仍然不可接受,那么您应该研究多线程。


推荐阅读