ENH: add mapping for fundamental and OpenFOAM types -> MPI data types

- this will allow send/recv/broadcast of various data types directly
  without reinterpreting as bytes. No performance benefit, but makes
  programming more flexible. In addition to the normal MPI types, also
  provide some convenient groupings such as double[3], double[9] etc.

- the additional user-defined data types are created on the start of
  MPI and removed before shutdown
This commit is contained in:
Mark Olesen 2025-02-25 09:52:12 +01:00
parent c448ea2a11
commit bf60a124ab
4 changed files with 417 additions and 0 deletions

View File

@ -90,6 +90,78 @@ public:
sync //!< (MPI_Ssend, MPI_Issend)
};
//- Mapping of some fundamental and aggregate types to MPI data types
enum class dataTypes : int
{
// NOTE: changes here require adjustment in
// PstreamGlobals, UPstreamTraits
// Builtin Types [8]:
DataTypes_begin, //!< (internal use) begin all data types
type_byte = DataTypes_begin, //!< byte, char, unsigned char, ...
type_int32,
type_int64,
type_uint32,
type_uint64,
type_float,
type_double,
type_long_double,
//! (internal use) end of builtin data types marker
BuiltinTypes_end, UserTypes_begin = BuiltinTypes_end,
//!< (internal use) begin of user data types marker
// User Types [6]:
type_3float = UserTypes_begin, //!< 3*float (eg, floatVector)
type_3double, //!< 3*double (eg, doubleVector)
type_6float, //!< 6*float (eg, floatSymmTensor, complex vector)
type_6double, //!< 6*double (eg, doubleSymmTensor, complex vector)
type_9float, //!< 9*float (eg, floatTensor)
type_9double, //!< 9*double (eg, doubleTensor)
// Internal markers
invalid, //!< invalid type (NULL)
//! (internal use) end of user data types marker
UserTypes_end = invalid, DataTypes_end = invalid
//!< (internal use) end of all data types marker
};
//- Mapping of some MPI op codes.
// Currently excluding min/max location until they are needed
enum class opCodes : int
{
// NOTE: changes here require adjustment in
// PstreamGlobals, UPstreamTraits
ReduceOps_begin, //!< (internal use) begin reduce/window
// Reduce or window operations [10]
op_min = ReduceOps_begin, //!< min(x,y)
op_max, //!< max(x,y)
op_sum, //!< (x + y)
op_prod, //!< (x * y)
op_bool_and, //!< Logical \c and
op_bool_or, //!< Logical \c or
op_bool_xor, //!< Logical \c xor
op_bit_and, //!< Bit-wise \c and for (unsigned) integral types
op_bit_or, //!< Bit-wise \c or for (unsigned) integral types
op_bit_xor, //!< Bit-wise \c xor for (unsigned) integral types
//! (internal use) end of reduce-ops marker
ReduceOps_end, WindowOps_begin = ReduceOps_end,
//!< (internal use) begin end of window-ops marker
// Window-only operations [2]
op_replace = WindowOps_begin, //!< Replace (window only)
op_no_op, //!< No-op (window only)
// Internal markers
invalid, //!< invalid op (NULL)
//! (internal use) end of window-ops marker
WindowOps_end = invalid, OpCodes_end = invalid
//!< (internal use) end of all ops marker
};
// Public Classes

View File

