在for循环中,我调用一个函数两次,但使用不同的参数集(argSet1
,argSet2
),它们在for循环的每次迭代中都会发生变化。我想并行化这个操作,因为一组参数导致被调用函数运行得更快,而另一组参数导致函数运行缓慢。请注意,我不希望为此操作设置两个for循环。我还有另一个要求:这些函数中的每一个都将执行一些并行操作,因此我不希望任何argSet1
或argSet2
的函数运行多次,因为计算我有限的资源。确保两个参数集的函数都在运行,这将有助于我尽可能地利用CPU核心。以下是没有并行化的常规方法:
def myFunc(arg1, arg2):
if arg1:
print ('do something that does not take too long')
else:
print ('do something that takes long')
for i in range(10):
argSet1 = arg1Storage[i]
argSet1 = arg2Storage[i]
myFunc(argSet1)
myFunc(argSet2)
这绝对不会占用我拥有的计算资源。这是我尝试并行化操作:
from multiprocessing import Process
def myFunc(arg1, arg2):
if arg1:
print ('do something that does not take too long')
else:
print ('do something that takes long')
for i in range(10):
argSet1 = arg1Storage[i]
argSet1 = arg2Storage[i]
p1 = Process(target=myFunc, args=argSet1)
p1.start()
p2 = Process(target=myFunc, args=argSet2)
p2.start()
然而,这样每个函数及其各自的参数将被调用10次,并且事情变得极其缓慢。鉴于我对多处理的了解有限,我尝试通过在for循环的末尾添加p1.join()
和p2.join()
来进一步改进,但这仍然会导致p1
做得更慢更快,事情等到p2
完成。我还考虑过使用multiprocessing.Value
与函数进行一些通信,但是我必须在函数内部为每个函数调用添加一个while
循环,这会再次降低所有内容的速度。我想知道是否有人可以提供实用的解决方案?
答案 0 :(得分:2)
您可能希望使用multiprocessing.Pool
个流程并将myFunc
映射到其中,如下所示:
from multiprocessing import Pool
import time
def myFunc(arg1, arg2):
if arg1:
print ('do something that does not take too long')
time.sleep(0.01)
else:
print ('do something that takes long')
time.sleep(1)
def wrap(args):
return myFunc(*args)
if __name__ == "__main__":
p = Pool()
argStorage = [(True, False), (False, True)] * 12
p.map(wrap, argStorage)
我添加了wrap
函数,因为传递给p.map
的函数必须接受单个参数。你也可以调整myFunc
来接受一个元组,如果你的情况可以的话。
我的样本appStorage
由24个项组成,其中12个将需要1秒来处理,12个将在10毫秒内完成。总的来说,这个脚本运行3-4秒(我有4个核心)。
答案 1 :(得分:1)
由于我在补丁中构建了这个答案,请向下滚动以找到解决此问题的最佳方法
您需要指定完全您希望如何运行。据我所知,您希望最多运行两个进程,但至少也是如此。此外,你不希望沉重的呼叫阻止快速呼叫。一种简单的非最佳运行方式是:
from multiprocessing import Process
def func(counter,somearg):
j = 0
for i in range(counter): j+=i
print(somearg)
def loop(counter,arglist):
for i in range(10):
func(counter,arglist[i])
heavy = Process(target=loop,args=[1000000,['heavy'+str(i) for i in range(10)]])
light = Process(target=loop,args=[500000,['light'+str(i) for i in range(10)]])
heavy.start()
light.start()
heavy.join()
light.join()
这里的输出是(例如运行):
light0
heavy0
light1
light2
heavy1
light3
light4
heavy2
light5
light6
heavy3
light7
light8
heavy4
light9
heavy5
heavy6
heavy7
heavy8
heavy9
你可以看到最后一部分是次优的,因为你有一系列繁重的运行 - 这意味着有一个进程而不是两个进程。
如果您可以估算重型流程运行的时间长度,则可以轻松优化此方法。如果它的速度是这里的两倍,那么只需先运行7次重复迭代,加入光照过程,并运行额外的3次。
另一种方法是成对运行繁重的流程,所以首先你有3个流程,直到快速流程结束,然后继续2。
主要观点将重型和轻型呼叫完全分离到另一个进程 - 因此,当快速呼叫一个接一个地快速完成时,您可以使用缓慢的工作。一旦快速结束,由你决定你想要继续多么复杂,但我认为现在估计如何打破沉重的呼叫是足够好的。这是我的例子:
from multiprocessing import Process
def func(counter,somearg):
j = 0
for i in range(counter): j+=i
print(somearg)
def loop(counter,amount,arglist):
for i in range(amount):
func(counter,arglist[i])
heavy1 = Process(target=loop,args=[1000000,7,['heavy1'+str(i) for i in range(7)]])
light = Process(target=loop,args=[500000,10,['light'+str(i) for i in range(10)]])
heavy2 = Process(target=loop,args=[1000000,3,['heavy2'+str(i) for i in range(7,10)]])
heavy1.start()
light.start()
light.join()
heavy2.start()
heavy1.join()
heavy2.join()
带输出:
light0
heavy10
light1
light2
heavy11
light3
light4
heavy12
light5
light6
heavy13
light7
light8
heavy14
light9
heavy15
heavy27
heavy16
heavy28
heavy29
更好的利用率。您当然可以通过为慢速进程运行共享队列来使其更先进,因此当快速完成时,它们可以作为缓冲队列上的工作者加入,但是对于仅两个不同的调用,这可能是过度的(尽管使用起来并不困难) queue)。 最佳解决方案:
from multiprocessing import Queue,Process
import queue
def func(index,counter,somearg):
j = 0
for i in range(counter): j+=i
print("Worker",index,':',somearg)
def worker(index):
try:
while True:
func,args = q.get(block=False)
func(index,*args)
except queue.Empty: pass
q = Queue()
for i in range(10):
q.put((func,(500000,'light'+str(i))))
q.put((func,(1000000,'heavy'+str(i))))
nworkers = 2
workers = []
for i in range(nworkers):
workers.append(Process(target=worker,args=(i,)))
workers[-1].start()
q.close()
for worker in workers:
worker.join()
这是您想要的最佳,最具扩展性的解决方案。输出:
Worker 0 : light0
Worker 0 : light1
Worker 1 : heavy0
Worker 1 : light2
Worker 0 : heavy1
Worker 0 : light3
Worker 1 : heavy2
Worker 1 : light4
Worker 0 : heavy3
Worker 0 : light5
Worker 1 : heavy4
Worker 1 : light6
Worker 0 : heavy5
Worker 0 : light7
Worker 1 : heavy6
Worker 1 : light8
Worker 0 : heavy7
Worker 0 : light9
Worker 1 : heavy8
Worker 0 : heavy9
答案 2 :(得分:0)
一种可能的实施方式如下:
import concurrent.futures
import math
list_of_args = [arg1, arg2]
def my_func(arg):
....
print ('do something that takes long')
def main():
with concurrent.futures.ProcessPoolExecutor() as executor:
for arg, result in zip(list_of_args, executor.map(is_prime, list_of_args)):
print('my_func({0}) => {1}'.format(arg, result))
executor.map
与内置函数类似,map方法允许多次调用提供的函数,将迭代中的每个项传递给该函数。