KaMPIng 0.2.0
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 <mpi.h>
21
22#include "kamping/assertion_levels.hpp"
24#include "kamping/collectives/bcast.hpp"
25#include "kamping/collectives/gather.hpp"
26#include "kamping/communicator.hpp"
29#include "kamping/kassert/kassert.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();
102 is_valid_rank(int_root),
103 "Invalid root rank " << int_root << " in communicator of size " << size(),
105 );
107 this->is_same_on_all_ranks(int_root),
108 "Root has to be the same on all ranks.",
110 );
111
112 // Parameter send_buf()
114 auto send_buf =
116 .construct_buffer_or_rebind();
117 using send_value_type = typename std::remove_reference_t<decltype(send_buf)>::value_type;
119 !is_root(int_root) || send_buf.data() != nullptr,
120 "Send buffer must be specified on root.",
122 );
123
124 // Optional parameter: recv_buf()
125 // Default: allocate new container
127 auto recv_buf =
128 internal::select_parameter_type_or_default<internal::ParameterType::recv_buf, default_recv_buf_type>(
129 std::tuple<>(),
130 args...
131 )
133 using recv_value_type = typename std::remove_reference_t<decltype(recv_buf)>::value_type;
134
135 static_assert(
136 !std::is_same_v<recv_value_type, internal::unused_tparam>,
137 "No send_buf or recv_buf parameter provided and no receive value given as template parameter. One of these is "
138 "required."
139 );
140
141 // Get send_type and recv_type
142 auto [send_type, recv_type] =
143 internal::determine_mpi_datatypes<send_value_type, recv_value_type, decltype(recv_buf)>(args...);
145 !is_root(int_root) || send_type.underlying() != MPI_DATATYPE_NULL,
146 "Send type must be specified on root.",
148 );
149 [[maybe_unused]] constexpr bool recv_type_is_in_param = !has_to_be_computed<decltype(recv_type)>;
150
151 // Compute sendcount based on the size of the sendbuf
153 auto send_count =
154 internal::select_parameter_type_or_default<internal::ParameterType::send_count, default_send_count_type>(
155 std::tuple<>(),
156 args...
157 )
158 .construct_buffer_or_rebind();
160 if constexpr (do_compute_send_count) {
161 if (is_root(int_root)) {
163 send_buf.size() % size() == 0u,
164 "No send count is given and the size of the send buffer ("
165 << send_buf.size() << ") at the root is not divisible by the number of PEs (" << size()
166 << ") in the communicator.",
168 );
169 send_count.underlying() = asserting_cast<int>(send_buf.size() / size());
170 }
171 }
172
173 // Optional parameter: recv_count()
174 // Default: compute value based on send_buf.size on root
176 auto recv_count =
177 internal::select_parameter_type_or_default<internal::ParameterType::recv_count, default_recv_count_type>(
178 std::tuple<>(),
179 args...
180 )
181 .construct_buffer_or_rebind();
182 constexpr bool do_compute_recv_count = has_to_be_computed<decltype(recv_count)>;
183
185 is_same_on_all_ranks(do_compute_recv_count),
186 "recv_count() parameter is an output parameter on some PEs, but not on alle PEs.",
188 );
189
190 // If it is an output parameter, broadcast send_count to get recv_count
191 if constexpr (do_compute_recv_count) {
192 recv_count.underlying() = send_count.get_single_element();
193 this->bcast_single(send_recv_buf(recv_count.underlying()), kamping::root(int_root));
194 }
195
196 auto compute_required_recv_buf_size = [&]() {
197 return asserting_cast<size_t>(recv_count.get_single_element());
198 };
199 recv_buf.resize_if_requested(compute_required_recv_buf_size);
201 // if the recv type is user provided, kamping cannot make any assumptions about the required size of
202 // the recv buffer
204 "Recv buffer is not large enough to hold all received elements.",
206 );
207
208 [[maybe_unused]] int const err = MPI_Scatter(
209 send_buf.data(),
210 send_count.get_single_element(),
211 send_type.get_single_element(),
212 recv_buf.data(),
213 recv_count.get_single_element(),
214 recv_type.get_single_element(),
215 int_root,
216 mpi_communicator()
217 );
218 this->mpi_error_hook(err, "MPI_Scatter");
219
220 return make_mpi_result<std::tuple<Args...>>(
221 std::move(recv_buf),
222 std::move(send_count),
223 std::move(recv_count),
224 std::move(send_type),
225 std::move(recv_type)
226 );
227}
228
229/// Calling scatter_single() is a shorthand for calling scatter() with a \ref kamping::send_buf() with the same size as
230/// the communicator.
231///
232/// The following parameters are required on the root rank:
233/// - \ref kamping::send_buf() containing the data that is sent to each rank. This buffer has to have the same size as
234/// the communicator on the root rank.
235///
236/// The following parameters are optional:
237/// - kamping::root() specifying the rank of the root PE. If omitted, the default root PE of the communicator
238/// is used instead.
239///
240/// @tparam recv_value_type_tparam The type that is received.
241/// @tparam Args Automatically deducted template parameters.
242/// @param args All required and any number of the optional buffers described above.
243/// @return The single output value.
244template <
245 template <typename...>
246 typename DefaultContainerType,
247 template <typename, template <typename...> typename>
248 typename... Plugins>
249template <typename recv_value_type_tparam /* = kamping::internal::unused_tparam */, typename... Args>
251 using namespace kamping::internal;
252
253 // In contrast to bcast(...), send_recv_count is not a possible parameter.
255
256 // Get the root PE
258 std::tuple(this->root()),
259 args...
260 );
261 // we have to do this check with communication, because otherwise the other ranks would already start with the
262 // broadcast and indefinitely wait for the root
263 if constexpr (kassert::internal::assertion_enabled(assert::light)) {
264 if (is_root(root.rank_signed())) {
267 std::tuple<>(),
268 args...
269 );
271 has_parameter_type<internal::ParameterType::send_buf, Args...>() && send_buf_builder.size() == size();
274 "send_buf of size equal to comm.size() must be provided on the root rank.",
276 );
277 }
278 }
279
280 if constexpr (has_parameter_type<ParameterType::send_buf, Args...>()) {
281 using send_recv_buf_type = buffer_type_with_requested_parameter_type<ParameterType::send_buf, Args...>;
282 using value_type = typename send_recv_buf_type::value_type;
283 return this->scatter(recv_buf(alloc_new<value_type>), std::forward<Args>(args)..., recv_count(1));
284 } else {
285 return this->scatter(recv_buf(alloc_new<recv_value_type_tparam>), std::forward<Args>(args)..., recv_count(1));
286 }
287}
288
289/// @brief Wrapper for \c MPI_Scatterv.
290///
291/// This wrapper for \c MPI_Scatterv distributes data on the root PE across all PEs in the current communicator.
292///
293/// The following parameters are mandatory on the root rank:
294/// - \ref kamping::send_buf() [on all PEs] containing the data to be distributed across all PEs. Non-root PEs can omit
295/// a send buffer by passing `kamping::ignore<T>` as a parameter, or `T` as a template parameter to \ref
296/// kamping::send_buf().
297///
298/// - \ref kamping::send_counts() [on root PE] specifying the number of elements to send to each PE.
299///
300/// The following parameter can be omitted at the cost of communication overhead (1x MPI_Scatter)
301/// - \ref kamping::recv_count() [on all PEs] specifying the number of elements sent to each PE. If this parameter is
302/// omitted, the number of elements sent to each PE is computed based on kamping::send_counts() provided on the
303/// root PE. This parameter is mandatory if \ref kamping::recv_type() is given.
304///
305/// The following parameter can be omitted at the cost of computational overhead:
306/// - \ref kamping::send_displs() [on root PE] specifying the data displacements in the send buffer. If omitted, an
307/// exclusive prefix sum of the send_counts is used.
308///
309/// The following parameters are optional:
310/// - \ref kamping::send_type() specifying the \c MPI datatype to use as send type. If omitted, the \c MPI datatype is
311/// derived automatically based on send_buf's underlying \c value_type. This parameter is ignored on non-root ranks.
312///
313/// - \ref kamping::recv_buf() [on all PEs] containing the received data. The buffer will be resized according to the
314/// buffer's kamping::BufferResizePolicy. If this is kamping::BufferResizePolicy::no_resize, the buffer's underlying
315/// storage must be large enough to hold all received elements.
316///
317/// - \ref kamping::recv_type() specifying the \c MPI datatype to use as recv type. If omitted, the \c MPI datatype is
318/// derived automatically based on recv_buf's underlying \c value_type.
319///
320/// - \ref kamping::root() [on all PEs] specifying the rank of the root PE. If omitted, the default root PE of the
321/// communicator is used instead.
322///
323/// @tparam recv_value_type_tparam The type that is received. Only required when no kamping::send_buf() and no
324/// kamping::recv_buf() is given.
325/// @tparam Args Automatically deduced template parameters.
326/// @param args All required and any number of the optional parameters described above.
327/// @return Result object wrapping the output parameters to be returned by value.
328///
329/// @see \ref docs/parameter_handling.md for general information about parameter handling in KaMPIng.
330/// <hr>
331/// \include{doc} docs/resize_policy.dox
332template <
333 template <typename...>
334 typename DefaultContainerType,
335 template <typename, template <typename...> typename>
336 typename... Plugins>
337template <typename recv_value_type_tparam /* = kamping::internal::unused_tparam */, typename... Args>
339 using namespace kamping::internal;
340
342 Args,
345 send_buf,
346 root,
349 send_type,
350 recv_buf,
353 )
354 );
355
356 // Optional parameter: root()
357 // Default: communicator root
358 using root_param_type = decltype(kamping::root(0));
359 auto&& root_param =
361 int const root_val = root_param.rank_signed();
363 is_valid_rank(root_val),
364 "Invalid root rank " << root_val << " in communicator of size " << size(),
366 );
368 is_same_on_all_ranks(root_val),
369 "Root has to be the same on all ranks.",
371 );
372
373 // Parameter send_buf()
375 auto send_buf =
377 .construct_buffer_or_rebind();
378 using send_value_type = typename std::remove_reference_t<decltype(send_buf)>::value_type;
379 auto const* send_buf_ptr = send_buf.data();
381 !is_root(root_val) || send_buf_ptr != nullptr,
382 "Send buffer must be specified on root.",
384 );
385
386 // Optional parameter: recv_buf()
387 // Default: allocate new container
389 auto recv_buf =
390 internal::select_parameter_type_or_default<internal::ParameterType::recv_buf, default_recv_buf_type>(
391 std::tuple<>(),
392 args...
393 )
395 using recv_value_type = typename std::remove_reference_t<decltype(recv_buf)>::value_type;
396
397 static_assert(
398 !std::is_same_v<recv_value_type, internal::unused_tparam>,
399 "No send_buf or recv_buf parameter provided and no receive value given as template parameter. One of these is "
400 "required."
401 );
402
403 // Get send_type and recv_type
404 auto [send_type, recv_type] =
405 internal::determine_mpi_datatypes<send_value_type, recv_value_type, decltype(recv_buf)>(args...);
406 [[maybe_unused]] constexpr bool recv_type_is_in_param = !has_to_be_computed<decltype(recv_type)>;
407
408 // Get send counts
410 auto send_counts =
413 [[maybe_unused]] constexpr bool send_counts_provided = !has_to_be_computed<decltype(send_counts)>;
415 !is_root(root_val) || send_counts_provided,
416 "send_counts() must be given on the root PE.",
418 );
420 !is_root(root_val) || send_counts.size() >= size(),
421 "Send counts buffer is smaller than the number of PEs at the root PE.",
423 );
424
425 // Get send displacements
427 auto send_displs =
430
431 if (is_root(root_val)) {
432 // send displacements are only considered on the root PE and ignored by MPI on all non-root PEs.
433 constexpr bool do_compute_send_displs = has_to_be_computed<decltype(send_displs)>;
434 if constexpr (do_compute_send_displs) {
435 send_displs.resize_if_requested([&]() { return this->size(); });
436 }
438 send_displs.size() >= size(),
439 "Send displs buffer is smaller than the number of PEs at the root PE.",
441 );
442
443 if constexpr (do_compute_send_displs) {
444 std::exclusive_scan(send_counts.data(), send_counts.data() + size(), send_displs.data(), 0);
445 }
446 }
447
448 // Get recv counts
449 using default_recv_count_type = decltype(recv_count_out());
450 auto recv_count =
452 .construct_buffer_or_rebind();
453
454 // Check that recv_counts() can be used to compute send_counts(); or send_counts() is given on the root PE
455 [[maybe_unused]] constexpr bool do_compute_recv_count = has_to_be_computed<decltype(recv_count)>;
457 this->is_same_on_all_ranks(do_compute_recv_count),
458 "recv_counts() must be given on all PEs or on no PEs",
460 );
461
462 if constexpr (do_compute_recv_count) {
463 scatter(
464 kamping::send_buf(send_counts.underlying()),
467 kamping::recv_buf(recv_count.underlying())
468 );
469 }
470
471 auto compute_required_recv_buf_size = [&]() {
472 return static_cast<size_t>(recv_count.get_single_element());
473 };
474 recv_buf.resize_if_requested(compute_required_recv_buf_size);
476 // if the recv type is user provided, kamping cannot make any assumptions about the required size of
477 // the recv buffer
479 "Recv buffer is not large enough to hold all received elements.",
481 );
482
483 [[maybe_unused]] int const err = MPI_Scatterv(
484 send_buf_ptr, // send buffer
485 send_counts.data(), // send counts
486 send_displs.data(), // send displs
487 send_type.get_single_element(), // send type
488 recv_buf.data(), // recv buffer
489 recv_count.get_single_element(), // recv count
490 recv_type.get_single_element(), // recv type
491 root_val, // root
492 mpi_communicator() // communicator
493 );
494 this->mpi_error_hook(err, "MPI_Scatterv");
495
496 return make_mpi_result<std::tuple<Args...>>(
497 std::move(recv_buf),
498 std::move(recv_count),
499 std::move(send_counts),
500 std::move(send_displs),
501 std::move(send_type),
502 std::move(recv_type)
503 );
504}
505/// @}
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
Wrapper for MPI_Scatter.
Definition scatter.hpp:86
auto scatterv(Args... args) const
Wrapper for MPI_Scatterv.
Definition scatter.hpp:338
auto scatter_single(Args... args) const
Definition scatter.hpp:250
static constexpr auto alloc_new
Convenience wrapper for creating library allocated containers. See AllocNewT for details.
Definition data_buffer.hpp:195
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:175
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.