@ -34,6 +34,12 @@ Foam::DynamicList<bool> Foam::PstreamGlobals::pendingMPIFree_;
Foam::DynamicList<MPI_Comm> Foam::PstreamGlobals::MPICommunicators_;
Foam::DynamicList<MPI_Request> Foam::PstreamGlobals::outstandingRequests_;
Foam::PstreamGlobals::DataTypeLookupTable
Foam::PstreamGlobals::MPIdataTypes_(MPI_DATATYPE_NULL);
Foam::PstreamGlobals::OpCodesLookupTable
Foam::PstreamGlobals::MPIopCodes_(MPI_OP_NULL);
// * * * * * * * * * * * * * * * Communicators * * * * * * * * * * * * * * * //
@ -60,4 +66,247 @@ void Foam::PstreamGlobals::initCommunicator(const label index)
}
// * * * * * * * * * * * * * * * * Data Types * * * * * * * * * * * * * * * //
void Foam::PstreamGlobals::initDataTypes()
{
static_assert
(
PstreamGlobals::DataTypeLookupTable::max_size()
== (int(UPstream::dataTypes::DataTypes_end)+1),
"Lookup table size != number of dataTypes enumerations"
);
// From enumeration to MPI datatype
#undef defineType
#define defineType(Idx, BaseType) \
MPIdataTypes_[int(UPstream::dataTypes::Idx)] = BaseType;
// Intrinsic Types [8]:
defineType(type_byte, MPI_BYTE);
defineType(type_int32, MPI_INT32_T);
defineType(type_int64, MPI_INT64_T);
defineType(type_uint32, MPI_UINT32_T);
defineType(type_uint64, MPI_UINT64_T);
defineType(type_float, MPI_FLOAT);
defineType(type_double, MPI_DOUBLE);
defineType(type_long_double, MPI_LONG_DOUBLE);
#undef defineType
// User-define types
#undef defineUserType
#define defineUserType(Idx, Count, BaseType, Name) \
{ \
auto& dt = MPIdataTypes_[int(UPstream::dataTypes::Idx)]; \
MPI_Type_contiguous(Count, BaseType, &dt); \
MPI_Type_set_name(dt, Name); \
MPI_Type_commit(&dt); \
}
// User Types [6]:
defineUserType(type_3float, 3, MPI_FLOAT, "float[3]");
defineUserType(type_3double, 3, MPI_DOUBLE, "double[3]");
defineUserType(type_6float, 6, MPI_FLOAT, "float[6]");
defineUserType(type_6double, 6, MPI_DOUBLE, "double[6]");
defineUserType(type_9float, 9, MPI_FLOAT, "float[9]");
defineUserType(type_9double, 9, MPI_DOUBLE, "double[9]");
#undef defineUserType
}
void Foam::PstreamGlobals::deinitDataTypes()
{
// User types only
auto first =
(
MPIdataTypes_.begin() + int(UPstream::dataTypes::UserTypes_begin)
);
const auto last =
(
MPIdataTypes_.begin() + int(UPstream::dataTypes::UserTypes_end)
);
for (; first != last; ++first)
{
if (MPI_DATATYPE_NULL != *first)
{
MPI_Type_free(&(*first));
}
}
}
// Debugging
bool Foam::PstreamGlobals::checkDataTypes()
{
// Check all types, not just user types
auto first =
(
MPIdataTypes_.begin()
);
const auto last =
(
MPIdataTypes_.begin() + int(UPstream::dataTypes::DataTypes_end)
);
for (; (first != last); ++first)
{
if (MPI_DATATYPE_NULL == *first)
{
return false;
}
}
return true;
}
// Debugging
void Foam::PstreamGlobals::printDataTypes(bool all)
{
int rank = -1;
if
(
(MPI_SUCCESS != MPI_Comm_rank(MPI_COMM_WORLD, &rank))
|| (rank != 0)
)
{
return;
}
const auto print = [&](auto firstIndex, auto lastIndex)
{
auto first =
(
PstreamGlobals::MPIdataTypes_.begin() + int(firstIndex)
);
const auto last =
(
PstreamGlobals::MPIdataTypes_.begin() + int(lastIndex)
);
for (; (first != last); ++first)
{
std::cerr
<< " name = "
<< PstreamGlobals::dataType_name(*first) << '\n';
}
};
if (all)
{
std::cerr << "enumerated data types:\n";
print
(
UPstream::dataTypes::DataTypes_begin,
UPstream::dataTypes::DataTypes_end
);
}
else
{
// User types only.
std::cerr << "enumerated user-defined data types:\n";
print
(
UPstream::dataTypes::UserTypes_begin,
UPstream::dataTypes::UserTypes_end
);
}
}
std::string Foam::PstreamGlobals::dataType_name(MPI_Datatype datatype)
{
if (MPI_DATATYPE_NULL == datatype)
{
return std::string("(null)");
}
char buf[MPI_MAX_OBJECT_NAME];
int len;
if (MPI_SUCCESS == MPI_Type_get_name(datatype, buf, &len))
{
if (len > 0)
{
return std::string(buf, len);
}
else
{
return std::string("(anon)");
}
}
return std::string("???");
}
// * * * * * * * * * * * * * * * * Op Codes * * * * * * * * * * * * * * * * //
void Foam::PstreamGlobals::initOpCodes()
{
static_assert
(
PstreamGlobals::OpCodesLookupTable::max_size()
== (int(UPstream::opCodes::OpCodes_end)+1),
"Lookup table size != number of opCodes enumerations"
);
// From enumeration to MPI datatype
#undef defineCode
#define defineCode(Idx, CodeType) \
MPIopCodes_[int(UPstream::opCodes::Idx)] = CodeType;
defineCode(op_min, MPI_MIN);
defineCode(op_max, MPI_MAX);
defineCode(op_sum, MPI_SUM);
defineCode(op_prod, MPI_PROD);
// TBD: still need to sort out if they are MPI_C_BOOL or MPI_CXX_BOOL
// ...
defineCode(op_bool_and, MPI_LAND);
defineCode(op_bool_or, MPI_LOR);
defineCode(op_bool_xor, MPI_LXOR);
defineCode(op_bit_and, MPI_BAND);
defineCode(op_bit_or, MPI_BOR);
defineCode(op_bit_xor, MPI_BXOR);
// Do not include MPI_MINLOC, MPI_MAXLOC since they are tied to
// float_int, double_int and larger or other types
// window-only
defineCode(op_replace, MPI_REPLACE);
defineCode(op_no_op, MPI_NO_OP);
#undef defineCode
}
void Foam::PstreamGlobals::deinitOpCodes()
{}
bool Foam::PstreamGlobals::checkOpCodes()
{
auto first = MPIopCodes_.begin();
const auto last =
(
MPIopCodes_.begin() + int(UPstream::opCodes::OpCodes_end)
);
for (; (first != last); ++first)
{
if (MPI_OP_NULL == *first)
{
return false;
}
}
return true;
}
// ************************************************************************* //

