Search code examples
c++structmpipacking

MPI_DOUBLE_INT and C++ struct


The MPI standard defines a family of data types MPI_X_INT with X = FLOAT, DOUBLE, .... Let's stick with MPI_DOUBLE_INT for definiteness. Its formal definition is:

(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);

The definition implies no padding between double and int. The standard also asserts that the total size of this type should be 16, the example reads (for char instead of 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).

The corresponding C++ structure is struct DI { double d; int i; };. This answer asserts that the struct should be packed to avoid padding between double and int. But the size of a packed structure is 12 (assuming sizeof(int) = 4), and it is impossible to use an array of them:

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

Is there a C++ struct that corresponds exactly to the MPI definition and that can be safely and portably used in an MPI code? It seems that the only guaranteed way to use struct is to define a packed structure with manually added tail padding chars. Is this correct?

As a side note, on my machine both MSVC and GCC generate the "MPI-compatible" layout for unpacked struct DI, so this question might be irrelevant from a practical point of view, but I'm not sure.


Solution

  • Perhaps you can do this using union?

    If you compile the following with g++ and run

    #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;
    }
    

    you get

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

    i.e. the union variables (Doubleint and Packdoubleint) are always 16 bytes long even if the structures have different sizes - I've forced Packtmp to be unpadded using an attribute specific to g++.