新闻中心

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

2025-12-04
浏览次数:
返回列表

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将这些数组拼接起来。

神笔马良 神笔马良

神笔马良 - AI让剧本一键成片。

神笔马良 320 查看详情 神笔马良

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

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则是更专业的选择。

以上就是mpi4py中异形NumPy数组的集合操作:gather与Gatherv详解的详细内容,更多请关注其它相关文章!


# 游戏开发  # 贵港高效seo技巧  # 全网通营销推广方案  # 湖北百度霸屏营销推广  # 产品推广的营销策略包括  # 德州网站建设外贸  # 苏州seo做关键词优化  # 郑州seo快排电话  # 给营销号做推广违法吗  # 松江区网站建设市场价  # 北部新集团网站建设  # 自己的  # python  # 工作机制  # 较高  # 两种  # 是一个  # 区中  # 序列化  # 适用于  # 偏移量  # red  # python程序  # 字节 


相关栏目: 【 科技资讯46185 】 【 网络学院92790


相关推荐: Go语言中动态执行代码字符串的策略与实践  poki网页游戏推荐_poki免费游戏平台入口  微博网页版首页入口 微博电脑端官网登录链接  QQ邮箱官方网页版登录 QQ邮箱个人邮箱快速访问  win11 Snap Layouts怎么用 Win11窗口布局与分屏多任务高效指南【必学】  css滚动动画效果怎么实现_使用Animate.css滚动触发动画类  Yandex官网搜索引擎免登录_俄罗斯Yandex一键直达入口  在J*a项目里如何构建对象之间的契约_接口约束的实际落地  HuggingFaceEmbeddings中向量嵌入维度调整的限制与理解  优化Django表单:提交验证失败后保留用户输入  J*aScript实现动态背景色下的文本与按钮颜色自适应调整  实现全屏滚动与导航点:专业教程  yandex入口引擎手机版 yandex安卓版下载入口  Win11文件资源管理器卡顿怎么修 Win11重置资源管理器进程优化响应速度【修复方法】  飞书妙记怎样用语音转文字速记_飞书妙记用语音转文字速记【速记方法】  MAC如何将整个网页截长图_MAC使用Safari的导出为PDF或第三方工具  如何仅使用CSS更改登录界面背景图像图标的颜色  我的世界mc.js免费游戏直接能玩 我的世界mc.js小游戏免费秒玩入口  批改网学生版PC登录 批改网官网登录系统入口  Python vgamepad库按键模拟:正确使用XUSB_BUTTON常量  Lar*el用户头像管理:实现图片缩放、存储与旧文件安全删除的最佳实践  Python getattr() 异常处理深度解析:避免程序意外退出  Win11怎么用U盘重装系统 Win11制作启动盘并重装系统完整教程【详解】  服务端验证_j*ascript输入检查  魅族20怎样在浏览器开无图省流_iPhone魅族20浏览器开无图省流【流量节省】  LINUX怎么设置定时任务_LINUX crontab配置教程  三星GalaxyZFold5怎样在相册制作折叠屏分镜_iPhone三星GalaxyZFold5相册制作折叠屏分镜【创意编辑】  J*aScript类型检查_j*ascript代码规范  12306选座怎么选到临时改签座_12306改签选座策略与步骤  Kafka Streams中基于消息头条件过滤消息的实现指南  百度浏览器字体显示异常偏小_百度浏览器字体渲染修复方案  格力空气能E5故障代码是什么情况_格力空气能E5代码解析与应对措施  windows10怎么关闭系统提示音_windows10彻底静音设置方法  解决 Vaadin 8 中大文件音频播放与定位时出现的 IOException  React Router 嵌套组件中 URL 重定向问题的解决方案  J*aScript对象创建方式_J*aScript设计模式应用  Go语言中的*string:深入理解字符串指针  React中useState与局部变量:理解组件状态管理与渲染机制  如何使用Go和Martini动态服务解码后的图片  j*a toString()的覆盖  响应式CSS Grid布局:优化网格项在小屏幕下的堆叠与宽度适配  React/Next.js中实现列表项的动态移动与状态管理:兼论唯一键的重要性  Composer的 "conflict" 字段有什么用_如何声明不兼容的包以避免依赖冲突  如何在网页中实现特定地点的随机图片展示  深入理解字体排版:Adobe光学字偶距与CSS字偶距的差异与实现  Node.js CSV 数据处理:基于字段值条件过滤整条记录的策略  QQ邮箱网页版入口 QQ邮箱官方邮箱登录通道  小米Civi 4录制视频过暗_小米Civi 4亮度优化  126邮箱账号注册 电脑版登录入口  印象笔记如何设离线包出差查阅_印象笔记设离线包出差查阅【离线阅读】 

搜索