gpt4 book ai didi

c++ - 如何为动态加载编写 MPI 包装器

转载 作者:塔克拉玛干 更新时间:2023-11-03 01:34:57 26 4
gpt4 key购买 nike

由于 MPI 不提供二进制兼容性,仅提供源代码兼容性,因此我们不得不将我们的求解器源代码发送给客户,以便他们将我们的求解器与他们首选的 MPI 版本一起使用。好吧,我们已经到了无法再提供源代码的地步。

因此,我正在寻找围绕 MPI 调用创建包装器的方法。我们的想法是为我们提供 stub 函数的 header ,用户将编写实现,从中创建一个动态库,然后我们的求解器将在运行时加载它。

但是解决方案并不“优雅”并且容易出错。因为有 struct 参数(例如,MPI_Request),其 struct 定义可能因一个 MPI 实现而异,我们需要接受 (void*) 用于我们的许多 stub 参数。此外,如果一个 MPI 与另一个 MPI 的参数数量不同(我不确定它是否保证永远不会发生),唯一的解决方法是使用 var_args

//header (provided by us)
int my_stub_mpi_send(const void buf, int count, void* datatype,
int dest, int tag, void* comm);

//*.c (provided by user)
#include <my_stub_mpi.h>
#include <mpi.h>
int my_stub_mpi_send(const void buf, int count, void* datatype,
int dest, int tag, void* comm)
{
return MPI_Send(buf, count, *((MPI_Datatype) datatype),
dest, tag, ((MPI_Comm) comm));
}
//Notes: (1) Most likely the interface will be C, not C++,
// unless I can make a convincing case for C++;
// (2) The goal here is to avoid *void pointers, if possible;

我的问题是是否有人知道解决这些问题的方法?

最佳答案

如果您只针对支持 PMPI 分析接口(interface)的平台,那么有一个通用解决方案,只需对原始源代码进行最少甚至不需要更改。基本思想是(滥用)使用 PMPI 接口(interface)作为包装器。在某种非 OO 意义上,它可能是桥接模式的实现。

首先,几点观察。 MPI 标准中定义了一种结构类型,即 MPI_Status。它只有三个公开可见的字段:MPI_SOURCEMPI_TAGMPI_ERR。没有 MPI 函数按值获取 MPI_Status。该标准定义了以下不透明类型:MPI_AintMPI_CountMPI_OffsetMPI_Status(+ 几种 Fortran 互操作性类型为清楚起见,特此删除)。前三个是不可或缺的。然后是10种句柄类型,从MPI_CommMPI_Win。句柄可以作为特殊整数值或作为指向内部数据结构的指针来实现。 MPICH 和基于它的其他实现采用第一种方法,而 Open MPI 采用第二种方法。作为指针或整数,任何类型的句柄都可以适合单个 C 数据类型,即 intptr_t

基本思想是覆盖所有 MPI 函数并将它们的参数重新定义为 intptr_t 类型,然后让用户编译的代码转换为正确的类型并进行实际的 MPI 调用:

mytypes.h 中:

typedef intptr_t my_MPI_Datatype;
typedef intptr_t my_MPI_Comm;

mympi.h中:

#include "mytypes.h"

// Redefine all MPI handle types
#define MPI_Datatype my_MPI_Datatype
#define MPI_Comm my_MPI_Comm

// Those hold the actual values of some MPI constants
extern MPI_Comm my_MPI_COMM_WORLD;
extern MPI_Datatype my_MPI_INT;

// Redefine the MPI constants to use our symbols
#define MPI_COMM_WORLD my_MPI_COMM_WORLD
#define MPI_INT my_MPI_INT

// Redeclare the MPI interface
extern int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm);

mpiwrap.c中:

#include <mpi.h>
#include "mytypes.h"

my_MPI_Comm my_MPI_COMM_WORLD;
my_MPI_Datatype my_MPI_INT;

int MPI_Init(int *argc, char ***argv)
{
// Initialise the actual MPI implementation
int res = PMPI_Init(argc, argv);
my_MPI_COMM_WORLD = (intptr_t)MPI_COMM_WORLD;
my_MPI_INT = (intptr_t)MPI_INT;
return res;
}

