当前位置:

首页 > 编程开发 > MPI4Py中异形数组的gather与gatherv操作详解

MPI4Py中异形数组的gather与gatherv操作详解

本文深入探讨了在mpi4py中使用comm.Gather处理不同形状NumPy数组时遇到的挑战,并提供了两种有效的解决方案:利用comm.gather收集通用Python对象后进行拼接,以及使用comm.Gatherv直接将不同大小的数组高效地集合到一个预分配的NumPy缓冲区中。文章将详细阐述这两种方法的实现细节、适用场景及代码示例,帮助开发者优化并行程序的集合通信效率。

本文目录

    mpi4py中异形NumPy数组的集合操作:gather与Gatherv详解

    本文深入探讨了在mpi4py中使用`comm.Gather`处理不同形状NumPy数组时遇到的挑战,并提供了两种有效的解决方案:利用`comm.gather`收集通用Python对象后进行拼接,以及使用`comm.Gatherv`直接将不同大小的数组高效地集合到一个预分配的NumPy缓冲区中。文章将详细阐述这两种方法的实现细节、适用场景及代码示例,帮助开发者优化并行程序的集合通信效率。

    在并行计算中,经常需要在各个进程(或核心)上处理数据,然后将这些分散的结果收集到根进程上进行进一步的分析或整合。mpi4py库提供了强大的MPI(Message Passing Interface)绑定,使得Python程序能够方便地进行并行化。其中,comm.Gather是一个常用的集体通信操作,用于将所有进程的相同类型和形状的数据收集到根进程的一个连续缓冲区中。

    然而,当每个进程需要发送的NumPy数组形状不一致时,直接使用comm.Gather会导致程序失败,因为它期望所有发送的数据都具有相同的维度和大小。本文将介绍两种在mpi4py中有效处理不同形状NumPy数组集合操作的方法:comm.gather(小写g)和comm.Gatherv(大写G,小写v)。

    1. 问题背景:comm.Gather的局限性

    comm.Gather操作的本质是将所有进程的相同类型数据按顺序收集到根进程的一个预定义缓冲区中。这意味着每个发送进程的数据必须是同构的,即具有相同的形状和数据类型。

    考虑以下示例,其中不同进程生成了形状不同的NumPy数组:

    from mpi4py import MPI
    import numpy as np
    
    comm = MPI.COMM_WORLD
    size = comm.Get_size()
    rank = comm.Get_rank()
    
    # rank 1生成(2,3)的数组,其他进程生成(5,3)的数组
    a = np.zeros((2 if rank == 1 else 5, 3), dtype=float) + rank
    print(f"Rank {rank}: 数组形状 {a.shape}, 数据:\n{a}")
    
    # 尝试使用comm.Gather,这通常会失败
    # b = np.zeros((12, 3), dtype=float) - 1 # 假设一个足够大的接收缓冲区
    # comm.Gather(a, b, root=0)
    # if rank == 0:
    #     print(f"Rank {rank}: 接收到的数据:\n{b}")

    运行上述代码中被注释掉的comm.Gather部分,会因为数组形状不匹配而导致运行时错误。为了解决这个问题,我们需要采用更灵活的集合通信方法。

    2. 解决方案一:使用 comm.gather 收集通用Python对象

    comm.gather(注意是小写g)是mpi4py中一个更通用的集合操作。它不局限于NumPy数组,可以收集任何可序列化的Python对象。当每个进程发送的NumPy数组形状不同时,comm.gather会将其作为独立的Python对象进行收集,并在根进程上返回一个包含这些对象的列表或元组。随后,我们可以使用numpy.concatenate将这些数组拼接起来。

    2.1 实现细节

    1. 发送阶段: 每个进程将自己的NumPy数组a作为独立的Python对象发送。
    2. 接收阶段: 根进程接收所有发送的数组,并将它们存储在一个Python列表(或元组)中。
    3. 后处理: 根进程使用np.concatenate()函数将列表中的所有NumPy数组沿指定轴拼接成一个大的NumPy数组。

    2.2 代码示例

    import numpy as np
    from mpi4py import MPI
    
    comm = MPI.COMM_WORLD
    size = comm.Get_size()
    rank = comm.Get_rank()
    
    # rank 1生成(2,3)的数组,其他进程生成(5,3)的数组
    a = np.zeros((2 if rank == 1 else 5, 3), dtype=float) + rank
    print(f"Rank {rank}: 数组形状 {a.shape}, 数据:\n{a}")
    
    # 使用comm.gather收集不同形状的数组
    # 根进程会收到一个包含所有数组的列表
    gathered_arrays = comm.gather(a, root=0)
    
    if rank == 0:
        print(f"\nRank {rank}: 原始收集到的数据 (列表形式):\n{gathered_arrays}")
        # 将收集到的数组列表拼接成一个大数组
        concatenated_array = np.concatenate(gathered_arrays, axis=0) # 沿0轴拼接
        print(f"\nRank {rank}: 拼接后的数据形状 {concatenated_array.shape}, 数据:\n{concatenated_array}")
    else:
        # 非根进程的gathered_arrays为None
        print(f"Rank {rank}: 非根进程,gathered_arrays为 {gathered_arrays}")

    2.3 适用场景与注意事项

    • 优点: 实现简单,代码直观,无需预先计算每个进程发送的数据大小和偏移量。适用于数据量不是特别巨大,或者需要灵活处理不同类型对象的场景。
    • 缺点: 涉及到Python对象的序列化和反序列化,以及后续的np.concatenate操作,可能会引入额外的性能开销,尤其是在数据量非常大时。此外,根进程需要足够的内存来存储所有单独的数组,然后再进行拼接。

    3. 解决方案二:使用 comm.Gatherv 直接集合到NumPy数组

    comm.Gatherv(注意是Gatherv)是comm.Gather的变体,专门设计用于处理每个进程发送数据大小不同的情况。它允许将来自不同进程的、大小不一的数据直接集合到根进程的一个预分配的NumPy数组中,而无需中间的Python对象列表和后续的拼接操作。这通常在性能要求较高的场景下更为高效。

    3.1 comm.Gatherv的接收缓冲区参数

    comm.Gatherv的接收缓冲区参数比comm.Gather复杂,它是一个元组,通常格式为 (recvbuf, recvcounts, displs, recvtype):

    • recvbuf:根进程上的目标NumPy数组,必须预先分配好,且大小足以容纳所有进程发送的数据。
    • recvcounts:一个列表或NumPy数组,长度等于进程总数。每个元素表示从对应进程接收的元素数量(不是字节数)。
    • displs:一个列表或NumPy数组,长度等于进程总数。每个元素表示从对应进程接收的数据在recvbuf中的起始元素偏移量(不是字节偏移量)。
    • recvtype:接收数据的MPI数据类型(例如MPI.DOUBLE对应float64,MPI.INT对应int32)。

    3.2 实现细节

    1. 预计算: 在所有进程上(或至少在根进程上),需要预先计算好每个进程将发送的元素数量 (recvcounts) 以及这些数据在根进程的接收缓冲区中的起始偏移量 (displs)。
    2. 根进程预分配: 根进程需要预先分配一个足够大的NumPy数组作为recvbuf。
    3. 调用 comm.Gatherv: 所有进程调用comm.Gatherv,并由根进程提供接收缓冲区的详细信息。

    3.3 代码示例

    为了简化recvcounts和displs的计算,以下示例假设只有两个进程(size <= 2),但在实际应用中,这些参数通常需要通过comm.allgather或comm.gather先收集每个进程的形状信息来动态计算。

    import numpy as np
    from mpi4py import MPI
    
    comm = MPI.COMM_WORLD
    size = comm.Get_size()
    rank = comm.Get_rank()
    
    # 示例限制为两个进程,以便手动设置recvcounts和displs
    assert size <= 2, "此Gatherv示例仅适用于2个或更少的进程"
    
    if rank == 0:
        a = np.zeros((5, 3), dtype=float) + rank
    else: # rank == 1
        a = np.zeros((2, 3), dtype=float) + rank
    
    print(f"Rank {rank}: 数组形状 {a.shape}, 数据:\n{a}")
    
    # 定义全局总行数 (5来自rank 0, 2来自rank 1)
    n_global_rows = 7
    # 定义每行元素数
    cols = a.shape[1]
    
    # 根进程需要预分配接收缓冲区
    if rank == 0:
        b = np.zeros((n_global_rows, cols), dtype=float)
        # 计算每个进程发送的元素数量
        # rank 0: 5行 * 3列 = 15个元素
        # rank 1: 2行 * 3列 = 6个元素
        recvcounts = [5 * cols, 2 * cols] # 对应每个进程的元素总数
    
        # 计算每个进程数据在b中的起始偏移量 (元素偏移量)
        # rank 0: 从b的0偏移量开始
        # rank 1: 从b的第15个元素 (即第5行3列后) 开始
        displs = [0, 5 * cols] 
    
        # 组合Gatherv的接收缓冲区参数
        recvbuf_params = (b, recvcounts, displs, MPI.DOUBLE)
    else:
        b = None
        recvbuf_params = None # 非根进程不需要提供接收缓冲区参数
    
    # 执行Gatherv操作
    comm.Gatherv(a, recvbuf_params, root=0)
    
    if rank == 0:
        print(f"\nRank {rank}: Gatherv接收到的数据形状 {b.shape}, 数据:\n{b}")
    else:
        print(f"Rank {rank}: 非根进程,b为 {b}")
    

    3.4 适用场景与注意事项

    • 优点: 效率高,数据直接传输到预分配的NumPy数组,避免了Python对象的序列化/反序列化和后续拼接的开销。适用于处理大规模NumPy数组,对性能要求高的场景。
    • 缺点: 实现相对复杂,需要精确计算recvcounts和displs。在实际应用中,通常需要先进行一次comm.gather或comm.allgather操作来收集所有进程的数组形状信息,然后根进程根据这些信息计算recvcounts和displs。
    • 数据类型匹配: recvtype参数必须与NumPy数组的dtype精确匹配。MPI.DOUBLE对应np.float64,MPI.FLOAT对应np.float32,MPI.INT对应np.int32等。
    • 元素计数与偏移: recvcounts和displs中的值都是元素数量,而不是字节数。例如,一个形状为(5, 3)的数组,其元素数量是15。

    4. 总结与选择建议

    当需要在mpi4py中将不同形状的NumPy数组收集到根进程时:

    • comm.gather (小写g):

      • 适用场景: 数据量相对较小,对代码简洁性要求更高,或者需要收集的不仅仅是NumPy数组,而是各种Python对象。
      • 优点: 简单易用,无需复杂的参数计算。
      • 缺点: 性能开销可能较高,需要额外的np.concatenate步骤。
    • comm.Gatherv (大写G,小写v):

      • 适用场景: 处理大规模NumPy数组,对性能有严格要求,且可以预先计算出每个进程发送的数据大小和偏移量。
      • 优点: 效率高,直接将数据写入预分配的缓冲区。
      • 缺点: 实现复杂,需要精确计算recvcounts和displs。

    在实际开发中,应根据具体的应用需求(数据规模、性能要求、代码复杂度等)权衡选择合适的方法。对于大多数情况,如果性能不是极致瓶颈,comm.gather配合np.concatenate是一个简单有效的方案。而对于高性能计算场景,comm.Gatherv则是更专业的选择。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    GitHub六个AI开源项目能做什么?软件理解、语音与求职场景
    GitHub六个AI开源项目能做什么?软件理解、语音与求职场景

    解析六个GitHub AI开源项目的具体用途,包括软件静态分析、智能体回答检查、多语言配音、求职材料生成、本地代码检索及动态歌词展示,帮助开发者根据实际需求筛选工具并正确验证结果。

    opencode安装skills教程及本地配置方法
    opencode安装skills教程及本地配置方法

    遇到OpenCode Skills无法加载或配置无效?本文手把手教你完成Skills的安装、环境变量设置及本地调试,避开常见坑点,让AI助手更懂你的代码习惯。

    如何开启Visual Studio2022实时代码分析警告提示
    如何开启Visual Studio2022实时代码分析警告提示

    解决 Visual Studio 2022 编辑时不显示代码分析警告的问题。指南涵盖 .NET 项目实时分析设置、Roslyn 分析器范围调整及错误列表来源筛选,确保开发者能高效利用实时代码诊断功能。

    如何在Visual Studio2022查看变量监视窗口调试程序
    如何在Visual Studio2022查看变量监视窗口调试程序

    详解 Visual Studio 2022 调试技巧:包括断点设置、监视窗口开启方法、变量添加步骤以及通过 F10 单步执行观察变量值变化的全过程。

    Visual Studio2017设置默认编码为UTF-8不带BOM
    Visual Studio2017设置默认编码为UTF-8不带BOM

    针对 Visual Studio 2017 缺乏全局默认编码选项的情况,详解如何利用 .editorconfig 设定项目级 UTF-8 无 BOM 规则,以及手动转换现有文件并校验字节的方法。

    Visual Studio2022F5调试提示启动项目未配置怎么修复
    Visual Studio2022F5调试提示启动项目未配置怎么修复

    解决 Visual Studio 2022 F5 调试报错问题,通过设置启动项目、检查解决方案属性及正确区分类库与应用项目,快速恢复调试功能。

    Visual Studio2022NuGet包还原失败离线处理方案
    Visual Studio2022NuGet包还原失败离线处理方案

    解决Visual Studio 2022离线环境中NuGet包还原失败的难题,涵盖依赖链分析、本地源配置、包集收集及隔离验证步骤,帮助开发者构建稳定的离线开发环境。

    Visual Studio2017大项目加载缓慢,优化加载性能技巧
    Visual Studio2017大项目加载缓慢,优化加载性能技巧

    解决Visual Studio 2017大型项目加载慢的实用指南。涵盖性能监测、扩展管理、文档恢复优化及版本兼容性检查,帮助开发者精准定位瓶颈并提升工作效率。

    Visual Studio2022设置多启动项目同时调试运行
    Visual Studio2022设置多启动项目同时调试运行

    详解 Visual Studio 2022 多启动项目配置方法,包括启动动作选择、顺序调整及调试技巧,帮助开发者高效调试分布式或前后端分离应用。

    fastapi下载文件接口实现教程
    fastapi下载文件接口实现教程

    深入解析在FastAPI框架中实现文件下载接口的完整方案,涵盖基于FileResponse的本地静态文件传输、基于StreamingResponse的分块流式传输、RFC 5987中文文件名编码规范、路径穿越安全防御及BackgroundTasks临时文件清理,并配备详细代码示例与架构解析图。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。