Python 如何使用多线程加速嵌套for循环计算?

Python 如何使用多线程加速嵌套for循环计算?,python,multithreading,optimization,multiprocessing,numba,Python,Multithreading,Optimization,Multiprocessing,Numba,我试图在一个大的阵列上进行数值积分,计算需要很长时间。我试图通过使用numba和jit装饰器来加快代码的速度,但不支持numpy.trapz 我的新想法是创建n个线程来并行运行计算,但我想知道如何才能做到这一点,或者这是否可行 参考下面的代码 我可以让sz[2]多个线程同时运行,调用ZO_SteadState来计算值吗 for i in range(sz[1]): phii = phi[i] for j in range(sz[2]):

我试图在一个大的阵列上进行数值积分,计算需要很长时间。我试图通过使用numba和jit装饰器来加快代码的速度,但不支持numpy.trapz

我的新想法是创建n个线程来并行运行计算,但我想知道如何才能做到这一点,或者这是否可行

参考下面的代码

我可以让sz[2]多个线程同时运行,调用ZO_SteadState来计算值吗

    for i in range(sz[1]):
        phii = phi[i]
        for j in range(sz[2]):
            s = tau[0, i, j, :].reshape(1, n4)
            [R3, PHI3, S3] = meshgrid(rprime, phiprime, s)
            BCoeff = Bessel0(bm * R3)

            SS[0, i, j] = ZO_SteadyState(alpha, b,bm,BCoeff,Bessel_Denom, k2,maxt,phii, PHI2, PHI3, phiprime,R3,rprime,s,S3, T,v)
有问题的计算


这是我可能会做的总体想法。没有足够的上下文为您提供更可靠的示例。您必须将所有变量设置到类中

import multiprocessing

pool = multiprocessing.Pool(processes=12)
runner = mp_Z0(variable=variable, variable2=variable2)

