AWS EC2实例使用Python multiprocessing多进程性能不达预期排查
Python多进程数值计算性能不达预期的排查与优化
核心问题定位
核心数提升但性能不升反降、CPU占用满但实际吞吐量低,是Python多进程跑计算密集型任务的典型现象,主要由代码bug、硬件特性认知偏差、跨进程开销过高三类原因导致。
第一步 先修复显式代码错误
代码里有两个直接影响性能的低级问题:
- 进程池初始化硬编码使用
mp.cpu_count()作为进程数,完全没有使用传入的ncores参数,之前测试不同进程数的操作根本没有生效。把对应代码改成:
# 错误写法 # with Pool(processes=mp.cpu_count()) as pool: # 修正写法 with Pool(processes=ncores) as pool:
- 在
with上下文管理器内调用pool.terminate()和pool.close()是多余操作,with块退出时会自动完成进程池的资源回收,手动调用反而可能触发资源竞争,直接删掉这两行即可。
第二步 纠正进程数配置逻辑
测试出53进程性能最优、192进程性能下降是符合硬件规律的:
- 云服务商标注的vCPU数是超线程后的逻辑核心数,不是物理计算核心。计算密集型任务(尤其是矩阵乘法这类吃浮点运算单元、吃CPU缓存的数值计算),同一个物理核心对应的两个超线程逻辑核心共享计算资源,开超线程跑只会增加缓存争抢、上下文切换开销,不会带来性能提升。192 vCPU的实例物理核心数通常在48-64区间,和测出的53进程最优值完全匹配。
- 虽然提前设置了BLAS库的单线程环境变量、位置正确,但建议跑任务时通过进程监控确认每个Python子进程的CPU占用不超过100%,如果单个进程占满多个核,说明numpy链接的底层线性代数库没有读取到环境变量,出现了多进程套多线程的资源抢占问题,会严重拉低性能。可以通过
np.__config__.show()打印numpy的BLAS配置,确认线程限制生效。
第三步 降低跨进程通信开销
所有CPU核心都被占满但实际计算吞吐量低,核心原因是大量CPU周期没有花在数值计算上,而是耗在了跨进程通信的序列化、内存拷贝上:
- 标准库
multiprocessing.Pool默认用pickle做数据序列化,跨进程返回numpy数组时,会把数组完整拷贝一遍,任务量越大、返回数组越大,这部分开销占比越高,进程数开得越多,拷贝和调度的开销增长越快,最终会完全吃掉多进程带来的性能收益。 - 对应的优化手段:
- 给
pool.map/pool.imap显式设置chunksize参数,默认chunksize对于百万级小任务来说过小,调度频率太高,建议设置为nsamples // (ncores * 4),大幅减少进程调度次数。 - 任务传参不要提前生成长度为百万级的参数列表,用
itertools.repeat生成轻量迭代器传递重复参数,避免不必要的内存占用和序列化开销:import itertools result = pool.map( sample_in_pool, itertools.repeat((noise_pepso, rows, cols), nsamples), chunksize=nsamples // (ncores * 4) ) - 如果返回的numpy数组体积较大,建议用
multiprocessing.shared_memory开辟共享内存段存储结果,所有子进程直接读写共享内存,完全避免跨进程的数组拷贝;如果改造成本高,可以替换多进程后端为loky,它对numpy数组做了序列化优化,比标准库Pickle的传输效率高3-10倍。
- 给
预期优化效果
按上述步骤调整后,在192vCPU(约48-64物理核)的实例上,吞吐量应该能达到28核物理机的1.7-2.2倍左右,和物理核心数的缩放比例基本匹配,不会再出现核心数上涨但性能不涨的问题。
内容的提问来源于stack exchange,提问作者Ron Cohen
相关产品推荐
相关产品推荐

