如何为Python multiprocessing.Pool添加不拖慢速度的进度条(TQDM慢7倍)
解决multiprocessing.Pool配合tqdm进度条的性能问题
你的问题核心在于pool.imap是逐个返回任务结果,每个结果都要触发一次进程间通信,而pool.map是批量返回结果,进程间通信的总开销要小得多,这才导致了7倍左右的性能差距。下面提供两种不牺牲速度的进度条实现方案:
方案一:用pool.map配合回调函数更新进度条
利用pool.map的高效批量处理,同时通过回调函数实时更新tqdm进度条,既保留map的性能,又能跟踪进度。
代码示例:
import numpy as np import time from multiprocessing import Pool, cpu_count from functools import partial from tqdm import tqdm def dummy_task(step_id, size=500): data = np.random.randn(size, 3) dist = np.linalg.norm(data, axis=1) return step_id, np.min(dist) def update_progress(_): # 每完成一个任务,进度条前进1 pbar.update(1) if __name__ == "__main__": steps = list(range(500000)) size = 500 print("Running with map + tqdm callback...") t0 = time.time() with Pool(processes=cpu_count()) as pool: # 初始化进度条 pbar = tqdm(total=len(steps)) # 用map配合回调函数 results = pool.map(partial(dummy_task, size=size), steps, callback=update_progress) pbar.close() t1 = time.time() print(f"Time taken: {t1 - t0:.3f} seconds")
这个方案的速度和原生pool.map几乎一致,因为本质还是用map处理任务,回调函数只是在主进程做了极轻量的进度更新操作。
方案二:任务分块后用imap处理
如果需要保留imap的迭代特性(比如提前处理部分结果),可以将任务列表分成若干大的块,减少进程间通信的次数,从而接近map的性能。
代码示例:
import numpy as np import time from multiprocessing import Pool, cpu_count from functools import partial from tqdm import tqdm def dummy_task(step_id, size=500): data = np.random.randn(size, 3) dist = np.linalg.norm(data, axis=1) return step_id, np.min(dist) # 定义分块函数 def chunk_tasks(tasks, chunk_size): for i in range(0, len(tasks), chunk_size): yield tasks[i:i+chunk_size] # 批量处理块内任务的函数 def process_chunk(chunk, size): return [dummy_task(step_id, size) for step_id in chunk] if __name__ == "__main__": steps = list(range(500000)) size = 500 chunk_size = 1000 # 每块包含1000个任务,可根据实际调整 print("Running with chunked imap + tqdm...") t0 = time.time() with Pool(processes=cpu_count()) as pool: # 分块后用imap处理,tqdm按块更新进度 results_iter = pool.imap(partial(process_chunk, size=size), chunk_tasks(steps, chunk_size)) # 展开所有结果 results = [] for chunk_result in tqdm(results_iter, total=len(steps)//chunk_size + 1): results.extend(chunk_result) t1 = time.time() print(f"Time taken: {t1 - t0:.3f} seconds")
这里通过把500000个任务分成500块(每块1000个),进程间通信的次数从500000降到500,性能会大幅提升,接近原生map的速度,同时也能通过tqdm跟踪进度。
补充说明
- 方案一的性能最优,完全继承
map的效率,适合不需要提前处理部分结果的场景; - 方案二适合需要迭代处理结果的场景,调整
chunk_size可以平衡进度更新的粒度和性能,块越大,性能越接近map,进度更新的频率越低。
内容的提问来源于stack exchange,提问作者brownser
相关产品推荐
相关产品推荐

