首页 > 解决方案 > 通过包装器传递指向 MPI_Win_allocate_shared 的指针

问题描述

我很难理解我的指针传递发生了什么:

我有以下最小程序

#include <mpi.h>
void ALLOC_SHM(double * arr, int sz);
void MPI_WRAP( MPI_Aint size, int disp, MPI_Comm comm, double * bufptr, MPI_Win* win ) ;
int main(int argc, char const *argv[])
{
   int size, whoami;
   double* arr;
   MPI_Init(NULL, NULL) ;
   MPI_Comm_size( MPI_COMM_WORLD, &size ); 
   MPI_Comm_rank(MPI_COMM_WORLD, &whoami);
   
   ALLOC_SHM(arr, 1000); 
   
   
   return 0;
}


void ALLOC_SHM(double * arr, int sz)
{
   MPI_Win win; 
   MPI_Aint size = sz*sizeof(double);
   int disp = sizeof(double);
   printf("in alloc before mpi_wrap: %p\n", arr);
   MPI_WRAP(size, disp, MPI_COMM_WORLD, arr, &win);  
   printf("in alloc AFTER mpi_wrap: %p\n", arr);

   return;
}


void MPI_WRAP( MPI_Aint size, int disp, MPI_Comm comm, double * bufptr, MPI_Win* win ) 

{
   printf("in mpi_wrap before WIN_ALLOC: %p\n", bufptr);
   int ierr = MPI_Win_allocate_shared( size, disp, MPI_INFO_NULL, comm, &bufptr, win );
   printf("in mpi_wrap AFTER WIN_ALLOC: %p\n", bufptr);
   return;
}

我的疑惑点:

  1. 对 MPI_WRAP 的调用是错误的,但为什么呢?是不是因为 MPI Api 需要指针的地址(即**arr

  2. 如果上述原因是正确的,那么可以通过将地址传递给指针来解决,所以我的 CALL 是MPI_WRAP(size, disp,MPI_COMM_WORLD, &arr, &win);

  3. 现在,如果这是正确的 (2),我意外地意识到我的代码在不更改MPI_WRAP界面的情况下运行它没有问题。但很明显,我不是传递一个指针 ( *bufptr),而是传递一个**buftr. 然后我将我的MPI_WRAP界面更改为以下内容 MPI_WRAP( MPI_Aint size, int disp, MPI_Comm comm, double ** bufptr, MPI_Win* win )(我将其更改为双指针,并仍然按照 (2) 中的方式调用它)。令人惊讶的是,这也有效。我与 MPI 合作了足够长的时间,知道仅仅因为它现在可以工作,并不意味着它是正确的——而且你很幸运它适用于你的情况——因此,这里发生了什么,我为什么可以同时考虑接口和他们似乎都工作?

标签: c++pointersc++17mpi

解决方案


阅读 MPI 标准并理解 C++ 指针和函数参数可能会有所帮助。例如,阅读 Open MPI 手册页MPI_Win_allocate_shared

在每个进程上,它分配至少size个字节的内存,在comm中的所有进程之间共享,并返回一个指向baseptr中本地分配的段的指针,该指针可用于调用进程的加载/存储访问。

C++ 函数通过函数参数返回值的唯一方法是该参数是引用还是指向该值的位置的指针。因此,虽然手册页列出baseptrvoid *,但它确实是类型void **

现在,两者之间的区别:

void foo(void *bar) {
   MPI_Win_allocate_shared(..., &bar, ...);
}

void *baz;
foo(baz);

void foo(void **bar) {
   MPI_Win_allocate_shared(..., bar, ...);
}

void *baz;
foo(&baz);

尽管在这两种情况下,对的调用都以MPI_Win_allocate_sharedavoid **作为参数,但前一种情况在概念上是错误的。您传递的不是 的地址baz,而是一个指向正式参数的指针,该参数bar包含的值的副本baz。形式参数的语义基本上是用实际函数参数初始化的局部变量的语义:

void *bar = baz;
MPI_Win_allocate_shared(..., &bar, ...);

这将写入一个新值bar,同时保持 的值baz不变。这就是为什么您在调用MPI_Win_allocate_sharedinside后会看到一个新值,MPI_WRAP但在返回调用函数后会看到旧值。

后者类似于

void **bar = &baz;
MPI_Win_allocate_shared(..., bar, ...);

这具有完全不同的语义。bar现在包含的地址,baz这就是MPI_Win_allocate_shared将分配的缓冲区的地址写入的位置。

所以正确的 C/C++ 代码是:

void foo(void **bar) {
   MPI_Win_allocate_shared(..., bar, ...);
}

void *baz;
foo(&baz);

C++ 有引用,同样可以这样写:

void foo(void *&bar) {
   MPI_Win_allocate_shared(..., &bar, ...);
}

void *baz;
foo(baz);

这与第一种(不正确的)情况非常相似,关键区别在于这里的形式参数bar是实际参数 的别名baz,所以 now&bar与 相同&baz。因此,MPI_Win_allocate_shared会将返回值写入 的存储空间baz

附注:我推荐 Open MPI 的手册页,因为其中的解释基本上是 MPI 标准的摘录。


推荐阅读