使用multiprocessing.Pool实现Numpy矩阵乘法,维度超50后性能骤降的优化咨询
优化multiprocessing.Pool矩阵乘法性能的方案
你的问题很典型:理论上总计算量一致,但随着d增大并行时间反而变长,核心原因是任务粒度太小+进程调度/创建开销占比过高,再加上numpy内部多线程与multiprocessing的资源冲突。下面是几个不需要共享内存的简便优化方法,完全适配你的随机矩阵生成场景:
关键问题分析
- 进程创建销毁开销:你原来的代码对每个
d都新建一个Pool,进程的启动/销毁在d较大时(单个任务执行时间极短)会成为主要开销。 - 任务粒度太小:当
d增大,单个f(d)的执行时间急剧缩短,但你仍提交20个独立任务,进程间的任务调度开销占比会越来越高。 - numpy多线程冲突:你的CPU是28线程的i9-7940X,numpy默认会用OpenBLAS/MKL的多线程加速矩阵乘法。当你用multiprocessing启动多个进程时,每个进程的numpy又会抢占CPU线程,导致CPU过载、上下文切换剧增,这是
d>50时性能骤降的核心原因之一。
优化方案与代码修改
1. 禁用numpy内部多线程,避免资源竞争
在代码开头添加环境变量设置,让每个进程的numpy仅使用单线程,避免与multiprocessing的进程并行冲突:
import os os.environ["OMP_NUM_THREADS"] = "1" # 适配OpenBLAS/MKL的多线程控制
2. 复用Pool,减少进程创建开销
不要为每个d新建Pool,而是全局创建一次Pool,让进程在所有d的计算中复用。
3. 优化任务提交方式,减少调度开销
用pool.map批量提交任务(代替多次apply_async),或者直接合并任务粒度,把20次f(d)调用打包成一个任务,进一步降低调度成本。
修改后的完整代码
import numpy as np from time import time from multiprocessing import Pool from functools import partial # 关键:禁用numpy内部多线程,避免与multiprocessing冲突 import os os.environ["OMP_NUM_THREADS"] = "1" def f(d): a = int(10*d) N = int(10000/d) for _ in range(N): X = np.random.randn(a,10) @ np.random.randn(10,10) return X # 可选:合并任务粒度,把20次调用打包成一个任务,进一步减少调度开销 def f_batch(d, num_runs=20): for _ in range(num_runs): a = int(10*d) N = int(10000/d) for __ in range(N): X = np.random.randn(a,10) @ np.random.randn(10,10) return X # Dimensions ds = [1,2,3,4,5,6,8,10,20,35,40,45,50,60,62,64,66,68,70,80,90,100] # Serial processing serial = [] for d in ds: t1 = time() for i in range(20): f(d) serial.append(time()-t1) # Parallel processing - 优化版本 parallel = [] # 全局复用一个Pool,避免重复创建进程 with Pool() as pool: for d in ds: t1 = time() # 方案A:用map批量提交20个任务 # pool.map(partial(f, d), range(20)) # 方案B:用合并后的任务,仅提交1个任务(推荐d较大时用) pool.apply(f_batch, args=(d,)) parallel.append(time()-t1) # Plot import matplotlib.pyplot as plt plt.title('Matrix multiplication time with 10000/d repetitions') plt.plot(ds,serial,label='serial') plt.plot(ds,parallel,label='parallel') plt.xlabel('d (dimension)') plt.ylabel('Total time (sec)') plt.legend() plt.show()
效果说明
- 禁用numpy多线程后,CPU的利用率会更平稳,不会出现过载导致的上下文切换开销。
- 复用Pool和合并任务粒度后,
d较大时的任务调度/进程创建开销会大幅降低,并行时间会更接近理论上的恒定值。 - 所有优化都不需要共享内存,完全保留你随机生成矩阵的逻辑,没有数据传输的额外开销。
内容的提问来源于stack exchange,提问作者Seung Hyeon Yu
相关产品推荐
相关产品推荐

