0

0

加速Python中NumPy密集型计算的多进程优化策略

聖光之護

聖光之護

发布时间:2025-09-21 11:52:17

|

994人浏览过

|

来源于php中文网

原创

加速Python中NumPy密集型计算的多进程优化策略

本文探讨了在Python中对NumPy密集型计算进行多进程加速时遇到的常见性能瓶颈。通过分析数据序列化和复制的开销,我们揭示了为何传统的process_map可能适得其反。文章提供了一种基于multiprocessing.Manager共享内存的优化方案,有效避免了重复数据复制,从而显著提升了计算效率,并给出了详细的实现代码和最佳实践。

理解Python多进程/多线程加速的挑战

python中处理大量计算密集型任务时,利用多核cpu进行并行计算是提高效率的常见方法。对于cpu密集型任务,由于python的全局解释器锁(gil)限制,多线程通常无法实现真正的并行计算,而多进程(multiprocessing)则通过创建独立的python解释器进程来绕过gil,从而实现并行执行。

然而,在使用multiprocessing库或其高级封装(如tqdm.contrib.concurrent.process_map)时,开发者有时会发现性能不升反降,尤其是在处理大型数据结构(如NumPy数组)时。这背后的主要原因在于进程间通信(IPC)的开销,特别是数据序列化和反序列化(即所谓的“pickling”和“unpickling”)过程。

当我们将一个Python对象作为参数传递给一个新创建的子进程时,该对象不会直接在进程间共享内存。相反,它会被序列化(pickled),然后复制到子进程的内存空间中,子进程再对其进行反序列化(unpickled)。对于小型数据,这个开销可以忽略不计,但对于像numpy.ndarray这样的大型数据结构,每次任务调用都进行这种复制操作会消耗大量CPU时间和内存带宽,最终成为整个并行计算的瓶颈。

考虑以下一个模拟NumPy密集型计算的例子,它展示了process_map在处理大型数组时的效率问题:

import time
import numpy as np
from tqdm.auto import tqdm
from tqdm.contrib.concurrent import process_map, thread_map

# 模拟生成大型数据集
def mydataset(size, length):
    for ii in range(length):
        yield np.random.rand(*size)

# 模拟耗时计算函数
def calc(mat):
    # 模拟一些耗时的NumPy计算
    for ii in range(1000):
        avg = np.mean(mat)
        std = np.std(mat)
    return avg, std

def main_original_test():
    ds = list(mydataset((500, 500), 100)) # 100个500x500的NumPy数组

    print("--- 原始测试结果 ---")
    t0 = time.time()
    res1 = []
    for mat in tqdm(ds):
        res1.append(calc(mat))
    print(f'for loop: {time.time() - t0:.2f}s')

    t0 = time.time()
    res2 = list(map(calc, tqdm(ds)))
    print(f'native map: {time.time() - t0:.2f}s')

    t0 = time.time()
    res3 = process_map(calc, ds) # 使用process_map
    print(f'process map: {time.time() - t0:.2f}s')

    t0 = time.time()
    res4 = thread_map(calc, ds) # 使用thread_map
    print(f'thread map: {time.time() - t0:.2f}s')

if __name__ == '__main__':
    main_original_test()

上述代码在某些环境下可能产生如下结果:

立即学习Python免费学习笔记(深入)”;

for loop: 51.88s
native map: 52.49s
process map: 71.06s  # 明显慢于for循环
thread map: 42.04s   # 略快,但未充分利用多核

可以看到,process_map的执行时间甚至超过了简单的for循环,这正是由于每次调用calc函数时,整个NumPy数组mat都需要被序列化并复制到子进程,导致了巨大的性能开销。thread_map虽然略快,但由于GIL的存在,其加速效果有限。

优化策略:利用共享内存避免数据复制

解决上述问题的关键在于避免在每次任务调用时重复复制大型数据。Python的multiprocessing模块提供了一种解决方案:Manager。Manager对象可以创建一个服务进程,该进程管理共享的Python对象,并允许其他进程通过代理对象来访问这些共享对象。这样,大型数据只需复制一次到Manager的内存中,后续的子进程通过引用来访问,大大减少了进程间通信的开销。

Paraflow
Paraflow

AI产品设计智能体

下载

对于我们的NumPy数组列表,我们可以使用Manager().list()来创建一个共享列表。然后,子进程通过列表的索引来访问特定的NumPy数组,而不是直接传递整个数组。

以下是使用multiprocessing.Manager和Pool.starmap进行优化的示例代码:

import time
import numpy as np
from multiprocessing import Pool, Manager

