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