View File

@ -40,6 +40,7 @@ SourceFiles
#define Foam_PstreamGlobals_H
#include "DynamicList.H"
#include "FixedList.H"
#include "UPstream.H" // For UPstream::Request
#include "openfoam_mpi.H"
@ -62,6 +63,17 @@ extern DynamicList<MPI_Comm> MPICommunicators_;
//- Outstanding non-blocking operations.
extern DynamicList<MPI_Request> outstandingRequests_;
typedef Foam::FixedList<MPI_Datatype, 15> DataTypeLookupTable;
//- MPI data types corresponding to some fundamental and OpenFOAM types.
//- Indexed by UPstream::dataTypes enum
extern DataTypeLookupTable MPIdataTypes_;
typedef Foam::FixedList<MPI_Op, 13> OpCodesLookupTable;
//- MPI operation types, indexed by UPstream::opCodes enum
extern OpCodesLookupTable MPIopCodes_;
// * * * * * * * * * * * * * * * Communicators * * * * * * * * * * * * * * * //
@ -89,6 +101,76 @@ inline bool warnCommunicator(int comm) noexcept
}
// * * * * * * * * * * * * * * * * Data Types * * * * * * * * * * * * * * * //
//- Create mapping into MPIdataTypes_ and define user data types
void initDataTypes();
//- Free any user data types
void deinitDataTypes();
//- Debugging only: check if data type mappings are non-null
bool checkDataTypes();
//- Debugging only: print data type names (all or just user-defined)
void printDataTypes(bool all = false);
//- Lookup of dataTypes enumeration as an MPI_Datatype
inline MPI_Datatype getDataType(UPstream::dataTypes id)
{
return MPIdataTypes_[static_cast<int>(id)];
}
//- Fatal if data type is not valid
inline void checkDataType(UPstream::dataTypes id)
{
if (id == UPstream::dataTypes::invalid)
{
FatalErrorInFunction
<< "Invalid data type"
<< Foam::abort(FatalError);
}
}
//- Return MPI internal name for specified MPI_Datatype
std::string dataType_name(MPI_Datatype datatype);
//- Return MPI internal name for dataTypes enumeration
inline std::string dataType_name(UPstream::dataTypes id)
{
return dataType_name(MPIdataTypes_[static_cast<int>(id)]);
}
// * * * * * * * * * * * * * * * * Op Codes * * * * * * * * * * * * * * * * //
//- Create mapping into MPIopCodes_
void initOpCodes();
//- Free any user-defined op codes
void deinitOpCodes();
//- Debugging only: check if op code mappings are non-null
bool checkOpCodes();
//- Lookup of opCodes enumeration as an MPI_Op
inline MPI_Op getOpCode(UPstream::opCodes id)
{
return MPIopCodes_[static_cast<int>(id)];
}
//- Fatal if opcode is not valid
inline void checkOpCode(UPstream::opCodes id)
{
if (id == UPstream::opCodes::invalid)
{
FatalErrorInFunction
<< "Invalid operation code"
<< Foam::abort(FatalError);
}
}
// * * * * * * * * * * * * * * * * Requests * * * * * * * * * * * * * * * * //
//- Reset UPstream::Request to MPI_REQUEST_NULL

View File

@ -251,6 +251,17 @@ bool Foam::UPstream::init(int& argc, char**& argv, const bool needsThread)
ourMpi = true;
}
// Define data type mappings and user data types. Defined now so that
// any OpenFOAM Pstream operations may make immediate use of them.
PstreamGlobals::initDataTypes();
PstreamGlobals::initOpCodes();
if (UPstream::debug)
{
PstreamGlobals::printDataTypes();
}
// Check argument list for local world
label worldIndex = -1;
for (int argi = 1; argi < argc; ++argi)
@ -591,6 +602,9 @@ void Foam::UPstream::shutdown(int errNo)
}
}
// Free any user data types
PstreamGlobals::deinitDataTypes();
PstreamGlobals::deinitOpCodes();
MPI_Finalize();
}