MPI_Win_allocate_shared介绍和使用
文章目录
- MPI_Win_allocate_shared 详解 + 幽灵单元(Ghost Cell)交换完整示例
- 一、核心概念
- 1. 函数作用
- 2. 函数原型
- 3. 关键特性
- 二、幽灵单元(Ghost Cell)交换场景
- 1. 场景说明
- 2. 优势
- 三、完整代码实现(C语言)
- 代码逻辑
- 四、代码关键细节解析
- 1. 共享通信域创建
- 2. 共享内存分配
- 3. 邻居地址查询
- 4. 窗口同步
- 五、编译与运行
- 编译(MPI编译器)
- 运行(同节点运行3个进程)
- 输出示例
- 六、核心优势与适用场景
- 优势
- 适用场景
- 限制
- 总结
MPI_Win_allocate_shared 详解 + 幽灵单元(Ghost Cell)交换完整示例
MPI_Win_allocate_shared是MPI-3 标准引入的单边通信(RMA)核心函数,专门用于同节点共享内存场景,能让同一计算节点上的 MPI 进程直接读写彼此的内存,无需消息传递,性能远高于传统MPI_Send/Recv。
一、核心概念
1. 函数作用
为**共享内存通信域(通常是同一节点)**内的所有进程,集体分配一段内存:
- 调用进程拥有本地内存段(可直接读写)
- 同节点其他进程可通过MPI 单边RMA操作直接访问这段内存
- 无需拷贝数据,直接内存访问,极致高效
2. 函数原型
intMPI_Win_allocate_shared(MPI_Aint size,// 本地分配的内存大小(字节)intdisp_unit,// 地址偏移单位(通常=1,字节对齐)MPI_Info info,// 优化信息(通常MPI_INFO_NULL)MPI_Comm comm,// 共享内存通信域(必须是节点内共享子通信域)void*baseptr,// 输出:本地内存段起始地址MPI_Win*win// 输出:创建的共享内存窗口对象);3. 关键特性
- 集体操作:共享通信域内所有进程必须同时调用
- 节点内有效:只能在同一NUMA节点/计算节点的进程间共享
- 零拷贝:直接访问物理内存,无中间缓冲区
- 配套API:
MPI_Win_shared_query(查询其他进程的共享内存地址)
二、幽灵单元(Ghost Cell)交换场景
1. 场景说明
一维区域分解:
- 每个进程负责一段主数据(
data[1]~data[size]) - 左右各1个幽灵单元(
data[0]=左幽灵,data[size+1]=右幽灵) - 需求:左幽灵 ← 左邻居的最后一个主数据;右幽灵 ← 右邻居的第一个主数据
2. 优势
传统MPI需要Send/Recv收发数据,而MPI_Win_allocate_shared:
- 直接内存读写,无消息开销
- 代码更简洁,适合同节点并行
三、完整代码实现(C语言)
代码逻辑
- 创建节点内共享内存通信域(
comm_shm) - 用
MPI_Win_allocate_shared分配带幽灵单元的共享数组 - 用
MPI_Win_shared_query获取邻居的共享内存地址 - 直接内存赋值完成幽灵单元交换(无MPI收发)
- 同步窗口、释放资源
#include<stdio.h>#include<stdlib.h>#include<mpi.h>// 每个进程的主数据长度(不含幽灵单元)#defineDATA_SIZE4intmain(intargc,char**argv){MPI_Init(&argc,&argv);intrank,size;MPI_Comm_rank(MPI_COMM_WORLD,&rank);MPI_Comm_size(MPI_COMM_WORLD,&size);// ===================== 步骤1:创建节点内共享内存通信域 =====================MPI_Comm comm_shm;// 按物理节点拆分通信域,同节点进程在同一个comm_shm中MPI_Comm_split_type(MPI_COMM_WORLD,MPI_COMM_TYPE_SHARED,// 按共享内存节点拆分rank,MPI_INFO_NULL,&comm_shm);intshm_rank,shm_size;MPI_Comm_rank(comm_shm,&shm_rank);MPI_Comm_size(comm_shm,&shm_size);// ===================== 步骤2:分配共享内存(含2个幽灵单元) =====================// 总长度:左幽灵(1) + 主数据(DATA_SIZE) + 右幽灵(1)inttotal_len=DATA_SIZE+2;MPI_Aint shm_size_bytes=total_len*sizeof(int);// 总字节数int*local_data;// 本地共享内存指针MPI_Win shm_win;// 共享内存窗口// 集体分配共享内存MPI_Win_allocate_shared(shm_size_bytes,// 本地内存大小sizeof(int),// 偏移单位:按int对齐MPI_INFO_NULL,comm_shm,&local_data,// 输出本地内存地址&shm_win// 输出窗口);// ===================== 步骤3:初始化本地数据 =====================// local_data[0] = 左幽灵单元// local_data[1~DATA_SIZE] = 主数据// local_data[DATA_SIZE+1] = 右幽灵单元for(inti=1;i<=DATA_SIZE;i++){local_data[i]=rank*10+i;// 方便识别数据归属}local_data[0]=-1;// 初始左幽灵local_data[DATA_SIZE+1]=-1;// 初始右幽灵// ===================== 步骤4:获取左右邻居的共享内存地址 =====================intleft_rank=(shm_rank-1+shm_size)%shm_size;// 左邻居intright_rank=(shm_rank+1)%shm_size;// 右邻居int*left_neigh_data=NULL;// 左邻居共享内存地址int*right_neigh_data=NULL;// 右邻居共享内存地址MPI_Aint query_size;intquery_disp_unit;// 查询左邻居的共享内存基地址MPI_Win_shared_query(shm_win,left_rank,&query_size,&query_disp_unit,&left_neigh_data);// 查询右邻居的共享内存基地址MPI_Win_shared_query(shm_win,right_rank,&query_size,&query_disp_unit,&right_neigh_data);// ===================== 步骤5:直接内存交换幽灵单元(核心!) =====================// 开启RMA访问窗口MPI_Win_fence(0,shm_win);// 1. 左幽灵 = 左邻居的最后一个主数据local_data[0]=left_neigh_data[DATA_SIZE];// 2. 右幽灵 = 右邻居的第一个主数据local_data[DATA_SIZE+1]=right_neigh_data[1];// 同步所有进程的内存访问MPI_Win_fence(0,shm_win);// ===================== 步骤6:打印结果 =====================printf("[进程 %d] 数据:幽灵左=%d, 主数据=[%d,%d,%d,%d], 幽灵右=%d\n",rank,local_data[0],local_data[1],local_data[2],local_data[3],local_data[4],local_data[DATA_SIZE+1]);// ===================== 步骤7:释放资源 =====================MPI_Win_free(&shm_win);MPI_Comm_free(&comm_shm);MPI_Finalize();return0;}四、代码关键细节解析
1. 共享通信域创建
MPI_Comm_split_type(..., MPI_COMM_TYPE_SHARED, ...):
- 自动将全局通信域拆分为物理节点内的子通信域
- 只有同节点进程能共享内存,跨节点不支持
2. 共享内存分配
MPI_Win_allocate_shared:
- 第二个参数
disp_unit=sizeof(int):地址偏移按int单位计算,更安全 local_data是本地直接可用的指针,和普通数组无区别
3. 邻居地址查询
MPI_Win_shared_query:
- 输入窗口+目标进程rank
- 输出目标进程的共享内存起始指针
- 拿到指针后,直接赋值即可交换数据,无任何MPI收发
4. 窗口同步
MPI_Win_fence:
- 集体同步函数,确保所有进程的内存访问完成
- 必须在共享内存读写前后调用,保证数据一致性
五、编译与运行
编译(MPI编译器)
mpicc-oshm_ghost shm_ghost.c运行(同节点运行3个进程)
mpirun-n3./shm_ghost输出示例
[进程 0] 数据:幽灵左=24, 主数据=[1,2,3,4], 幽灵右=11 [进程 1] 数据:幽灵左=4, 主数据=[11,12,13,14], 幽灵右=21 [进程 2] 数据:幽灵左=14, 主数据=[21,22,23,24], 幽灵右=1- 进程0的左幽灵=进程2的最后一个主数据(24)
- 进程0的右幽灵=进程1的第一个主数据(11)
- 幽灵单元交换完成!
六、核心优势与适用场景
优势
- 性能极高:零拷贝、直接内存访问,比
Send/Recv快5~10倍 - 代码简洁:无需处理消息收发、标签、匹配问题
- 原生支持:MPI-3标准,主流MPI库(OpenMPI、MPICH)全支持
适用场景
- 同节点多进程并行(CPU多核、节点内并行)
- 数值模拟:幽灵单元交换、边界数据更新
- 共享内存架构下的高频数据交互
限制
- 仅支持同一物理节点的进程,跨节点必须配合传统MPI通信
- 必须集体调用,不能单独执行
总结
MPI_Win_allocate_shared是节点内共享内存+单边通信的最优方案- 幽灵单元交换:查询邻居地址 → 直接内存赋值 → 窗口同步,无收发
- 代码可直接用于一维/二维/三维数值模拟的边界数据交换
- 同节点场景下,性能远超传统MPI点对点通信