int MPI_Send(void *buf, int count, intptr_t datatype, int dest, int tag, intptr_t comm)
{
return PMPI_Send(buf, count, (MPI_Datatype)datatype, dest, tag, (MPI_Comm)comm);
}

在您的代码中:

#include "mympi.h" // instead of mpi.h

...
MPI_Init(NULL, NULL);
...
MPI_Send(buf, 10, MPI_INT, 1, 10, MPI_COMM_WORLD);
...

MPI 包装器可以静态链接或动态预加载。只要 MPI 实现对 PMPI 接口(interface)使用弱符号,这两种方法都有效。您可以扩展上面的代码示例以涵盖所有使用的 MPI 函数和常量。所有常量都应保存在 MPI_Init/MPI_Init_thread 的包装器中。

处理 MPI_Status 有点复杂。尽管该标准定义了公共(public)字段,但并未说明它们在结构中的顺序或位置。再一次,MPICH 和 Open MPI 显着不同:

// MPICH (Intel MPI)
typedef struct MPI_Status {
int count_lo;
int count_hi_and_cancelled;
int MPI_SOURCE;
int MPI_TAG;
int MPI_ERROR;
} MPI_Status;

// Open MPI
struct ompi_status_public_t {
/* These fields are publicly defined in the MPI specification.
User applications may freely read from these fields. */
int MPI_SOURCE;
int MPI_TAG;
int MPI_ERROR;
/* The following two fields are internal to the Open MPI
implementation and should not be accessed by MPI applications.
They are subject to change at any time. These are not the
droids you're looking for. */
int _cancelled;
size_t _ucount;
};

如果您只使用 MPI_Status 从诸如 MPI_Recv 的调用中获取信息,那么将三个公共(public)字段复制到一个用户定义的静态结构中是微不足道的,其中包含只有那些领域。但是,如果您还使用读取非公共(public)函数的 MPI 函数,那是不够的,例如MPI_Get_count。在这种情况下,一种愚蠢的非 OO 方法是简单地嵌入原始状态结构:

mytypes.h 中:

// 64 bytes should cover most MPI implementations
#define MY_MAX_STATUS_SIZE 64

typedef struct my_MPI_Status
{
int MPI_SOURCE;
int MPI_TAG;
int MPI_ERROR;
char _original[MY_MAX_STATUS_SIZE];
} my_MPI_Status;

mympi.h中:

#define MPI_Status        my_MPI_Status
#define MPI_STATUS_IGNORE ((my_MPI_Status*)NULL)

extern int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Status *status);
extern int MPI_Get_count(MPI_Status *status, MPI_Datatype datatype, int *count);

mpiwrap.c中:

int MPI_Recv(void *buf, int count, my_MPI_Datatype datatype, int dest, int tag, my_MPI_Comm comm, my_MPI_Status *status)
{
MPI_Status *real_status = (status != NULL) ? (MPI_Status*)&status->_original : MPI_STATUS_IGNORE;
int res = PMPI_Recv(buf, count, (MPI_Datatype)datatype, dest, tag, (MPI_Comm)comm, real_status);
if (status != NULL)
{
status->MPI_SOURCE = real_status->MPI_SOURCE;
status->MPI_TAG = real_status->MPI_TAG;
status->MPI_ERROR = real_status->MPI_ERROR;
}
return res;
}

int MPI_Get_count(my_MPI_Status *status, my_MPI_Datatype datatype, int *count)
{
MPI_Status *real_status = (status != NULL) ? (MPI_Status*)&status->_original : MPI_STATUS_IGNORE;
return PMPI_Get_count(real_status, (MPI_Datatype)datatype, count);
}

在您的代码中:

#include "mympi.h"

...
MPI_Status status;
int count;

MPI_Recv(buf, 100, MPI_INT, 0, 10, MPI_COMM_WORLD, &status);
MPI_Get_count(&status, MPI_INT, &count);
...

然后,您的构建系统应检查实际 MPI 实现的 sizeof(MPI_Status) 是否小于或等于 MY_MAX_STATUS_SIZE

以上只是一个快速而肮脏的想法——还没有测试它,一些 const 或转换可能在这里或那里丢失。它应该在实践中工作并且非常易于维护。

关于c++ - 如何为动态加载编写 MPI 包装器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38442254/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com