0

0

掌握mpi4py中不同形状数组的聚合操作

聖光之護

聖光之護

发布时间:2025-12-06 12:39:16

|

159人浏览过

|

来源于php中文网

原创

掌握mpi4py中不同形状数组的聚合操作

在使用mpi4py进行并行编程时,`comm.Gather`函数默认要求所有进程发送相同形状的数组,这在处理动态或异构数据时会引发问题。本文将深入探讨两种有效的解决方案:利用`comm.gather`(小写)聚合通用Python对象并进行后续拼接,以及使用更强大的`comm.Gatherv`函数直接将不同大小的数组聚合到一个预分配的NumPy缓冲区中,从而实现高效且灵活的数据收集。

mpi4py中不同形状数组聚合的挑战

在mpi4py中,MPI.COMM_WORLD.Gather()方法设计用于收集所有进程中形状和大小完全相同的NumPy数组到一个根进程上的单个NumPy数组中。它的工作原理是预设一个固定大小的接收缓冲区,因此,当各个进程发送的数组形状或元素数量不一致时,comm.Gather会因为无法匹配预期的缓冲区布局而失败。

例如,考虑以下场景,进程1发送一个形状为(2, 3)的数组,而其他进程发送形状为(5, 3)的数组:

from mpi4py import MPI
import numpy as np

comm = MPI.COMM_WORLD
size = comm.Get_size()
rank = comm.Get_rank()

# 模拟不同形状的数组
a = np.zeros((2 if rank == 1 else 5, 3), dtype=float) + rank
print(f"Rank {rank}: 数组形状 {a.shape}")

# comm.Gather 会在这里失败,因为它期望所有进程发送相同大小的数据
# b = np.zeros((12, 3), dtype=float) - 1 # 预分配一个足够大的缓冲区,但comm.Gather仍会因形状不匹配而失败
# comm.Gather(a, b, root=0)
# if rank == 0:
#     print(b)

为了解决这个问题,我们需要采用不同的聚合策略。

解决方案一:使用 comm.gather 聚合通用Python对象

comm.gather(注意小写)是comm.Gather的通用版本,它不限于处理NumPy数组,可以聚合任意Python对象。在根进程上,comm.gather会返回一个包含所有进程发送对象的列表(或元组)。对于不同形状的NumPy数组,这意味着根进程将收到一个由这些不同形状数组组成的列表,随后可以通过numpy.concatenate将其拼接成一个更大的数组。

示例代码

import numpy as np
from mpi4py import MPI

comm = MPI.COMM_WORLD
size = comm.Get_size()
rank = comm.Get_rank()

# 模拟不同形状的数组
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:
    # 根进程将收到的数组列表进行拼接
    concatenated_array = np.concatenate(gathered_arrays)
    print(f"\nRank {rank}: 聚合并拼接后的数组形状 {concatenated_array.shape}, 数组内容:\n{concatenated_array}")
else:
    print(f"\nRank {rank}: 非根进程不接收聚合结果")

优点与考虑

  • 简单易用: 对于任意Python对象都适用,无需关心底层数据类型或缓冲区细节。
  • 灵活性高: 能够轻松处理不同形状、甚至不同类型的对象。
  • 性能考量: 数据首先被包装成Python对象,然后在根进程上解包并进行NumPy拼接。对于非常大的数据集,这可能会引入额外的内存开销和计算时间,因为它涉及多次内存拷贝。

解决方案二:使用 comm.Gatherv 高效聚合变长数组

comm.Gatherv(注意大写V)是MPI中专门为聚合变长数据设计的函数。它允许每个进程发送不同数量的数据,并由根进程将其直接收集到一个预分配的NumPy数组缓冲区中。这避免了comm.gather中可能存在的额外Python对象处理和内存拷贝,因此通常在处理大型数值数据时更高效。

使用comm.Gatherv的关键在于正确配置接收缓冲区参数,这些参数以一个元组的形式提供:(recvbuf, counts, displacements, datatype)。

GPT Detector
GPT Detector

在线检查文本是否由GPT-3或ChatGPT生成

下载
  • recvbuf: 根进程上预先分配好的NumPy数组,用于接收所有进程的数据。其总大小必须足以容纳所有发送数据。
  • counts: 一个列表或元组,指定从每个进程接收的元素数量。例如,如果进程0发送(5,3)的数组,则其元素数量为5*3=15。
  • displacements: 一个列表或元组,指定从每个进程接收的数据在recvbuf中的起始偏移量(以元素数量计)。
  • datatype: MPI数据类型,指定发送和接收元素的类型,例如MPI.DOUBLE对应NumPy的float64。

