KaMPIng 0.1.2
Flexible and (near) zero-overhead C++ bindings for MPI
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())) {
259 std::tuple<>(),
260 args...
261 );
263 has_parameter_type<internal::ParameterType::send_buf, Args...>() && send_buf_builder.size() == size();
264 KASSERT(
266 "send_buf of size equal to comm.size() must be provided on the root rank.",
268 );
269 }
270 }
271
272 if constexpr (has_parameter_type<ParameterType::send_buf, Args...>()) {
273 using send_recv_buf_type = buffer_type_with_requested_parameter_type<ParameterType::send_buf, Args...>;
274 using value_type = typename send_recv_buf_type::value_type;
275 return this->scatter(recv_buf(alloc_new<value_type>), std::forward<Args>(args)..., recv_count(1));
276 } else {
277 return this->scatter(recv_buf(alloc_new<recv_value_type_tparam>), std::forward<Args>(args)..., recv_count(1));
278 }
279}
280
281/// @brief Wrapper for \c MPI_Scatterv.
282///
283/// This wrapper for \c MPI_Scatterv distributes data on the root PE across all PEs in the current communicator.
284///
285/// The following parameters are mandatory on the root rank:
286/// - \ref kamping::send_buf() [on all PEs] containing the data to be distributed across all PEs. Non-root PEs can omit
287/// a send buffer by passing `kamping::ignore<T>` as a parameter, or `T` as a template parameter to \ref
288/// kamping::send_buf().
289///
290/// - \ref kamping::send_counts() [on root PE] specifying the number of elements to send to each PE.
291///
292/// The following parameter can be omitted at the cost of communication overhead (1x MPI_Scatter)
293/// - \ref kamping::recv_count() [on all PEs] specifying the number of elements sent to each PE. If this parameter is
294/// omitted, the number of elements sent to each PE is computed based on kamping::send_counts() provided on the
295/// root PE. This parameter is mandatory if \ref kamping::recv_type() is given.
296///
297/// The following parameter can be omitted at the cost of computational overhead:
298/// - \ref kamping::send_displs() [on root PE] specifying the data displacements in the send buffer. If omitted, an
299/// exclusive prefix sum of the send_counts is used.
300///
301/// The following parameters are optional:
302/// - \ref kamping::send_type() specifying the \c MPI datatype to use as send type. If omitted, the \c MPI datatype is
303/// derived automatically based on send_buf's underlying \c value_type. This parameter is ignored on non-root ranks.
304///
305/// - \ref kamping::recv_buf() [on all PEs] containing the received data. The buffer will be resized according to the
306/// buffer's kamping::BufferResizePolicy. If this is kamping::BufferResizePolicy::no_resize, the buffer's underlying
307/// storage must be large enough to hold all received elements.
308///
309/// - \ref kamping::recv_type() specifying the \c MPI datatype to use as recv type. If omitted, the \c MPI datatype is
310/// derived automatically based on recv_buf's underlying \c value_type.
311///
312/// - \ref kamping::root() [on all PEs] specifying the rank of the root PE. If omitted, the default root PE of the
313/// communicator is used instead.
314///
315/// @tparam recv_value_type_tparam The type that is received. Only required when no kamping::send_buf() and no
316/// kamping::recv_buf() is given.
317/// /// @tparam Args Automatically deduced template parameters.
318/// @param args All required and any number of the optional parameters described above.
319/// @return Result object wrapping the output parameters to be returned by value.
320///
321/// @see \ref docs/parameter_handling.md for general information about parameter handling in KaMPIng.
322/// <hr>
323/// \include{doc} docs/resize_policy.dox
324template <
325 template <typename...>
326 typename DefaultContainerType,
327 template <typename, template <typename...> typename>
328 typename... Plugins>
329template <typename recv_value_type_tparam /* = kamping::internal::unused_tparam */, typename... Args>
331 using namespace kamping::internal;
332
334 Args,
337 send_buf,
338 root,
341 send_type,
342 recv_buf,
345 )
346 );
347
348 // Optional parameter: root()
349 // Default: communicator root
350 using root_param_type = decltype(kamping::root(0));
351 auto&& root_param =
353 int const root_val = root_param.rank_signed();
354 KASSERT(
355 is_valid_rank(root_val),
356 "Invalid root rank " << root_val << " in communicator of size " << size(),
358 );
359 KASSERT(is_same_on_all_ranks(root_val), "Root has to be the same on all ranks.", assert::light_communication);
360
361 // Parameter send_buf()
363 auto send_buf =
365 .construct_buffer_or_rebind();
366 using send_value_type = typename std::remove_reference_t<decltype(send_buf)>::value_type;
367 auto const* send_buf_ptr = send_buf.data();
368 KASSERT(!is_root(root_val) || send_buf_ptr != nullptr, "Send buffer must be specified on root.", assert::light);
369
370 // Optional parameter: recv_buf()
371 // Default: allocate new container
373 auto recv_buf =
374 internal::select_parameter_type_or_default<internal::ParameterType::recv_buf, default_recv_buf_type>(
375 std::tuple<>(),
376 args...
377 )
379 using recv_value_type = typename std::remove_reference_t<decltype(recv_buf)>::value_type;
380
381 static_assert(
382 !std::is_same_v<recv_value_type, internal::unused_tparam>,
383 "No send_buf or recv_buf parameter provided and no receive value given as template parameter. One of these is "
384 "required."
385 );
386
387 // Get send_type and recv_type
388 auto [send_type, recv_type] =
389 internal::determine_mpi_datatypes<send_value_type, recv_value_type, decltype(recv_buf)>(args...);
390 [[maybe_unused]] constexpr bool recv_type_is_in_param = !has_to_be_computed<decltype(recv_type)>;
391
392 // Get send counts
394 auto send_counts =
397 [[maybe_unused]] constexpr bool send_counts_provided = !has_to_be_computed<decltype(send_counts)>;
398 KASSERT(
399 !is_root(root_val) || send_counts_provided,
400 "send_counts() must be given on the root PE.",
402 );
403 KASSERT(
404 !is_root(root_val) || send_counts.size() >= size(),
405 "Send counts buffer is smaller than the number of PEs at the root PE.",
407 );
408
409 // Get send displacements
411 auto send_displs =
414
415 if (is_root(root_val)) {
416 // send displacements are only considered on the root PE and ignored by MPI on all non-root PEs.
417 constexpr bool do_compute_send_displs = has_to_be_computed<decltype(send_displs)>;
418 if constexpr (do_compute_send_displs) {
419 send_displs.resize_if_requested([&]() { return this->size(); });
420 }
421 KASSERT(
422 send_displs.size() >= size(),
423 "Send displs buffer is smaller than the number of PEs at the root PE.",
425 );
426
427 if constexpr (do_compute_send_displs) {
428 std::exclusive_scan(send_counts.data(), send_counts.data() + size(), send_displs.data(), 0);
429 }
430 }
431
432 // Get recv counts
433 using default_recv_count_type = decltype(recv_count_out());
434 auto recv_count =
436 .construct_buffer_or_rebind();
437
438 // Check that recv_counts() can be used to compute send_counts(); or send_counts() is given on the root PE
439 [[maybe_unused]] constexpr bool do_compute_recv_count = has_to_be_computed<decltype(recv_count)>;
440 KASSERT(
441 this->is_same_on_all_ranks(do_compute_recv_count),
442 "recv_counts() must be given on all PEs or on no PEs",
444 );
445
446 if constexpr (do_compute_recv_count) {
447 scatter(
448 kamping::send_buf(send_counts.underlying()),
451 kamping::recv_buf(recv_count.underlying())
452 );
453 }
454
455 auto compute_required_recv_buf_size = [&]() {
456 return static_cast<size_t>(recv_count.get_single_element());
457 };
458 recv_buf.resize_if_requested(compute_required_recv_buf_size);
459 KASSERT(
460 // if the recv type is user provided, kamping cannot make any assumptions about the required size of
461 // the recv buffer
463 "Recv buffer is not large enough to hold all received elements.",
465 );
466
467 [[maybe_unused]] int const err = MPI_Scatterv(
468 send_buf_ptr, // send buffer
469 send_counts.data(), // send counts
470 send_displs.data(), // send displs
471 send_type.get_single_element(), // send type
472 recv_buf.data(), // recv buffer
473 recv_count.get_single_element(), // recv count
474 recv_type.get_single_element(), // recv type
475 root_val, // root
476 mpi_communicator() // communicator
477 );
478 this->mpi_error_hook(err, "MPI_Scatterv");
479
480 return make_mpi_result<std::tuple<Args...>>(
481 std::move(recv_buf),
482 std::move(recv_count),
483 std::move(send_counts),
484 std::move(send_displs),
485 std::move(send_type),
486 std::move(recv_type)
487 );
488}
489/// @}
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:330
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:194
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:576
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:981
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:205
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:139
auto send_count(int count)
Passes count as send count to the underlying call.
Definition named_parameters.hpp:323
auto send_displs_out()
Indicates to construct a container with type kamping::Communicator::default_container_type<int>,...
Definition named_parameters.hpp:681
auto recv_count(int count)
Passes count as recv count to the underlying call.
Definition named_parameters.hpp:492
auto send_counts_out()
Indicates to construct a container with type kamping::Communicator::default_container_type<int>,...
Definition named_parameters.hpp:311
auto recv_buf(Container &&container)
Passes a container, into which the received elements will be written, to the underlying call....
Definition named_parameters.hpp:861
auto recv_type(MPI_Datatype recv_type)
Passes recv_type as recv type to the underlying call.
Definition named_parameters.hpp:1302
auto send_type(MPI_Datatype send_type)
Passes send_type as send type to the underlying call.
Definition named_parameters.hpp:1259
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:349
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:518
auto send_buf(internal::ignore_t< Data > ignore)
Generates a dummy send buf that wraps a nullptr.
Definition named_parameters.hpp:53
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.