for i, j, v in pool.imap(runner.run, range(sz[1]):
    SS[0, i, j] = v


class mp_Z0:

    def __init__(self, **kwargs):
        for k, v in kwargs:
            setattr(self, k, v)


    def run(self, i):
        phii = self.phi[i]
        for j in range(self.sz[2]):
            s = self.tau[0, i, j, :].reshape(1, self.n4)
            [R3, PHI3, S3] = meshgrid(self.rprime, self.phiprime, s)
            BCoeff = Bessel0(self.bm * R3)

            return (i, j, ZO_SteadyState(self.alpha, self.b, self.bm, BCoeff, Bessel_Denom, self.k2, self.maxt, phii, self.PHI2, PHI3, self.phiprime, R3, self.rprime, self.s, S3, self.T, self.v))
这是一个不使用类的示例(假设所有内容都在本地命名空间中):

import multiprocessing

pool = multiprocessing.Pool(processes=12)    
def runner_function(i):
        phii = phi[i]
        for j in range(sz[2]):
            s = tau[0, i, j, :].reshape(1, n4)
            [R3, PHI3, S3] = meshgrid(rprime, phiprime, s)
            BCoeff = Bessel0(bm * R3)

            return (i, j, ZO_SteadyState(alpha, b, bm, BCoeff, Bessel_Denom, k2, maxt, phii, PHI2, PHI3,
                                         phiprime, R3, rprime, s, S3, T, v))

for i, j, v in pool.imap(runner_function, range(sz[1]):
    SS[0, i, j] = v   

另一个概念实现,流程生成流程(编辑:jit测试):

编辑:解释

这段代码之所以复杂,是因为我想做比OP要求的更多的并行化。如果只有内环被并行化,那么外环将保持不变,因此对于外环的每次迭代,将创建新的进程池,并执行内环的计算。在我看来,只要公式不依赖于外循环的其他迭代,我就决定将所有内容并行化:现在外循环的计算被分配给池中的进程,然后每个“外循环”进程创建自己的新池,并生成额外的进程来执行内部循环的计算

虽然我可能错了,但外部循环不能并行化;在这种情况下,只能保留内部进程池

使用进程池可能不是最佳解决方案,因为创建和销毁进程池需要花费时间。更有效(但需要手工模式)的解决方案是一次性实例化N个进程,然后将数据输入其中,并使用多处理队列()接收结果。因此,您应该首先测试此多处理解决方案是否能为您提供足够的加速(如果与
Z0_SteadyState
run相比,构建和销毁池的时间很短,就会出现这种情况)

下一个复杂问题是人工的无守护进程池。守护进程用于优雅地停止应用程序:当主程序退出时,守护进程以静默方式终止。然而,守护进程不能产生子进程。在您的示例中,您需要等到每个进程结束后才能检索数据,所以我将它们设置为非守护进程,以允许生成子进程计算内部循环

数据交换:我假设填充矩阵所需的数据量和时间与实际计算相比是很小的。因此,我使用pool和
pool.imap
函数(这比
.map()
快一点。您也可以尝试
.imap\u unordered()
,但是在您的情况下,它不会产生显著的差异)。因此,内部池将等待所有结果被计算并作为列表返回。因此,外部池返回必须连接的列表列表。然后在单个快速循环中根据这些结果重构矩阵

注意
with closing()
thing:在完成此语句下的操作后,它会自动关闭池,避免僵尸进程占用内存

另外,您可能会注意到,我在另一个函数中奇怪地定义了一个函数,在进程中,我可以访问一些没有传递到那里的变量:
I
phii
。这是因为进程可以访问全局作用域,从全局作用域启动它们时使用
copy-on-change
策略(默认
fork
模式)。这不需要酸洗,而且速度很快

最后一条评论是关于使用
paths
库而不是标准的
多处理
并发.futures
子流程
等。原因是
paths
使用了更好的酸洗库,因此它可以序列化标准库无法序列化的函数(例如,lambda函数)。我不知道你的功能,所以我使用了更强大的工具来避免进一步的问题


最后一件事:多处理vs线程。您可以将
pathos
处理池从
concurrent.futures
更改为标准
ThreadPoolExecutor
,就像我刚开始编写代码时所做的那样。但是,在执行过程中,在我的系统上,CPU仅以100%的速度加载(即,使用一个内核,似乎所有8个内核都以15-20%的速度加载)。我不太擅长理解线程和进程之间的差异,但在我看来,进程允许使用所有内核(每个100%,总共800%)

是的,您可以使用
多处理.Pool
非常轻松地进行多处理。不过,您可能需要子进程,而不是线程。@CJ59我认为线程在这里很好,因为几乎所有的计算都是在
numpy
库中完成的,该库允许多个线程并行运行。线程意味着更少的数据洗牌,所以线程可能会更快。也许吧,但如果你从numpy的openmp部分获得最大的优势,那么线程将不会有多大帮助。如果您没有,那么线程将不会有帮助(但子进程会有帮助)。@CJ59为什么您在使用线程时不能获得OpenMP优势?据我所知,将它们组合在一起应该没有内在的问题。不使用类可以做到这一点吗?我能够使用Numba并使用njit decorator计算代码的某些部分,但是self语句将阻止我使用no python模式。是的。这是我写它最简单的方法。任何接受
i
并返回
(i,j,Z0_SteadyState())
的函数都将替换
runner。
import multiprocessing

pool = multiprocessing.Pool(processes=12)    
def runner_function(i):
        phii = phi[i]
        for j in range(sz[2]):
            s = tau[0, i, j, :].reshape(1, n4)
            [R3, PHI3, S3] = meshgrid(rprime, phiprime, s)
            BCoeff = Bessel0(bm * R3)

            return (i, j, ZO_SteadyState(alpha, b, bm, BCoeff, Bessel_Denom, k2, maxt, phii, PHI2, PHI3,
                                         phiprime, R3, rprime, s, S3, T, v))

for i, j, v in pool.imap(runner_function, range(sz[1]):
    SS[0, i, j] = v   
import numpy as np

# better pickling
import pathos 
from contextlib import closing


from numba import jit

#https://stackoverflow.com/questions/47574860/python-pathos-process-pool-non-daemonic
import multiprocess.context as context
class NoDaemonProcess(context.Process):
    def _get_daemon(self):
        return False
    def _set_daemon(self, value):
        pass
    daemon = property(_get_daemon, _set_daemon)

class NoDaemonPool(pathos.multiprocessing.Pool):
    def Process(self, *args, **kwds):
        return NoDaemonProcess(*args, **kwds)




# matrix dimensions
x = 100 # i
y = 500 # j

NUM_PROCESSES = 10 # total NUM_PROCESSES*NUM_PROCESSES will be spawned

SS = np.zeros([x, y], dtype=float)

@jit
def foo(i):
    return (i*i + 1)
@jit
def bar(phii, j):
    return phii*(j+1)

# The code which is implemented down here:
'''
for i in range(x):
    phii = foo(i)
    for j in range(y):
        SS[i, j] = bar(phii, j)
'''

# Threaded version:
# queue is in global scope


def outer_loop(i):

    phii = foo(i)

    # i is in process scope
    def inner_loop(j):
        result = bar(phii,j)
        # the data is coordinates and result
        return (i, j, result)


    with closing(NoDaemonPool(processes=NUM_PROCESSES)) as pool:
        res = list(pool.imap(inner_loop, range(y)))
    return res

with closing(NoDaemonPool(processes=NUM_PROCESSES)) as pool:    
    results = list(pool.imap(outer_loop, range(x)))

result_list = []
for r in results:
    result_list += r


# read results from queue
for res in result_list:
    if res:
        i, j, val = res
        SS[i,j] = val


# check that all cells filled
print(np.count_nonzero(SS)) # 100*500