KaMPIng 0.1.0
(Near) zero-overhead C++ MPI bindings.
Loading...
Searching...
No Matches
scatter.hpp
1// This file is part of KaMPIng
2//
3// Copyright 2022-2023 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 <cstddef>
17#include <numeric>
18#include <type_traits>
19
20#include <kassert/kassert.hpp>
21#include <mpi.h>
22
23#include "kamping/assertion_levels.hpp"
25#include "kamping/collectives/bcast.hpp"
26#include "kamping/collectives/gather.hpp"
27#include "kamping/communicator.hpp"
35#include "kamping/result.hpp"
36
37//// @addtogroup kamping_collectives
38/// @{
39
40// @brief Wrapper for \c MPI_Scatter.
41///
42/// This wrapper for \c MPI_Scatter distributes data on the root PE evenly across all PEs in the current
43/// communicator.
44///
45/// The following parameters are mandatory on the root rank:
46/// - kamping::send_buf() containing the data to be evenly distributed across all PEs. The size of
47/// this buffer must be divisible by the number of PEs in the current communicator. Non-root PEs can omit a send
48/// buffer by passing `kamping::ignore<T>` as a parameter, or `T` as a template parameter to \ref kamping::send_buf().
49///
50/// The following parameters are optional but incur communication overhead if omitted:
51/// - kamping::recv_count() specifying the number of elements sent to each PE. If this parameter is omitted,
52/// the number of elements sent to each PE is computed based on the size of the \ref kamping::send_buf() on the root
53/// PE and broadcasted to other PEs.
54///
55/// The following parameters are optional:
56/// - kamping::send_count() specifying how many elements are sent to each process.
57/// If omitted, the size of send buffer divided by communicator size is used. This parameter is mandatory if \ref
58/// kamping::send_type() is given.
59///
60/// - \ref kamping::send_type() specifying the \c MPI datatype to use as send type. If omitted, the \c MPI datatype is
61/// derived automatically based on send_buf's underlying \c value_type. This parameter is ignored on non-root ranks.
62///
63/// - kamping::recv_buf() containing the received data.
64///
65/// - \ref kamping::recv_type() specifying the \c MPI datatype to use as recv type. If omitted, the \c MPI datatype is
66/// derived automatically based on recv_buf's underlying \c value_type.
67///
68/// - kamping::root() specifying the rank of the root PE. If omitted, the default root PE of the communicator
69/// is used instead.
70///
71/// @tparam recv_value_type_tparam The type that is received. Only required when no \ref kamping::send_buf() and no \ref
72/// kamping::recv_buf() is given.
73/// @tparam Args Automatically deduced template parameters.
74/// @param args All required and any number of the optional parameters described above.
75/// @return Result object wrapping the output parameters to be returned by value.
76///
77/// @see \ref docs/parameter_handling.md for general information about parameter handling in KaMPIng.
78/// <hr>
79/// \include{doc} docs/resize_policy.dox
80template <
81 template <typename...>
82 typename DefaultContainerType,
83 template <typename, template <typename...> typename>
84 typename... Plugins>
85template <typename recv_value_type_tparam /* = kamping::internal::unused_tparam */, typename... Args>
87 using namespace kamping::internal;
88
90 Args,
93 );
94
95 // Optional parameter: root()
96 // Default: communicator root
97 using root_param_type = decltype(kamping::root(0));
98 auto&& root_param =
100 int const int_root = root_param.rank_signed();
101 KASSERT(
102 is_valid_rank(int_root),
103 "Invalid root rank " << int_root << " in communicator of size " << size(),
105 );
106 KASSERT(this->is_same_on_all_ranks(int_root), "Root has to be the same on all ranks.", assert::light_communication);
107
108 // Parameter send_buf()
110 auto send_buf =
112 .construct_buffer_or_rebind();
113 using send_value_type = typename std::remove_reference_t<decltype(send_buf)>::value_type;
114 KASSERT(!is_root(int_root) || send_buf.data() != nullptr, "Send buffer must be specified on root.", assert::light);
115
116 // Optional parameter: recv_buf()
117 // Default: allocate new container
119 auto&& recv_buf =
120 internal::select_parameter_type_or_default<internal::ParameterType::recv_buf, default_recv_buf_type>(
121 std::tuple(),
122 args...
123 )
125 using recv_value_type = typename std::remove_reference_t<decltype(recv_buf)>::value_type;
126
127 static_assert(
128 !std::is_same_v<recv_value_type, internal::unused_tparam>,
129 "No send_buf or recv_buf parameter provided and no receive value given as template parameter. One of these is "
130 "required."
131 );
132
133 // Get send_type and recv_type
134 auto&& [send_type, recv_type] =
135 internal::determine_mpi_datatypes<send_value_type, recv_value_type, decltype(recv_buf)>(args...);
136 KASSERT(
137 !is_root(int_root) || send_type.underlying() != MPI_DATATYPE_NULL,
138 "Send type must be specified on root.",
140 );
141 [[maybe_unused]] constexpr bool recv_type_is_in_param = !has_to_be_computed<decltype(recv_type)>;
142
143 // Compute sendcount based on the size of the sendbuf
145 auto&& send_count =
146 internal::select_parameter_type_or_default<internal::ParameterType::send_count, default_send_count_type>(
147 std::tuple(),
148 args...
149 )
150 .construct_buffer_or_rebind();
152 if constexpr (do_compute_send_count) {
153 if (is_root(int_root)) {
154 KASSERT(
155 send_buf.size() % size() == 0u,
156 "No send count is given and the size of the send buffer ("
157 << send_buf.size() << ") at the root is not divisible by the number of PEs (" << size()
158 << ") in the communicator.",
160 );
161 send_count.underlying() = asserting_cast<int>(send_buf.size() / size());
162 }
163 }
164
165 // Optional parameter: recv_count()
166 // Default: compute value based on send_buf.size on root
168 auto&& recv_count =
169 internal::select_parameter_type_or_default<internal::ParameterType::recv_count, default_recv_count_type>(
170 std::tuple(),
171 args...
172 )
173 .construct_buffer_or_rebind();
174 constexpr bool do_compute_recv_count = has_to_be_computed<decltype(recv_count)>;
175
176 KASSERT(
177 is_same_on_all_ranks(do_compute_recv_count),
178 "recv_count() parameter is an output parameter on some PEs, but not on alle PEs.",
180 );
181
182 // If it is an output parameter, broadcast send_count to get recv_count
183 if constexpr (do_compute_recv_count) {
184 recv_count.underlying() = send_count.get_single_element();
185 this->bcast_single(send_recv_buf(recv_count.underlying()), kamping::root(int_root));
186 }
187
188 auto compute_required_recv_buf_size = [&]() {
189 return asserting_cast<size_t>(recv_count.get_single_element());
190 };
191 recv_buf.resize_if_requested(compute_required_recv_buf_size);
192 KASSERT(
193 // if the recv type is user provided, kamping cannot make any assumptions about the required size of
194 // the recv buffer
196 "Recv buffer is not large enough to hold all received elements.",
198 );
199
200 [[maybe_unused]] int const err = MPI_Scatter(
201 send_buf.data(),
202 send_count.get_single_element(),
203 send_type.get_single_element(),
204 recv_buf.data(),
205 recv_count.get_single_element(),
206 recv_type.get_single_element(),
207 int_root,
208 mpi_communicator()
209 );
210 this->mpi_error_hook(err, "MPI_Scatter");
211
212 return make_mpi_result<std::tuple<Args...>>(
213 std::move(recv_buf),
214 std::move(send_count),
215 std::move(recv_count),
216 std::move(send_type),
217 std::move(recv_type)
218 );
219}
220
221/// Calling scatter_single() is a shorthand for calling scatter() with a \ref kamping::send_buf() with the same size as
222/// the communicator.
223///
224/// The following parameters are required on the root rank:
225/// - \ref kamping::send_buf() containing the data that is sent to each rank. This buffer has to have the same size as
226/// the communicator on the root rank.
227///
228/// The following parameters are optional:
229/// - kamping::root() specifying the rank of the root PE. If omitted, the default root PE of the communicator
230/// is used instead.
231///
232/// @tparam recv_value_type_tparam The type that is received.
233/// @tparam Args Automatically deducted template parameters.
234/// @param args All required and any number of the optional buffers described above.
235/// @return The single output value.
236template <
237 template <typename...>
238 typename DefaultContainerType,
239 template <typename, template <typename...> typename>
240 typename... Plugins>
241template <typename recv_value_type_tparam /* = kamping::internal::unused_tparam */, typename... Args>
243 using namespace kamping::internal;
244
245 // In contrast to bcast(...), send_recv_count is not a possible parameter.
247
248 // Get the root PE
250 std::tuple(this->root()),
251 args...
252 );
253 // we have to do this check with communication, because otherwise the other ranks would already start with the
254 // broadcast and indefinitely wait for the root
255 if constexpr (kassert::internal::assertion_enabled(assert::light)) {
256 if (is_root(root.rank_signed())) {
258 auto&& send_buf_builder =
261 has_parameter_type<internal::ParameterType::send_buf, Args...>() && send_buf_builder.size() == size();
262 KASSERT(
264 "send_buf of size equal to comm.size() must be provided on the root rank.",
266 );
267 }
268 }
269
270 if constexpr (has_parameter_type<ParameterType::send_buf, Args...>()) {
271 using send_recv_buf_type = buffer_type_with_requested_parameter_type<ParameterType::send_buf, Args...>;
272 using value_type = typename send_recv_buf_type::value_type;
273 return this->scatter(recv_buf(alloc_new<value_type>), std::forward<Args>(args)..., recv_count(1));
274 } else {
275 return this->scatter(recv_buf(alloc_new<recv_value_type_tparam>), std::forward<Args>(args)..., recv_count(1));
276 }
277}
278
279/// @brief Wrapper for \c MPI_Scatterv.
280///
281/// This wrapper for \c MPI_Scatterv distributes data on the root PE across all PEs in the current communicator.
282///
283/// The following parameters are mandatory on the root rank:
284/// - \ref kamping::send_buf() [on all PEs] containing the data to be distributed across all PEs. Non-root PEs can omit
285/// a send buffer by passing `kamping::ignore<T>` as a parameter, or `T` as a template parameter to \ref
286/// kamping::send_buf().
287///
288/// - \ref kamping::send_counts() [on root PE] specifying the number of elements to send to each PE.
289///
290/// The following parameter can be omitted at the cost of communication overhead (1x MPI_Scatter)
291/// - \ref kamping::recv_count() [on all PEs] specifying the number of elements sent to each PE. If this parameter is
292/// omitted, the number of elements sent to each PE is computed based on kamping::send_counts() provided on the
293/// root PE. This parameter is mandatory if \ref kamping::recv_type() is given.
294///
295/// The following parameter can be omitted at the cost of computational overhead:
296/// - \ref kamping::send_displs() [on root PE] specifying the data displacements in the send buffer. If omitted, an
297/// exclusive prefix sum of the send_counts is used.
298///
299/// The following parameters are optional:
300/// - \ref kamping::send_type() specifying the \c MPI datatype to use as send type. If omitted, the \c MPI datatype is
301/// derived automatically based on send_buf's underlying \c value_type. This parameter is ignored on non-root ranks.
302///
303/// - \ref kamping::recv_buf() [on all PEs] containing the received data. The buffer will be resized according to the
304/// buffer's kamping::BufferResizePolicy. If this is kamping::BufferResizePolicy::no_resize, the buffer's underlying
305/// storage must be large enough to hold all received elements.
306///
307/// - \ref kamping::recv_type() specifying the \c MPI datatype to use as recv type. If omitted, the \c MPI datatype is
308/// derived automatically based on recv_buf's underlying \c value_type.
309///
310/// - \ref kamping::root() [on all PEs] specifying the rank of the root PE. If omitted, the default root PE of the
311/// communicator is used instead.
312///
313/// @tparam recv_value_type_tparam The type that is received. Only required when no kamping::send_buf() and no
314/// kamping::recv_buf() is given.
315/// /// @tparam Args Automatically deduced template parameters.
316/// @param args All required and any number of the optional parameters described above.
317/// @return Result object wrapping the output parameters to be returned by value.
318///
319/// @see \ref docs/parameter_handling.md for general information about parameter handling in KaMPIng.
320/// <hr>
321/// \include{doc} docs/resize_policy.dox
322template <
323 template <typename...>
324 typename DefaultContainerType,
325 template <typename, template <typename...> typename>
326 typename... Plugins>
327template <typename recv_value_type_tparam /* = kamping::internal::unused_tparam */, typename... Args>
329 using namespace kamping::internal;
330
332 Args,
335 send_buf,
336 root,
339 send_type,
340 recv_buf,
343 )
344 );
345
346 // Optional parameter: root()
347 // Default: communicator root
348 using root_param_type = decltype(kamping::root(0));
349 auto&& root_param =
351 int const root_val = root_param.rank_signed();
352 KASSERT(
353 is_valid_rank(root_val),
354 "Invalid root rank " << root_val << " in communicator of size " << size(),
356 );
357 KASSERT(is_same_on_all_ranks(root_val), "Root has to be the same on all ranks.", assert::light_communication);
358
359 // Parameter send_buf()
361 auto send_buf =
363 .construct_buffer_or_rebind();
364 using send_value_type = typename std::remove_reference_t<decltype(send_buf)>::value_type;
365 auto const* send_buf_ptr = send_buf.data();
366 KASSERT(!is_root(root_val) || send_buf_ptr != nullptr, "Send buffer must be specified on root.", assert::light);
367
368 // Optional parameter: recv_buf()
369 // Default: allocate new container
371 auto&& recv_buf =
372 internal::select_parameter_type_or_default<internal::ParameterType::recv_buf, default_recv_buf_type>(
373 std::tuple(),
374 args...
375 )
377 using recv_value_type = typename std::remove_reference_t<decltype(recv_buf)>::value_type;
378
379 static_assert(
380 !std::is_same_v<recv_value_type, internal::unused_tparam>,
381 "No send_buf or recv_buf parameter provided and no receive value given as template parameter. One of these is "
382 "required."
383 );
384
385 // Get send_type and recv_type
386 auto&& [send_type, recv_type] =
387 internal::determine_mpi_datatypes<send_value_type, recv_value_type, decltype(recv_buf)>(args...);
388 [[maybe_unused]] constexpr bool recv_type_is_in_param = !has_to_be_computed<decltype(recv_type)>;
389
390 // Get send counts
392 auto&& send_counts =
395 [[maybe_unused]] constexpr bool send_counts_provided = !has_to_be_computed<decltype(send_counts)>;
396 KASSERT(
397 !is_root(root_val) || send_counts_provided,
398 "send_counts() must be given on the root PE.",
400 );
401 KASSERT(
402 !is_root(root_val) || send_counts.size() >= size(),
403 "Send counts buffer is smaller than the number of PEs at the root PE.",
405 );
406
407 // Get send displacements
409 auto&& send_displs =
412
413 if (is_root(root_val)) {
414 // send displacements are only considered on the root PE and ignored by MPI on all non-root PEs.
415 constexpr bool do_compute_send_displs = has_to_be_computed<decltype(send_displs)>;
416 if constexpr (do_compute_send_displs) {
417 send_displs.resize_if_requested([&]() { return this->size(); });
418 }
419 KASSERT(
420 send_displs.size() >= size(),
421 "Send displs buffer is smaller than the number of PEs at the root PE.",
423 );
424
425 if constexpr (do_compute_send_displs) {
426 std::exclusive_scan(send_counts.data(), send_counts.data() + size(), send_displs.data(), 0);
427 }
428 }
429
430 // Get recv counts
431 using default_recv_count_type = decltype(recv_count_out());
432 auto&& recv_count =
434 .construct_buffer_or_rebind();
435
436 // Check that recv_counts() can be used to compute send_counts(); or send_counts() is given on the root PE
437 [[maybe_unused]] constexpr bool do_compute_recv_count = has_to_be_computed<decltype(recv_count)>;
438 KASSERT(
439 this->is_same_on_all_ranks(do_compute_recv_count),
440 "recv_counts() must be given on all PEs or on no PEs",
442 );
443
444 if constexpr (do_compute_recv_count) {
445 scatter(
446 kamping::send_buf(send_counts.underlying()),
449 kamping::recv_buf(recv_count.underlying())
450 );
451 }
452
453 auto compute_required_recv_buf_size = [&]() {
454 return static_cast<size_t>(recv_count.get_single_element());
455 };
456 recv_buf.resize_if_requested(compute_required_recv_buf_size);
457 KASSERT(
458 // if the recv type is user provided, kamping cannot make any assumptions about the required size of
459 // the recv buffer
461 "Recv buffer is not large enough to hold all received elements.",
463 );
464
465 [[maybe_unused]] int const err = MPI_Scatterv(
466 send_buf_ptr, // send buffer
467 send_counts.data(), // send counts
468 send_displs.data(), // send displs
469 send_type.get_single_element(), // send type
470 recv_buf.data(), // recv buffer
471 recv_count.get_single_element(), // recv count
472 recv_type.get_single_element(), // recv type
473 root_val, // root
474 mpi_communicator() // communicator
475 );
476 this->mpi_error_hook(err, "MPI_Scatterv");
477
478 return make_mpi_result<std::tuple<Args...>>(
479 std::move(recv_buf),
480 std::move(recv_count),
481 std::move(send_counts),
482 std::move(send_displs),
483 std::move(send_type),
484 std::move(recv_type)
485 );
486}
487/// @}
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
Code for error handling.
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 scatter(Args... args) const
Definition scatter.hpp:86
auto scatterv(Args... args) const
Wrapper for MPI_Scatterv.
Definition scatter.hpp:328
auto scatter_single(Args... args) const
Definition scatter.hpp:242
static constexpr auto alloc_new
Convenience wrapper for creating library allocated containers. See AllocNewT for details.
Definition data_buffer.hpp:170
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_count(int count)
Passes count as send count to the underlying call.
Definition named_parameters.hpp:321
auto send_type(MPI_Datatype send_type)
Passes send_type as send type to the underlying call.
Definition named_parameters.hpp:1195
auto recv_buf(Container &&container)
Passes a container, into which the received elements will be written, to the underlying call....
Definition named_parameters.hpp:859
auto send_buf(internal::ignore_t< Data > ignore)
Generates a dummy send buf that wraps a nullptr.
Definition named_parameters.hpp:51
auto send_counts_out()
Indicates to construct a container with type kamping::Communicator::default_container_type<int>,...
Definition named_parameters.hpp:309
auto send_count_out()
Indicates to deduce the send count and return it to the caller as part of the underlying call's resul...
Definition named_parameters.hpp:347
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_counts(Container &&container)
Passes a container as send counts to the underlying call, i.e. the container's storage must contain t...
Definition named_parameters.hpp:203
auto recv_count(int count)
Passes count as recv count to the underlying call.
Definition named_parameters.hpp:490
auto send_displs_out()
Indicates to construct a container with type kamping::Communicator::default_container_type<int>,...
Definition named_parameters.hpp:679
auto recv_type(MPI_Datatype recv_type)
Passes recv_type as recv type to the underlying call.
Definition named_parameters.hpp:1238
auto send_displs(Container &&container)
Passes a container as send displacements to the underlying call, i.e. the container's storage must co...
Definition named_parameters.hpp:574
auto recv_count_out()
Indicates to deduce the recv count and return it to the caller as part of the underlying call's resul...
Definition named_parameters.hpp:516
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 has_to_be_computed
Checks if the buffer has to be computed by kamping, i.e. if it is an output parameter or the buffer h...
Definition named_parameter_check.hpp:398
Some functions and types simplifying/enabling the development of wrapped MPI calls in KaMPIng.