KaMPIng 0.1.1
Flexible and (near) zero-overhead C++ bindings for MPI
Loading...
Searching...
No Matches
bcast.hpp
1// This file is part of KaMPIng.
2//
3// Copyright 2022-2024 The KaMPIng Authors
4//
5// KaMPIng is free software : you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
6// License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
7// version. KaMPIng is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
8// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
9// for more details.
10//
11// You should have received a copy of the GNU Lesser General Public License along with KaMPIng. If not, see
12// <https://www.gnu.org/licenses/>.
13
14#pragma once
15
16#include <type_traits>
17
18#include <kassert/kassert.hpp>
19#include <mpi.h>
20
21#include "kamping/assertion_levels.hpp"
23#include "kamping/collectives/collectives_helpers.hpp"
24#include "kamping/comm_helper/is_same_on_all_ranks.hpp"
25#include "kamping/communicator.hpp"
32#include "kamping/result.hpp"
33
34/// @addtogroup kamping_collectives
35/// @{
36
37/// @brief Wrapper for \c MPI_Bcast
38///
39/// This wrapper for \c MPI_Bcast sends data from the root to all other ranks.
40///
41/// The following buffer is required on the root rank:
42/// - \ref kamping::send_recv_buf() containing the data that is sent to the other ranks. Non-root ranks must allocate
43/// and provide this buffer or provide the receive type as a template parameter to \c bcast() as
44/// it's used for deducing the value type. The buffer will be resized on non-root ranks according to the buffer's
45/// kamping::BufferResizePolicy.
46///
47/// The following parameter is optional but causes additional communication if not present.
48/// - \ref kamping::send_recv_count() specifying how many elements are broadcasted. This parameter must be given either
49/// on all or none of the ranks. If not specified, the count is set to the size of kamping::send_recv_buf() on
50/// root and broadcasted to all other ranks. This parameter is mandatory if \ref kamping::send_recv_type() is given.
51///
52/// The following parameter are optional:
53/// - \ref kamping::send_recv_type() specifying the \c MPI datatype to use as send type on the root PE and recv type on
54/// all non-root PEs. If omitted, the \c MPI datatype is derived automatically based on send_recv_buf's underlying \c
55/// value_type.
56///
57/// - \ref kamping::root() specifying an alternative root. If not present, the default root of the \c
58/// Communicator is used, see root().
59///
60/// @tparam recv_value_type_tparam The type that is received. Only required when no \ref kamping::send_recv_buf() is
61/// given.
62/// @tparam Args Automatically deduced template parameters.
63/// @param args All required and any number of the optional parameters described above.
64/// @return Result object wrapping the output parameters to be returned by value.
65///
66/// @see \ref docs/parameter_handling.md for general information about parameter handling in KaMPIng.
67/// <hr>
68/// \include{doc} docs/resize_policy.dox
69template <
70 template <typename...>
71 typename DefaultContainerType,
72 template <typename, template <typename...> typename>
73 typename... Plugins>
74template <typename recv_value_type_tparam /* = kamping::internal::unused_tparam */, typename... Args>
76 using namespace ::kamping::internal;
78 Args,
81 );
82
83 // Get the root PE
85 std::tuple(this->root()),
86 args...
87 );
88 KASSERT(this->is_valid_rank(root.rank_signed()), "Invalid rank as root.", assert::light);
89 KASSERT(
90 is_same_on_all_ranks(root.rank_signed()),
91 "root() parameter must be the same on all ranks.",
93 );
94
97 auto&& send_recv_buf =
98 internal::select_parameter_type_or_default<internal::ParameterType::send_recv_buf, default_send_recv_buf_type>(
99 std::tuple(),
100 args...
101 )
104 if constexpr (is_serialization_used) {
107 if (this->is_root(root.rank_signed())) {
108 send_recv_buf.underlying().serialize();
109 }
110 }
111
112 using value_type = typename std::remove_reference_t<decltype(send_recv_buf)>::value_type;
113 static_assert(
114 !std::is_same_v<value_type, internal::unused_tparam>,
115 "No send_recv_buf parameter provided and no receive value given as template parameter. One of these is "
116 "required."
117 );
118
119 constexpr bool buffer_is_modifiable = std::remove_reference_t<decltype(send_recv_buf)>::is_modifiable;
120
122 [[maybe_unused]] constexpr bool send_recv_type_is_in_param = !has_to_be_computed<decltype(send_recv_type)>;
123
124 KASSERT(
125 this->is_root(root.rank_signed()) || buffer_is_modifiable,
126 "send_recv_buf must be modifiable on all non-root ranks.",
128 );
129
130 // Get the optional recv_count parameter. If the parameter is not given, allocate a new container.
132 auto&& count_param = internal::select_parameter_type_or_default<ParameterType::send_recv_count, default_count_type>(
133 std::tuple(),
134 args...
135 )
136 .construct_buffer_or_rebind();
137
138 constexpr bool count_has_to_be_computed = has_to_be_computed<decltype(count_param)>;
139 KASSERT(
140 is_same_on_all_ranks(count_has_to_be_computed),
141 "send_recv_count() parameter is either deduced on all ranks or must be explicitly provided on all ranks.",
143 );
144 if constexpr (count_has_to_be_computed) {
145 int count;
146 int const NO_BUF_ON_ROOT = -1;
147 if (this->is_root(root.rank_signed())) {
148 count_param.underlying() = asserting_cast<int>(send_recv_buf.size());
149 count = count_param.get_single_element();
150
151 if constexpr (!has_parameter_type<internal::ParameterType::send_recv_buf, Args...>()) {
152 // if no send_recv_buf is provided on the root rank, we abuse the recv_count parameter to signal that
153 // there is no buffer on the root rank to all other ranks.
154 // This allows us to fail on all ranks if the root rank does not provide a buffer.
155 count = NO_BUF_ON_ROOT;
156 };
157 }
158 // Transfer the recv_count
159 // This error code is unused if KTHROW is removed at compile time.
160 /// @todo Use bcast_single for this.
161 [[maybe_unused]] int err = MPI_Bcast(
162 &count, // buffer
163 1, // count
164 mpi_datatype<decltype(count)>(), // datatype
165 root.rank_signed(), // root
166 this->mpi_communicator() // comm
167 );
168 this->mpi_error_hook(err, "MPI_Bcast");
169
170 // it is valid to do this check here, because if no send_recv_buf is provided on the root rank, we have
171 // always have deduce counts and get into this branch.
172 KASSERT(count != NO_BUF_ON_ROOT, "send_recv_buf must be provided on the root rank.", assert::light);
173
174 // Output the recv count via the output_parameter
175 count_param.underlying() = count;
176 } else {
177 KASSERT(
179 ),
180 "send_recv_buf must be provided on the root rank.",
182 );
183 }
184
185 // Resize my send_recv_buf to be able to hold all received data on all non_root ranks.
186 // Trying to resize a single element buffer to something other than 1 will throw an error.
187 if (!this->is_root(root.rank_signed())) {
188 auto compute_recv_buffer_size = [&] {
189 return asserting_cast<size_t>(count_param.get_single_element());
190 };
191 send_recv_buf.resize_if_requested(compute_recv_buffer_size);
192 KASSERT(
193 // if the send_recv type is user provided, kamping cannot make any assumptions about the required size of
194 // the send_recv buffer
196 "send/receive buffer is not large enough to hold all received elements on a non-root rank.",
198 );
199 }
200
201 // Perform the broadcast. The error code is unused if KTHROW is removed at compile time.
202 [[maybe_unused]] int err = MPI_Bcast(
203 send_recv_buf.data(), // buffer
204 count_param.get_single_element(), // count
205 send_recv_type.get_single_element(), // datatype
206 root.rank_signed(), // root
207 this->mpi_communicator() // comm
208 );
209 this->mpi_error_hook(err, "MPI_Bcast");
210
211 return make_mpi_result<std::tuple<Args...>>(
213 std::move(count_param),
214 std::move(send_recv_type)
215 );
216} // namespace kamping::internal
217
218/// @brief Wrapper for \c MPI_Bcast
219///
220/// This wrapper for \c MPI_Bcast sends a single value from the root to all other ranks. Calling \c bcast_single() is a
221/// shorthand for calling `bcast(..., recv_counts(1))`. It always issues only a single \c MPI_Bcast call, as no receive
222/// counts have to be exchanged.
223///
224/// The following buffer is required on the root rank:
225/// - \ref kamping::send_recv_buf() containing the single value that is sent to the other ranks. Non-root ranks must
226/// either allocate and provide this buffer or provide the receive type as a template parameter to \c bcast_single() as
227/// it's used for deducing the value type.
228///
229/// The following parameter is optional:
230/// - \ref kamping::root() specifying an alternative root. If not present, the default root of the \c Communicator is
231/// used, see root().
232///
233/// @todo Add support for unnamed first parameter send_recv_buf.
234///
235/// @tparam recv_value_type_tparam The type that is received. Only required when no \ref kamping::send_recv_buf() is
236/// given.
237/// @tparam Args Automatically deduced template parameters.
238/// @param args All required and any number of the optional buffers described above.
239/// @return The single broadcasted value.
240template <
241 template <typename...>
242 typename DefaultContainerType,
243 template <typename, template <typename...> typename>
244 typename... Plugins>
245template <typename recv_value_type_tparam /* = kamping::internal::unused_tparam */, typename... Args>
247 //! If you expand this function to not being only a simple wrapper around bcast, you have to write more unit tests!
248
249 using namespace kamping::internal;
250
251 // In contrast to bcast(...), send_recv_count is not a possible parameter.
253
254 // Get the root PE
256 std::tuple(this->root()),
257 args...
258 );
259 // we have to do this check with communication, because otherwise the other ranks would already start with the
260 // broadcast and indefinitely wait for the root
261 if constexpr (kassert::internal::assertion_enabled(assert::light_communication)) {
262 bool root_has_buffer = has_parameter_type<internal::ParameterType::send_recv_buf, Args...>();
263 int err = MPI_Bcast(&root_has_buffer, 1, MPI_CXX_BOOL, root.rank_signed(), this->mpi_communicator());
264 this->mpi_error_hook(err, "MPI_Bcast");
265 KASSERT(root_has_buffer, "send_recv_buf must be provided on the root rank.", assert::light_communication);
266 }
267
268 if constexpr (has_parameter_type<ParameterType::send_recv_buf, Args...>()) {
269 using send_recv_buf_type = buffer_type_with_requested_parameter_type<ParameterType::send_recv_buf, Args...>;
270 static_assert(
271 send_recv_buf_type::is_single_element,
272 "The underlying container has to be a single element \"container\""
273 );
274 return this->bcast<recv_value_type_tparam>(std::forward<Args>(args)..., send_recv_count(1));
275 } else {
276 return *this->bcast<recv_value_type_tparam>(std::forward<Args>(args)..., send_recv_count(1)).data();
277 }
278}
279/// @}
Helper functions that make casts safer.
STL-compatible allocator for requesting memory using the builtin MPI allocator.
Definition allocator.hpp:32
T value_type
The value type.
Definition allocator.hpp:53
constexpr int light
Assertion level for lightweight assertions.
Definition assertion_levels.hpp:13
constexpr int light_communication
Assertions that perform lightweight communication.
Definition assertion_levels.hpp:25
auto bcast(Args... args) const
Wrapper for MPI_Bcast.
Definition bcast.hpp:75
auto bcast_single(Args... args) const
Wrapper for MPI_Bcast.
Definition bcast.hpp:246
MPI_Datatype mpi_datatype() KAMPING_NOEXCEPT
Translate template parameter T to an MPI_Datatype. If no corresponding MPI_Datatype exists,...
Definition mpi_datatype.hpp:294
static constexpr auto alloc_new
Convenience wrapper for creating library allocated containers. See AllocNewT for details.
Definition data_buffer.hpp:194
auto root(int rank)
Passes rank as root rank to the underlying call. This parameter is needed in functions like MPI_Gathe...
Definition named_parameters.hpp:979
auto send_recv_buf(Data &&data)
Passes a container/single value as a send or receive buffer to the underlying MPI call.
Definition named_parameters.hpp:137
auto send_recv_count(int count)
Passes count as send/recv count to the underlying call.
Definition named_parameters.hpp:529
auto send_recv_type(MPI_Datatype send_recv_type)
Passes send_recv_type as send/recv type to the underlying call. (This parameter is in MPI routines su...
Definition named_parameters.hpp:1282
auto send_recv_count_out()
Indicates to deduce the send/recv count and return it to the caller as part of the underlying call's ...
Definition named_parameters.hpp:555
std:: tuple_element_t< find_pos< std::integral_constant< ParameterType, parameter_type >, 0, Args... >(), std::tuple< Args... > > buffer_type_with_requested_parameter_type
Type of Buffer with requested.
Definition named_parameter_selection.hpp:176
Utility that maps C++ types to types that can be understood by MPI.
Template magic to check named parameters passed to wrappers at compile time.
#define KAMPING_REQUIRED_PARAMETERS(...)
Wrapper to pass (possibly empty) list of parameter type names as required parameters to KAMPING_CHECK...
Definition named_parameter_check.hpp:52
#define KAMPING_OPTIONAL_PARAMETERS(...)
Wrapper to pass (possibly empty) list of parameter type names as optional parameters to KAMPING_CHECK...
Definition named_parameter_check.hpp:58
#define KAMPING_CHECK_PARAMETERS(args, required, optional)
Assertion macro that checks if passed parameters are correct, i.e., all parameter types are unique,...
Definition named_parameter_check.hpp:80
Template magic to implement named parameters in cpp.
File containing the parameter types used by the KaMPIng library.
Factory methods for buffer wrappers.
Internal namespace marking the code that is not user-facing.
Definition collectives_helpers.hpp:20
static constexpr bool buffer_uses_serialization
Checks if DataBufferType is a serialization buffer.
Definition named_parameter_check.hpp:415
Some functions and types simplifying/enabling the development of wrapped MPI calls in KaMPIng.