MPI_DOUBLE_INT 和 C++ 结构

MPI_DOUBLE_INT and C++ struct

MPI standard 定义了 MPI_X_INTX = FLOAT, DOUBLE, ... 数据类型族。为了确定性,让我们坚持使用 MPI_DOUBLE_INT。它的正式定义是:

(p. 180) The datatype MPI_DOUBLE_INT is as if defined by the following sequence of instructions:

block[0] = 1;
block[1] = 1;
disp[0] = 0;
disp[1] = sizeof(double);
type[0] = MPI_DOUBLE;
type[1] = MPI_INT;
MPI_TYPE_CREATE_STRUCT(2, block, disp, type, MPI_DOUBLE_INT);

定义意味着 doubleint 之间没有填充。该标准还断言此类型的总大小应为 16,示例如下(对于 char 而不是 int):

(p.85) Example 4.1 Assume that Type = {(double, 0), (char, 8)} (a double at displacement zero, followed by a char at displacement eight). Assume, furthermore, that doubles have to be strictly aligned at addresses that are multiples of eight. Then, the extent of this datatype is 16 (9 rounded to the next multiple of 8).

对应的C++结构为struct DI { double d; int i; }; 断言 struct 应该被打包以避免 doubleint 之间的填充。但是packed structure的大小是12(假设sizeof(int) = 4),不可能用他们的数组:

constexpr auto count = 2;  // >1
DI_packed di[count] = {...};
MPI_Send(di, count, MPI_DOUBLE_INT, ...);  // wrong!

是否有 C++ struct 与 MPI 定义完全对应,并且可以在 MPI 代码中安全且可移植地使用?似乎唯一 保证 使用 struct 的方法是定义一个带有手动添加的尾部填充 char 的打包结构。这是正确的吗?

附带说明一下,在我的机器上,MSVC 和 GCC 都会为未打包的 struct DI 生成 "MPI-compatible" 布局,所以从实际的角度来看,这个问题可能无关紧要,但我不确定。

也许你可以使用 union?

如果用 g++ 和 运行

编译以下内容
#include <iostream>
using namespace std;

int main()
{
  typedef struct {double x; int i;} Tmp;
  typedef union {char pad[16]; Tmp dint;} Doubleint;

  Doubleint value;

  value.dint.x = 3.14;
  value.dint.i = 6;

  cout << "sizeof(Tmp)       = " << sizeof(Tmp)       << endl;
  cout << "sizeof(Doubleint) = " << sizeof(Doubleint) << endl;

  typedef struct {double x; int i;} __attribute__((packed)) Packtmp;
  typedef union {char pad[16]; Packtmp dint;} Packdoubleint;

  Packdoubleint packvalue;

  packvalue.dint.x = 6.12;
  packvalue.dint.i = 9;

  cout << "sizeof(Packtmp)       = " << sizeof(Packtmp)       << endl;
  cout << "sizeof(Packdoubleint) = " << sizeof(Packdoubleint) << endl;

  return 0;
}

你得到

sizeof(Tmp)       = 16
sizeof(Doubleint) = 16
sizeof(Packtmp)       = 12
sizeof(Packdoubleint) = 16

即联合变量(Doubleint 和 Packdoubleint)总是 16 字节长,即使结构有不同的大小——我强制使用特定于 g++ 的属性取消填充 Packtmp。

您可以使用 C++11's alignas keyword to fix alignment (gcc 9.2,clang 9.0.0,icc 19.0.1):

 int main() {
    #pragma pack(push, 1)
    struct alignas(alignof(double)) pair {
        double d;
        int i;
    };
    #pragma pack(pop)

    static_assert(sizeof(double) == 8, "");
    static_assert(sizeof(int) == 4, "");
    static_assert(sizeof(double)+sizeof(int) == 12, "");
    static_assert(sizeof(pair) == 16, "");
    static_assert(alignof(double) == 8, "");
    static_assert(alignof(pair) == 8, "");

    return 0;
}

如果没有 alignas,打包结构在 x86-64 linux 和 gcc 9.2, clang 9.0.0 and icc 19.0.1:

上存在对齐问题
 int main() {
     #pragma pack(push, 1)
     struct pair {
         double d;
         int i;
     };
     #pragma pack(pop)

     static_assert(sizeof(double) == 8, "");
     static_assert(sizeof(int) == 4, "");
     static_assert(sizeof(double)+sizeof(int) == 12, "");
     static_assert(sizeof(pair) == 12, "");
     static_assert(alignof(double) == 8, "");
     static_assert(alignof(pair) == 1, ""); // !!!

     return 0;
 }

顺便说一句:不确定,但 OpenMPI 可能能够在 ompi/datatype/ompi_datatype_module.c:

中的数据类型定义期间处理非压缩结构
 #define DECLARE_MPI2_COMPOSED_STRUCT_DDT( PDATA, MPIDDT, MPIDDTNAME, type1, type2, MPIType1, MPIType2, FLAGS) \
     do {                                                                             \
         struct { type1 v1; type2 v2; } s[2];                                         \
         ompi_datatype_t *types[2], *ptype;                                           \
         int bLength[2] = {1, 1};                                                     \
         ptrdiff_t base, displ[2];                                                    \
                                                                                      \
         types[0] = (ompi_datatype_t*)ompi_datatype_basicDatatypes[MPIType1];         \
         types[1] = (ompi_datatype_t*)ompi_datatype_basicDatatypes[MPIType2];         \
         base = (ptrdiff_t)(&(s[0]));                                                 \
         displ[0] = (ptrdiff_t)(&(s[0].v1));                                          \
         displ[0] -= base;                                                            \
         displ[1] = (ptrdiff_t)(&(s[0].v2));                                          \
         displ[1] -= base;                                                            \
                                                                                      \
         ompi_datatype_create_struct( 2, bLength, displ, types, &ptype );             \
         displ[0] = (ptrdiff_t)(&(s[1]));                                             \
         displ[0] -= base;                                                            \
         if( displ[0] != (displ[1] + (ptrdiff_t)sizeof(type2)) )                      \
             ptype->super.ub = displ[0];                                              \
         ...