# 模拟生成大型数据集
def mydataset(size, length):
    for ii in range(length):
        yield np.random.rand(*size)

# 适应共享内存的计算函数
# 现在接收数据索引和共享列表作为参数
def calc_optimized(idx, mat_list):
    # 从共享列表中获取NumPy数组
    mat = mat_list[idx]
    # 模拟一些耗时的NumPy计算
    for ii in range(1000):
        avg = np.mean(mat)
        std = np.std(mat)
    return avg, std

def main_optimized_test():
    ds = list(mydataset((500, 500), 100)) # 原始数据集

    # 1. 创建进程池
    # 建议根据CPU核心数设置,例如os.cpu_count()
    num_processes = 4
    mypool = Pool(num_processes)

    # 2. 创建Manager并生成共享列表
    manager = Manager()
    # 将原始数据集一次性复制到Manager管理的共享列表中
    mylist = manager.list(ds)

    print(f"\n--- 优化后测试结果 ({num_processes} 进程) ---")
    t0 = time.time()
    # 使用starmap传递多个参数:数据索引和共享列表
    # zip(range(len(ds)), [mylist]*len(ds)) 为每个任务生成 (索引, 共享列表) 对
    res_optimized = mypool.starmap(calc_optimized, zip(range(len(ds)), [mylist]*len(ds)))
    print(f"map with manager: {time.time() - t0:.2f}s")

    # 关闭进程池
    mypool.close()
    mypool.join()
    manager.shutdown() # 关闭Manager进程

if __name__ == '__main__':
    main_optimized_test()

运行上述优化后的代码,其输出结果可能如下:

map with manager: 1.94s

与原始的for循环和process_map相比,性能提升是巨大的。这验证了通过Manager实现共享内存,避免重复数据复制,是解决此类问题的有效途径。

实现细节与注意事项

  1. multiprocessing.Manager: Manager创建了一个单独的进程,该进程负责管理共享对象(如列表、字典等)。其他进程通过代理对象与Manager进程通信来访问这些共享对象。
  2. Manager().list(): 当你将一个可迭代对象(如ds)传递给manager.list()时,Manager会将ds中的所有元素一次性复制到其管理的共享列表中。此后,子进程通过mylist[idx]访问数据时,无需再次进行序列化和复制。
  3. Pool.starmap(): starmap适用于需要向目标函数传递多个参数的情况。在我们的例子中,calc_optimized函数需要idx(数据索引)和mat_list(共享列表)。zip(range(len(ds)), [mylist]*len(ds))生成了一个迭代器,其中每个元素都是一个元组(idx, mylist),starmap会将这些元组解包作为calc_optimized的参数。
  4. 进程数量: Pool(num_processes)中的num_processes应根据你的CPU核心数进行调整。对于CPU密集型任务,通常设置为CPU的核心数或核心数减一可以获得最佳性能。
  5. 资源管理: 在使用Pool和Manager后,务必调用mypool.close()、mypool.join()和manager.shutdown()来正确关闭进程池和Manager进程,释放系统资源。
  6. 数据可变性: Manager管理的共享对象是可变的。如果多个进程需要修改同一个共享对象,需要额外考虑同步机制(如锁),以避免竞态条件。在我们的例子中,calc_optimized只是读取数据,因此无需额外同步。
  7. 适用场景: 这种方法最适用于:
    • 处理大量相同结构但数据不同的任务。
    • 每个任务都需要访问一个或多个大型数据集。
    • 数据在任务执行期间是只读的或修改后不需要立即同步回主进程的。

总结

在Python中对NumPy等库进行计算密集型任务的并行加速时,简单地使用multiprocessing.Pool或process_map可能因数据序列化和反序列化的开销而导致性能下降。通过深入理解其背后的机制,我们发现对于大型数据集,利用multiprocessing.Manager创建共享内存是避免重复数据复制、显著提升并行计算效率的关键。这种方法将数据一次性加载到共享内存,后续子进程通过索引访问,从而消除了主要的性能瓶颈,实现了高效的并行处理。在实际应用中,务必根据任务特性和数据规模选择合适的并行策略。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

538

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

17

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

25

2026.01.06

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

502

2023.08.10

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

166

2025.12.24

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

10

2026.01.21

C++多线程相关合集
C++多线程相关合集

本专题整合了C++多线程相关教程,阅读专题下面的的文章了解更多详细内容。

14

2026.01.21

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

166

2025.12.24

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

10

2026.01.27

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.3万人学习

Django 教程
Django 教程

共28课时 | 3.6万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号