I want to create a generic MPI method, let's say a bcast for a specific object. but I need to convert primitive types to MPI_Data types ? any idea how I can do it ?
template <typename T>
void bcast_data(std::vector<T> vec)
{
...
}
I need to use MPI_INT for int, MPI_DOUBLE for double , ... so I need a type conversion method, I thought of creating an enum of dataypes that could give me the MPI_datatypes, but it requires passing the type as an input argument.
any idea ?
Thanks
You can use "type traits" idiom to serialize a generic object T
. This gives you the advantage to be able to add support for new types without changing the implementation.
Take a look at this MPI wrapper I wrote years ago: https://github.com/motonacciu/mpp.
You want to define a type trait like the following:
template <class T>
struct mpi_type_traits {
typedef T element_type;
typedef T* element_addr_type;
static inline MPI_Datatype get_type(T&& raw);
static inline size_t get_size(T& raw);
static inline element_addr_type get_addr(T& raw);
};
and provide specialization for concrete types, e.g. an std::vector<T>
as follows:
template <class T>
struct mpi_type_traits<std::vector<T>> {
typedef T element_type;
typedef T* element_addr_type;
static inline size_t get_size(std::vector<T>& vec) {
return vec.size();
}
static inline MPI_Datatype get_type(std::vector<T>&& vec) {
return mpi_type_traits<T>::get_type( T{} );
}
static inline element_addr_type get_addr(std::vector<T>& vec) {
return mpi_type_traits<T>::get_addr( vec.front() );
}
};
The last thing you need to do is to implement your MPI method and use the type traits, e.g. when calling an MPI_Send
:
template <class T>
void send(T &&value, ...) {
MPI_Send(mpi_type_traits<T>::get_addr(value),
mpi_type_traits<T>::get_size(value),
mpi_type_traits<T>::get_type(value), ...);
}
I think the Boost feature get_mpi_datatype should offer this functionality. I recommend building on such a sophisticated library rather than home brewed code whenever you can.
If you are looking for a light-weight generic solution that does not rely on Boost, one might extend apramc's idea to all current MPI data types with a constexpr
function with type_traits
such that the corresponding MPI data type is already evaluated at compile time as follows (click here for the Gist, C++17 required)
#include <cassert>
#include <complex>
#include <cstdint>
#include <type_traits>
#include <mpi.h>
template <typename T>
[[nodiscard]] constexpr MPI_Datatype mpi_get_type() noexcept
{
MPI_Datatype mpi_type = MPI_DATATYPE_NULL;
if constexpr (std::is_same<T, char>::value)
{
mpi_type = MPI_CHAR;
}
else if constexpr (std::is_same<T, signed char>::value)
{
mpi_type = MPI_SIGNED_CHAR;
}
else if constexpr (std::is_same<T, unsigned char>::value)
{
mpi_type = MPI_UNSIGNED_CHAR;
}
else if constexpr (std::is_same<T, wchar_t>::value)
{
mpi_type = MPI_WCHAR;
}
else if constexpr (std::is_same<T, signed short>::value)
{
mpi_type = MPI_SHORT;
}
else if constexpr (std::is_same<T, unsigned short>::value)
{
mpi_type = MPI_UNSIGNED_SHORT;
}
else if constexpr (std::is_same<T, signed int>::value)
{
mpi_type = MPI_INT;
}
else if constexpr (std::is_same<T, unsigned int>::value)
{
mpi_type = MPI_UNSIGNED;
}
else if constexpr (std::is_same<T, signed long int>::value)
{
mpi_type = MPI_LONG;
}
else if constexpr (std::is_same<T, unsigned long int>::value)
{
mpi_type = MPI_UNSIGNED_LONG;
}
else if constexpr (std::is_same<T, signed long long int>::value)
{
mpi_type = MPI_LONG_LONG;
}
else if constexpr (std::is_same<T, unsigned long long int>::value)
{
mpi_type = MPI_UNSIGNED_LONG_LONG;
}
else if constexpr (std::is_same<T, float>::value)
{
mpi_type = MPI_FLOAT;
}
else if constexpr (std::is_same<T, double>::value)
{
mpi_type = MPI_DOUBLE;
}
else if constexpr (std::is_same<T, long double>::value)
{
mpi_type = MPI_LONG_DOUBLE;
}
else if constexpr (std::is_same<T, std::int8_t>::value)
{
mpi_type = MPI_INT8_T;
}
else if constexpr (std::is_same<T, std::int16_t>::value)
{
mpi_type = MPI_INT16_T;
}
else if constexpr (std::is_same<T, std::int32_t>::value)
{
mpi_type = MPI_INT32_T;
}
else if constexpr (std::is_same<T, std::int64_t>::value)
{
mpi_type = MPI_INT64_T;
}
else if constexpr (std::is_same<T, std::uint8_t>::value)
{
mpi_type = MPI_UINT8_T;
}
else if constexpr (std::is_same<T, std::uint16_t>::value)
{
mpi_type = MPI_UINT16_T;
}
else if constexpr (std::is_same<T, std::uint32_t>::value)
{
mpi_type = MPI_UINT32_T;
}
else if constexpr (std::is_same<T, std::uint64_t>::value)
{
mpi_type = MPI_UINT64_T;
}
else if constexpr (std::is_same<T, bool>::value)
{
mpi_type = MPI_C_BOOL;
}
else if constexpr (std::is_same<T, std::complex<float>>::value)
{
mpi_type = MPI_C_COMPLEX;
}
else if constexpr (std::is_same<T, std::complex<double>>::value)
{
mpi_type = MPI_C_DOUBLE_COMPLEX;
}
else if constexpr (std::is_same<T, std::complex<long double>>::value)
{
mpi_type = MPI_C_LONG_DOUBLE_COMPLEX;
}
assert(mpi_type != MPI_DATATYPE_NULL);
return mpi_type;
}
You can call the MPI commands then like e.g.
template <typename T>
void mpi_exchange_inplace(std::vector<T>& vec, int const length, MPI_Comm const& icomm)
{
MPI_Alltoall(MPI_IN_PLACE, 0, MPI_DATATYPE_NULL, vec.data(), length, mpi_get_type<T>(), icomm);
return;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With