KaMPIng 0.1.1
Flexible and (near) zero-overhead C++ bindings for MPI
|
A core feature of KaMPIng is the flexible named parameters concept, allowing the caller to name and pass parameters in arbitrary order and even omit certain parameters when the library is able to infer them (at the cost of additional computation/communication). With this approach, KaMPIng allows the user a fine-grained control over the parameter set which will be explained in greater detail in this document.
To illustrate the concept, we first look at the function signature of MPI_Alltoallv
as defined in MPI-4.0 which takes a sendbuf
of variable size on each PE i and sends messages of size sendcounts[j]
from PE i to PE j where the messages are received into recvbuf
. Apart from these three parameters, MPI requires additional information such as the number of elements to receive (recvcounts[]
) or possible displacements (sdispls
, rdispls
) etc.
In contrast to plain MPI, where the caller has to pass nine parameters to MPI_Alltoallv
, KaMPIng requires only the sendbuf
and sendcounts
parameters. All other parameters are optional and are computed as follows (if omitted):
recvcounts
are inferred through an additional call to MPI_Alltoall
over the sendcounts
.sdispls
and rdispls
are defaulted to an exclusive prefix sum of the sendcounts
and recvcounts
, respectively.sendtype
and recvtype
are inferred from the container's value_type
storing the data to be sent (or received).As outlined above, it is possible to pass any subset of the optional parameters, in any arbitrary order, to the wrapped function call.
This flexibility is enabled by KaMPIng's named parameter approach: Each argument passed to a wrapped MPI function is identified by a name instead of its position in the complete parameter list as in plain MPI.
Internally, named parameters are realized as factory functions which construct parameter objects inplace, but it's easier to think of this concept in terms of parameter_name(data)
.
Parameter Name | Factory Function In-Parameter | Factory Function Out-Parameter |
---|---|---|
send_buf | send_buf(...) | - |
recv_buf | - | recv_buf_out(..)/recv_buf(...) |
send_counts | send_counts(...) | send_counts_out(...) |
send_displs | send_displs(...) | send_displs_out(...) |
recv_counts | recv_counts(...) | recv_counts_out(...) |
... | ... | ... |
(See List of Named Parameters for a list of all named parameters currently used in KaMPIng.)
The named parameters passed to a wrapped MPI function serve either as in(put) or out(put) parameters. Through a named in parameter, the caller can provide input data to the wrapped MPI call, as exemplified by send_buf(data_to_send)
or send_counts(counts)
with data_to_send
and counts
accomodating the data to be sent and send counts, respectively. Using named out parameter the caller asks KaMPIng to internally compute/infer this parameter and output its value. Named output parameters are created via the respective *_out()
suffix. The data requested via out parameters is then either directly written to a memory location passed to the named parameter factory function or returned in a std::tuple
-like result object (depending on the ownership property, see Ownership).
One special case is the receive buffer (recv_buf(...)
). Although being an out parameter, it does not need to be explicitly given as KaMPIng assumes that a caller always wants to obtain this buffer.
See the following examples for an illustration of the different options
To summarize, there are three ways to pass parameters to KaMPIng:
sendbuf
, sendcounts
, recvcounts
etc.Note: KaMPIng will show an error message at compile time if any required parameter is missing.
Internally, a call to a named parameter factory function (send_buf(...), send_counts(...), ...
) instantiates an object of the DataBuffer/DataBufferBuilder class encapsulating all passed data/memory and user request regarding potential output strategies.
To fully benefit from KaMPIng's parameter flexibility, we briefly present the most important concepts of the DataBuffer
class. A DataBuffer
wraps either a single value or a whole underlying C++ container. In the latter case, KaMPIng requires the container to
value_type
data()
returning a pointer to the start of its memorysize()
returing the container's size.If resizing of the container is requested, KaMPIng additionally requires the container to expose a resize(unsigned int)
member function (see Resize Policy).
Futhermore, DataBuffer
has multiple (orthogonal) properties with which the caller can control the behavior of corresponding parameter inside the wrapped MPI call:
Named parameters factory functions like send_counts(...)
, recv_counts(...)
, ... generate DataBuffers
with type property in. This signals that they wrap a container with meaningful input data which can be used by the MPI call directly, e.g. the send counts wrapped by send_counts(...)
.
The corresponding *_out
named parameters instantiates DataBuffer
objects with type property out. They do not contain meaningful data yet and will be filled with values during the wrapped MPI call.
DataBuffers
with type inout correspond to named parameters like send_recv_buf(...)
used in MPI_Bcast
where data is sent on one root rank (in parameter) and received (out parameter) on all other ranks. Furthermore, passing send_recv_buf(...)
to wrapped MPI routines such as MPI_Allreduce, MPI_Allgather,...
indicates that the inplace version (MPI_INPLACE
) will be used. See the corresponding Doxygen documentation for more information.
This property simply determines whether a DataBuffer
owns its underlying data following the corresponding C++ ownership concept and is most important for out parameters. A DataBuffer
references (non-owns) its container if the latter has been passed to the named parameter as an lvalue as in send_buf(data)
or recv_buf_out(recv_buffer)
. Otherwise a DataBuffer
is owning, e.g. for recv_buf_out()
, recv_buf_out(std::move(recv_buffer))
.
Note that a named (out) parameter without associated underlying container (such as recv_buf_out()
) implies that the caller asks KaMPIng to allocate the memory to hold the computed/infered values. This results in an owning container, which is allocated by KaMPIng and ownership is transferred to the caller upon return.
Furthermore, the ownership of an out parameter is important as it specifies how the computed data will be returned to the caller. Owning out parameters are moved to a result object which is returned by value. Non-owning out parameters write their data directly to their associated underlying container. Therefore, the DataBuffer
object corresponding to this named parameter will not be part of the result object. The following code provides some example for owning and non-owning out parameters:
For more information about the result object, we refer to its doxygen documentation.
Resize policies are important only for out parameters. They control if and how the underlying memory/container are resized if the provided memory is not large enough to hold the computed values.
no_resize
: the underlying container is not resized and the caller has to ensure that it is large enough to hold all data.grow_only
: KaMPIng will resize the underlying container if its initial size is too small. However, a container's size will never be reduced.resize_to_fit
: KaMPIng will resize the underlying container to have exactly the size required to hold all data.The default resize policy is no-resize
(except for empty named out parameters such as recv_buf_out()
or recv_counts_out()
). For grow-only
or resize-to-fit
KaMPIng requires the container to posess a member function resize(unsigned integer)
taking an (unsigned) integer specify the requested number of elements of tye value_type
which the container should store. Note that KaMPIng can resize recv_buf
only if the corresponding recv_type
matches the size of the underlying container's value_type
. Otherwise the user must ensure that the container is large enough and only no-resize
is allowed.
In the following we give a list of all currently used named parameters in KaMPIng.
Parameter Name | Factory Function In-Parameter | Factory Function Out-Parameter | Remarks |
---|---|---|---|
send_buf | send_buf(...) | - | no out parameter available as send_buf is the canonical input parameter |
recv_buf | - | recv_buf_out(..)/recv_buf(...) | no in parameter available as recv_buf is the canoncial output parameter. recv_buf() is an alias for recv_buf_out() |
send_recv_buf | send_recv_buf(...) | - | e.g. in MPI_Bcast serves as send buffer on root rank and output parameter on other ranks; also used to indicate MPI_INPLACE |
send_count | send_count(...) | send_count_out(...) | |
send_counts | send_counts(...) | send_counts_out(...) | |
send_displs | send_displs(...) | send_displs_out(...) | |
recv_count | recv_count(...) | recv_count_out(...) | |
recv_counts | recv_counts(...) | recv_counts_out(...) | |
recv_displs | recv_displs(...) | recv_displs_out() | |
send_recv_count | send_recv_count(...) | send_recv_count_out(...) | |
op | op(...) | - | encapsulates the reduction function in, for example, MPI_Reduce/MPI_Allreduce |
source | source(...) | - | |
destination | destination(...) | - | |
status | status(...) | status_out(...) | |
statuses | statues(...) | statuses_out(...) | |
request | request(...) | - | |
root | root(...) | - | |
tag | tag(...) | - | |
send_mode | send_mode(...) | - | |
values_on_rank_0 | values_on_rank_0(...) | - | optionally encapsulates values to be used on rank 0 in MPI_Exscan |
send_type | send_type(...) | send_type_out(...) | |
recv_type | recv_type(...) | recv_type_out(...) | |
send_recv_type | send_recv_type(...) | send_recv_type_out(...) |
For further information about the usage of named parameters, we refer to the doxygen documentation of the wrapped MPI functions.