示例代码

为了简化示例,我们假设只有两个进程,一个发送(5, 3)的数组,另一个发送(2, 3)的数组。

import numpy as np
from mpi4py import MPI

comm = MPI.COMM_WORLD
size = comm.Get_size()
rank = comm.Get_rank()

# 确保进程数量不超过2,以便示例清晰
assert size <= 2, "此Gatherv示例仅为2个进程设计"

if rank == 0:
    a = np.zeros((5, 3), dtype=float) + rank
else:
    a = np.zeros((2, 3), dtype=float) + rank

print(f"Rank {rank}: 数组形状 {a.shape}, 数组内容:\n{a}")

# 计算全局总行数
# 对于 rank 0 (5行) 和 rank 1 (2行),总共 7 行
n_global_rows = 7 
n_cols = a.shape[1] # 列数保持不变

# 根进程预分配接收缓冲区
if rank == 0:
    b = np.zeros((n_global_rows, n_cols), dtype=float)

    # 定义每个进程发送的元素数量 (行数 * 列数)
    # 假设 rank 0 发送 5*3=15 个元素,rank 1 发送 2*3=6 个元素
    send_counts = [5 * n_cols, 2 * n_cols] 

    # 定义每个进程数据在 b 中的起始偏移量 (以元素为单位)
    # rank 0 的数据从 b[0] 开始 (0个元素偏移)
    # rank 1 的数据从 b[5*n_cols] 开始 (15个元素偏移,即在 rank 0 数据之后)
    displacements = [0, 5 * n_cols] 

    # 接收缓冲区描述符
    recvbuf_params = (b, send_counts, displacements, MPI.DOUBLE)
else:
    b = None # 非根进程不需要接收缓冲区,设为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"\nRank {rank}: 非根进程不接收 Gatherv 结果")

优点与考虑

  • 高效: 直接将数据聚合到预分配的NumPy缓冲区中,避免了不必要的内存拷贝和Python对象处理开销,尤其适用于大型数值数据集。
  • 精确控制: 通过counts和displacements参数,可以精确控制每个进程发送的数据量及其在接收缓冲区中的位置。
  • 复杂性: 需要手动计算每个进程发送的元素数量和在接收缓冲区中的偏移量,这在进程数量多或数据结构复杂时可能会变得繁琐且容易出错。

总结与选择建议

在mpi4py中处理不同形状的NumPy数组聚合时,您有以下两种主要选择:

  1. comm.gather (小写):

    • 适用场景: 当您需要聚合任意Python对象,或者数组形状差异较大且不规则,且对聚合性能要求不是极致时。
    • 优点: 简单易用,代码直观。
    • 缺点: 可能会引入额外的内存拷贝和Python对象处理开销。
  2. comm.Gatherv (大写V):

    • 适用场景: 当您处理大型数值NumPy数组,且对聚合性能有较高要求,希望直接将数据收集到预分配的缓冲区中时。
    • 优点: 效率高,直接操作底层缓冲区,避免额外拷贝。
    • 缺点: 配置相对复杂,需要手动计算每个进程的发送元素数量和偏移量。

选择哪种方法取决于您的具体需求:如果追求代码简洁和通用性,且数据量不大,comm.gather是更好的选择;如果数据量庞大,且对性能有严格要求,那么投入精力配置comm.Gatherv将是值得的。在实际应用中,通常会根据数据规模和性能瓶颈来决定最合适的聚合策略。

相关专题

更多
python开发工具
python开发工具

php中文网为大家提供各种python开发工具,好的开发工具,可帮助开发者攻克编程学习中的基础障碍,理解每一行源代码在程序执行时在计算机中的过程。php中文网还为大家带来python相关课程以及相关文章等内容,供大家免费下载使用。

769

2023.06.15

python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

661

2023.07.20

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

764

2023.07.25

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

659

2023.07.31

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

1325

2023.08.03

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

549

2023.08.04

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

579

2023.08.04

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

730

2023.08.11

AO3中文版入口地址大全
AO3中文版入口地址大全

本专题整合了AO3中文版入口地址大全,阅读专题下面的的文章了解更多详细内容。

1

2026.01.21

热门下载

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

精品课程

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

共4课时 | 11.4万人学习

Django 教程
Django 教程

共28课时 | 3.3万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.2万人学习

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

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