通过包装器传递指向 MPI_Win_allocate_shared 的指针

Passing a pointer to MPI_Win_allocate_shared through a wrapper

我很难理解我的指针传递是怎么回事:

我有以下最小程序

#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 合作了足够长的时间,知道仅仅因为它现在可以工作,并不意味着它是正确的 - 你很幸运它适用于你的情况 - 因此,这里发生了什么,我怎么能同时考虑接口和它们似乎都有效?

阅读 MPI 标准和了解 C++ 指针和函数参数可能会有所帮助。例如,阅读 MPI_Win_allocate_shared:

的 Open MPI 手册页

On each process, it allocates memory of at least size bytes that is shared among all processes in comm, and returns a pointer to the locally allocated segment in baseptr that can be used for load/store accesses on the calling process.

C++ 函数通过函数参数 return 值的唯一方法是该参数是引用还是指向值位置的指针。因此,虽然手册页将 baseptr 列为 void *,但它实际上是 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_shared 的调用最终都有一个 void ** 作为参数,但前一种情况在概念上是错误的。您不是在传递 baz 的地址,而是传递指向正式参数 bar 的指针,它包含 baz 值的 copy .形式参数的语义基本上是用实际函数参数初始化的局部变量的语义:

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

这会将一个新值写入 bar,同时保持 baz 的值不变。这就是为什么在 MPI_WRAP 中调用 MPI_Win_allocate_shared 后你会看到一个新值,但一旦它 return 到调用函数就会看到旧值。

后者类似于

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 的别名,所以现在 &bar&baz 相同。因此,MPI_Win_allocate_shared 会将 return 值写入 baz 的存储区 space。

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