如何用MPI_Scatterv无拷贝分散行主序矩阵的非连续列?
嘿,这个问题问到点子上了!其实完全可以用MPI_Scatterv实现无拷贝的非连续列分散,而且自定义数据类型恰恰是解决这类问题的关键(你之前觉得它不行可能是没找对用法)。下面分两种常见场景给你拆解操作方法:
场景1:分配的是连续列块(比如你举的8×8矩阵例子)
这种情况最简单,不需要自定义类型,直接通过计算sendcounts和displs数组就能搞定:
假设根进程(比如进程0)持有完整矩阵,存储规则是A(i,j) = A[i + j*num_row](也就是每一列的num_row个元素是连续存储的)。
- 确定每个进程的列范围:比如进程0拿0-2列(3列),进程1拿3-4列(2列),进程2拿5-7列(3列)。
- 计算sendcounts数组:每个进程接收的总元素数 = 列数 × 行数。比如:
sendcounts[0] = 3*8 = 24sendcounts[1] = 2*8 = 16sendcounts[2] = 3*8 = 24
- 计算displs数组:每个进程对应数据在根进程缓冲区的起始位移 = 起始列号 × 行数。比如:
displs[0] = 0*8 = 0displs[1] = 3*8 = 24displs[2] = 5*8 = 40
- 调用MPI_Scatterv:
这种方式完全无拷贝,因为根进程中每个进程对应的数据是连续内存块,MPI直接发送这段连续区域。#include <mpi.h> #define NUM_ROWS 8 #define NUM_COLS 8 int main(int argc, char** argv) { MPI_Init(&argc, &argv); int rank, size; MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); double* root_matrix = NULL; double* local_matrix = NULL; int sendcounts[3] = {24, 16, 24}; int displs[3] = {0, 24, 40}; if (rank == 0) { // 根进程初始化完整矩阵 root_matrix = (double*)malloc(NUM_ROWS * NUM_COLS * sizeof(double)); // 填充数据... } // 每个进程分配本地缓冲区 local_matrix = (double*)malloc(sendcounts[rank] * sizeof(double)); // 无拷贝分散数据 MPI_Scatterv(root_matrix, sendcounts, displs, MPI_DOUBLE, local_matrix, sendcounts[rank], MPI_DOUBLE, 0, MPI_COMM_WORLD); // 后续处理... if (rank == 0) free(root_matrix); free(local_matrix); MPI_Finalize(); return 0; }
场景2:分配的是非连续列(比如进程0拿列0、2、5)
这时候就需要自定义MPI数据类型来描述非连续的内存布局,配合MPI_Scatterv实现无拷贝:
- 为每个进程定义列列表:比如进程0的列列表是
{0,2,5},共3列;进程1的列列表是{1,3,4,6},共4列。 - 创建自定义数据类型:用
MPI_Type_indexed来描述任意间隔的列块(每个列块是num_row个连续元素):// 以进程0为例,创建包含列0、2、5的自定义类型 int k_p = 3; // 列数 int blocklengths[] = {NUM_ROWS, NUM_ROWS, NUM_ROWS}; // 每个块的元素数 int displacements[] = {0*NUM_ROWS, 2*NUM_ROWS, 5*NUM_ROWS}; // 每个块的起始位移(以基本类型为单位) MPI_Datatype p_type; MPI_Type_indexed(k_p, blocklengths, displacements, MPI_DOUBLE, &p_type); MPI_Type_commit(&p_type); - 调用MPI_Scatterv:此时
sendcounts[p] = 1(每个进程接收1个自定义类型实例),displs[p] = 0(因为自定义类型已经包含了所有位移信息):
这种方式也是无拷贝的,MPI会根据自定义类型的描述直接读取根进程中的非连续内存区域,发送到对应进程。// 注意:这里需要确保所有进程都知道各自的sendcounts和自定义类型 // 通常根进程会先广播列分配信息,各进程创建自己的类型 MPI_Scatterv(root_matrix, sendcounts, displs, p_type, local_matrix, 1, p_type, 0, MPI_COMM_WORLD);
关键注意事项
- 所有进程都需要知道
sendcounts和displs数组的内容,通常由根进程计算后通过MPI_Bcast广播给其他进程。 - 自定义数据类型使用完后要记得用
MPI_Type_free释放,避免内存泄漏。 - 如果每个进程的列分配规则差异很大,手动用
MPI_Send/MPI_Recv配合自定义类型可能更灵活,本质也是无拷贝的,和Scatterv效率差不多。
内容的提问来源于stack exchange,提问作者Xu Hui
相关产品推荐
相关产品推荐

