The last, and the most complex MPI data type constructor is
MPI_Type_struct. This function lets MPI programmers do
all that they can do with MPI_Type_indexed, and on top
of all that the items picked up from various memory locations can
have different types. The items, as before, are assumed to be
present in contiguous blocks at every location pointed to by the
displacement array and within those blocks they are all
assumed to be of the same type. But the types may vary
from block to block. Here is the synopsis of this function in C:
MPI_Type_struct(int count, int *array_of_blocklengths,
MPI_Aint *array_of_displacements, MPI_Datatype *array_of_types,
MPI_Datatype *newtype)
and in Fortran:
mpi_type_struct(count, array_of_blocklengths, array_of_displacements,
array_of_types, newtype, ierror)
In summary the function works as follows: we are going to collect
count blocks of data. Each block comprises a number of
elements given by the corresponding entry in array_of_blocklengths.
Each block begins at a location given by the corresponding entry in
array_of_displacements. The type of data in each block is given
by the corresponding entry in array_of_types. What are the
displacements measured in? This is not a trivial question, because
now every block can comprise elements of a different type. There is
only one way to measure the displacements in this context. They
have to be measured in bytes. Consequently there is no
MPI_Type_hstruct function. You may say that MPI_Type_struct
is the MPI_Type_hstruct: the displacements are measured
in bytes and there is no no-byte version of this function.
Here is an example of how this function works. Let
type1 = {(double, 0), (char, 8)}
The call:
MPI_Type_struct(3, (2, 1, 3), (0, 16, 26), (MPI_FLOAT, type1, MPI_CHAR),
&newtype)
constructs a new data type with the following map:
newtype =
{(float, 0), (float, 4), (double, 16), (char, 24), (char, 26), (char, 27),
(char, 28)}
The call grabs 3 data blocks from locations given by the following
displacements in bytes: 0, 16, and 26. The first block comprises
2 floating point numbers. The second block comprises one item of
type type1, which is a double precision number followed by
a character. The third block comprises 3 characters. Observe
that the characters of the third block commence from a half-word boundary,
i.e., byte number 26, and then the following 1-byte characters are written one
after another without gaps in between. Strings are usually stored like
that. It would be tremendously wasteful to pad every 8-bit character
with 3 empty bytes (so as to reach a 32-bit boundary on a 32-bit system).
Likewise, some architectures allow to pack
up to two separate items into one word, so that if you have to pad
you only pad up to the half-word boundary.
All these things are obviously very system dependent. If you ever
decide to write programs like that you must very carefully annotate
what you do. A program that makes 32-bit architecture assumptions
may not work on a 64-bit or a 128-bit system. Today 128-bit systems
become increasingly common. The new Macintoshes and the new games
machines are 128-bit architectures. The IBM Power-3 and Power-4 chips
are in a way also 128-bit chips (split into
for easier
coding and better register utilization). As these systems become
more popular people who wrote MPI programs for 32-bit or 64-bit architectures
utilizing hand-defined derived MPI data types are going to get into
trouble. Low level coding is always very difficult to port.