Python 对面向队列的函数使用多处理后没有性能提升
我想要优化的实际代码太复杂,无法包含在这里,因此这里有一个简化的示例:Python 对面向队列的函数使用多处理后没有性能提升,python,performance,optimization,queue,multiprocessing,Python,Performance,Optimization,Queue,Multiprocessing,我想要优化的实际代码太复杂,无法包含在这里,因此这里有一个简化的示例: def enumerate_paths(n, k): """ John want to go up a flight of stairs that has N steps. He can take up to K steps each time. This function enumerate all different ways he can go up this flight of sta
def enumerate_paths(n, k):
"""
John want to go up a flight of stairs that has N steps. He can take
up to K steps each time. This function enumerate all different ways
he can go up this flight of stairs.
"""
paths = []
to_analyze = [(0,)]
while to_analyze:
path = to_analyze.pop()
last_step = path[-1]
if last_step >= n:
# John has reach the top
paths.append(path)
continue
for i in range(1, k + 1):
# possible paths from this point
extended_path = path + (last_step + i,)
to_analyze.append(extended_path)
return paths
输出结果如下所示
>>> enumerate_paths(3, 2)
[(0, 2, 4), (0, 2, 3), (0, 1, 3), (0, 1, 2, 4), (0, 1, 2, 3)]
您可能会发现结果令人困惑,因此这里有一个解释。例如,(0,1,2,4)
意味着John可以按时间顺序将脚放在第一、第二和第四步上,最后他在第四步停止,因为他只需要上3步
我试图将多处理
合并到这段代码中,但没有观察到性能提升,甚至一点也没有
import multiprocessing
def enumerate_paths_worker(n, k, queue):
paths = []
while not queue.empty():
path = queue.get()
last_step = path[-1]
if last_step >= n:
# John has reach the top
paths.append(path)
continue
for i in range(1, k + 1):
# possible paths from this point
extended_path = path + (last_step + i,)
queue.put(extended_path)
return paths
def enumerate_paths(n, k):
pool = multiprocessing.Pool()
manager = multiprocessing.Manager()
queue = manager.Queue()
path_init = (0,)
queue.put(path_init)
apply_result = pool.apply_async(enumerate_paths_worker, (n, k, queue))
return apply_result.get()
Python列表to_analysis
就像一个任务队列,队列中的每个项目都可以单独处理,因此我认为这个函数有可能通过使用多线程/处理来优化。另外,请注意,物品的顺序并不重要。事实上,在对其进行优化时,可以返回Python集、Numpy数组或Pandas数据帧,只要它们表示相同的路径集
奖金问题:对于这样的任务,使用Numpy、Pandas或Scipy等科学软件包可以获得多少性能?TL;DR
如果您的实际算法不涉及比您在示例中向我们展示的更昂贵的计算,则多处理的通信开销将占主导地位,并使您的计算时间比顺序执行长很多倍
使用
apply\u async
的尝试实际上只使用了池中的一个工作线程,这就是为什么您看不出有什么不同apply_async
只是通过设计一次为一个工人提供服务。此外,如果您的工作人员需要共享中间结果,那么仅仅将串行版本传递到池中是不够的,因此您必须修改您的目标函数以启用它
但是正如在介绍中已经说过的,如果计算量足够大,足以收回进程间通信(和进程创建)的开销,那么您的计算只会从多处理中受益
我下面针对一般问题的解决方案使用JoinableQueue
结合进程终止的sentinel值来同步工作流。我添加了一个函数busy\u foo
,使计算更重,以显示多处理的好处
from multiprocessing import Process
from multiprocessing import JoinableQueue as Queue
import time
SENTINEL = 'SENTINEL'
def busy_foo(x = 10e6):
for _ in range(int(x)):
x -= 1
def enumerate_paths(q_analyze, q_result, n, k):
"""
John want to go up a flight of stairs that has N steps. He can take
up to K steps each time. This function enumerate all different ways
he can go up this flight of stairs.
"""
for path in iter(q_analyze.get, SENTINEL):
last_step = path[-1]
if last_step >= n:
busy_foo()
# John has reach the top
q_result.put(path)
q_analyze.task_done()
continue
else:
busy_foo()
for i in range(1, k + 1):
# possible paths from this point
extended_path = path + (last_step + i,)
q_analyze.put(extended_path)
q_analyze.task_done()
if __name__ == '__main__':
N_CORES = 4
N = 6
K = 2
start = time.perf_counter()
q_analyze = Queue()
q_result = Queue()
q_analyze.put((0,))
pool = []
for _ in range(N_CORES):
pool.append(
Process(target=enumerate_paths, args=(q_analyze, q_result, N, K))
)
for p in pool:
p.start()
q_analyze.join() # block until everything is processed
for p in pool:
q_analyze.put(SENTINEL) # let the processes exit gracefully
results = []
while not q_result.empty():
results.append(q_result.get())
for p in pool:
p.join()
print(f'elapsed: {time.perf_counter() - start: .2f} s')
结果
如果我在注释掉上面的代码时使用了busy\u foo
,则需要N=30,K=2(2178309个结果):
- ~208sN_核心=4
- 2.78s连续原稿
busy\u foo
和N=6,K=2(21个结果),则需要:
- 6.45sN_芯=4
- 30.46s连续原始
Numpy可以多次加速矢量化操作,但您可能会在这一次看到Numpy的性能损失。Numpy使用连续的内存块作为它的数组。当您更改数组大小时,整个数组将不得不重新构建,这与使用python列表不同。“但我认为它们只能处理彼此完全独立的任务”事实并非如此,例如,进程可以在列表上协同工作。对于你的奖金问题,如果不实施它,就不可能知道;可能是相同的速度,可能是1000倍faster@roganjosh我明白你的意思,但我的意思是,你不能用
map
,它基本上只是在不同的参数上同时应用函数。我实际上很难理解函数的作用。它给了我一个类似于(0,1,2,6)
的枚举路径(3,4)
的答案。这到底是什么意思?你不能在最后采取4个步骤,因为只能再采取1个步骤。您还没有实现一个上界来考虑实际可以采取多少步骤?但是,是的,现在我已经在玩代码了,我不确定它是否适合当前形式的多处理,除非您能够找到一种方法来划分这些步骤task@roganjosh我已经更新了我的帖子,加入了一个解释。基本上,John被允许“过度”,因为我认为这将使示例更加简洁(并且更容易实现)。