多进程vs迭代实现性能疑问:为何迭代方式更快?
问题分析与解决方案
你的判断完全正确——进程创建/销毁的开销远超过了多进程并行带来的收益,因为单个count_bugs_needed任务的计算量不足以抵消进程启动、IPC(进程间通信)的成本,最终导致多进程版本反而更慢。
具体原因拆解
- 进程本身的开销:创建子进程需要操作系统复制父进程的内存空间(即使有写时复制优化,仍有初始化、调度、销毁的固定开销),这些开销对于轻量级计算任务来说占比极高。
- 任务特性:
count_bugs_needed里的循环虽然有41600次迭代,但每次只是简单的随机数生成和整数运算,属于超轻量计算,单线程就能在极短时间内完成,进程开销完全盖过了多核并行的收益。 - 代码额外放大开销:当前多进程逻辑中,每次批量创建24个进程后都要销毁重建,还反复创建新的
Queue,进一步增加了不必要的系统调用开销;同时用Queue.get()阻塞等待结果的方式,也会带来额外的通信延迟。
改进方案
1. 用进程池复用进程
使用multiprocessing.Pool代替手动创建Process,进程池会预先创建一批进程并复用,彻底避免反复创建销毁进程的开销。
2. 分摊进程开销
让每个子进程处理多次任务(而非单次),将进程启动的固定开销分摊到更多计算上,提升并行效率。
3. 简化IPC逻辑
进程池的map/imap方法比手动维护Queue更高效,减少进程间通信的复杂度和延迟。
修改后的代码示例
import collections import math import random from time import perf_counter from multiprocessing import Pool import matplotlib.pyplot as plt MULTIPROCESSING = True MAX_PROCESSES = 24 TRIALS = 10000 def count_bugs_needed(_): fragments = 0 bugs_killed = 0 drop_chance = .77 while fragments < 41600: bugs_killed += 1 if random.randint(1,100) >= int((1-drop_chance) * 100): fragments += 1 return bugs_killed if __name__ == '__main__': start_time = perf_counter() bug_count = [] if not MULTIPROCESSING: for x in range(TRIALS): bug_count.append(count_bugs_needed(1)) if (x+1) % MAX_PROCESSES == 0: speed = (x+1) / (perf_counter() - start_time) print(f"Iterations done: {x+1}, Speed: {speed:.2f} ITERATIONS PER SECOND") else: # 进程池复用进程,避免反复创建销毁的开销 with Pool(processes=MAX_PROCESSES) as pool: # 流式获取结果,无需等待全部任务完成 for idx, result in enumerate(pool.imap_unordered(count_bugs_needed, range(TRIALS)), 1): bug_count.append(result) if idx % MAX_PROCESSES == 0: speed = idx / (perf_counter() - start_time) print(f"Iterations done: {idx}, Speed: {speed:.2f} ITERATIONS PER SECOND") print("Done generating values. Starting to graph.") bug_count_sorted = sorted(bug_count) counter = collections.Counter(bug_count_sorted) sorted_counter = sorted(counter.items()) unique_bug_counts = [item[0] for item in sorted_counter] frequencies = [item[1] for item in sorted_counter] plt.scatter(unique_bug_counts, frequencies) max_freq = max(frequencies) plt.ylim(0, int(max_freq * 1.2 + 1)) plt.yticks(range(0, int(max_freq * 1.2 + 1) + 1)) plt.title('Frequency of bug kills for 41600 fragments') plt.xlabel('Bugs killed') plt.ylabel('Frequency') plt.show()
改进效果预期
修改后,多进程版本的速度会显著超过单线程迭代版本:进程池的复用消除了大部分进程开销,多核CPU的并行计算能力得以发挥,同时流式获取结果的方式也避免了不必要的等待。
内容的提问来源于stack exchange,提问作者Dont worry about it
相关产品推荐
相关产品推荐

