KaMPIng 0.1.1
Flexible and (near) zero-overhead C++ bindings for MPI
Loading...
Searching...
No Matches
gather.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
19#include <kassert/kassert.hpp>
20#include <mpi.h>
21
22#include "kamping/assertion_levels.hpp"
24#include "kamping/collectives/collectives_helpers.hpp"
25#include "kamping/comm_helper/is_same_on_all_ranks.hpp"
26#include "kamping/communicator.hpp"
33#include "kamping/result.hpp"
34
35/// @addtogroup kamping_collectives
36/// @{
37
38/// @brief Wrapper for \c MPI_Gather.
39///
40/// This wrapper for \c MPI_Gather collects the same amount of data from each rank to a root.
41///
42/// The following arguments are required:
43/// - \ref kamping::send_buf() containing the data that is sent to the root.
44///
45/// The following buffers are optional:
46/// - \ref kamping::send_count() [on all PEs] specifying the number of elements to send to the root PE. If not given,
47/// the size of the kamping::send_buf() will be used. This parameter is mandatory if \ref kamping::send_type() is given.
48///
49/// - \ref kamping::send_type() specifying the \c MPI datatype to use as send type. If omitted, the \c MPI datatype is
50/// derived automatically based on send_buf's underlying \c value_type. This parameter is ignored on non-root ranks.
51///
52/// - \ref kamping::recv_buf() containing a buffer for the output.
53/// On the root rank, the buffer will contain all data from all send buffers.
54/// At all other ranks, the buffer will not be modified and the parameter is ignored.
55///
56/// - \ref kamping::recv_count() [on root PE] specifying the number of elements to receive from each PE. On non-root
57/// ranks, this parameter is ignored. If not specified, defaults to the value of \ref kamping::send_count() on the
58/// root. PE. In total, comm.size() * recv_counts elements will be received into the receiver buffer.
59/// This parameter is mandatory if \ref kamping::recv_type() is given.
60///
61/// - \ref kamping::recv_type() specifying the \c MPI datatype to use as recv type. If omitted, the \c MPI datatype is
62/// derived automatically based on recv_buf's underlying \c value_type.
63///
64/// - \ref kamping::root() specifying an alternative root. If not present, the default root of the \c Communicator
65/// is used, see root().
66///
67/// @tparam Args Automatically deduced template parameters.
68/// @param args All required and any number of the optional parameters described above.
69/// @return Result object wrapping the output parameters to be returned by value.
70///
71/// @see \ref docs/parameter_handling.md for general information about parameter handling in KaMPIng.
72/// <hr>
73/// \include{doc} docs/resize_policy.dox
74template <
75 template <typename...>
76 typename DefaultContainerType,
77 template <typename, template <typename...> typename>
78 typename... Plugins>
79template <typename... Args>
81 using namespace kamping::internal;
83 Args,
86 );
87
88 auto&& root = internal::select_parameter_type_or_default<internal::ParameterType::root, internal::RootDataBuffer>(
89 std::tuple(this->root()),
90 args...
91 );
92 KASSERT(this->is_valid_rank(root.rank_signed()), "Invalid rank as root.");
93 KASSERT(
94 this->is_same_on_all_ranks(root.rank_signed()),
95 "Root has to be the same on all ranks.",
97 );
98
99 auto&& send_buf =
100 internal::select_parameter_type<internal::ParameterType::send_buf>(args...).construct_buffer_or_rebind();
101 using send_value_type = typename std::remove_reference_t<decltype(send_buf)>::value_type;
102
104 auto&& send_count =
105 internal::select_parameter_type_or_default<internal::ParameterType::send_count, default_send_count_type>(
106 std::tuple(),
107 args...
108 )
109 .construct_buffer_or_rebind();
111 if constexpr (do_compute_send_count) {
112 send_count.underlying() = asserting_cast<int>(send_buf.size());
113 }
114
116
117 auto&& recv_buf =
118 internal::select_parameter_type_or_default<internal::ParameterType::recv_buf, default_recv_buf_type>(
119 std::tuple(),
120 args...
121 )
123 using recv_value_type = typename std::remove_reference_t<decltype(recv_buf)>::value_type;
124
125 // Get send_type and recv_type
126 auto&& [send_type, recv_type] =
127 internal::determine_mpi_datatypes<send_value_type, recv_value_type, decltype(recv_buf)>(args...);
128 [[maybe_unused]] constexpr bool recv_type_is_in_param = !has_to_be_computed<decltype(recv_type)>;
129
130 // Optional parameter: recv_count()
131 // Default: compute value based on send_buf.size on root
133 auto&& recv_count =
134 internal::select_parameter_type_or_default<internal::ParameterType::recv_count, default_recv_count_type>(
135 std::tuple(),
136 args...
137 )
138 .construct_buffer_or_rebind();
139 constexpr bool do_compute_recv_count = has_to_be_computed<decltype(recv_count)>;
140 if constexpr (do_compute_recv_count) {
141 if (this->is_root(root.rank_signed())) {
142 recv_count.underlying() = send_count.get_single_element();
143 }
144 }
145
147 return asserting_cast<size_t>(recv_count.get_single_element()) * this->size();
148 };
149 if (this->is_root(root.rank_signed())) {
150 recv_buf.resize_if_requested(compute_required_recv_buf_size);
151 KASSERT(
152 // if the recv type is user provided, kamping cannot make any assumptions about the required size of
153 // the recv buffer
155 "Recv buffer is not large enough to hold all received elements.",
157 );
158 }
159
160 // error code can be unused if KTHROW is removed at compile time
161 [[maybe_unused]] int err = MPI_Gather(
162 send_buf.data(), // sendbuffer
163 send_count.get_single_element(), // sendcount
164 send_type.get_single_element(), // sendtype
165 recv_buf.data(), // recvbuffer
166 recv_count.get_single_element(), // recvcount
167 recv_type.get_single_element(), // recvtype
168 root.rank_signed(), // root
169 this->mpi_communicator() // communicator
170 );
171 this->mpi_error_hook(err, "MPI_Gather");
172 return make_mpi_result<std::tuple<Args...>>(
173 std::move(recv_buf),
174 std::move(recv_count),
175 std::move(send_count),
176 std::move(send_type),
177 std::move(recv_type)
178 );
179}
180
181/// @brief Wrapper for \c MPI_Gatherv.
182///
183/// This wrapper for \c MPI_Gatherv collects possibly different amounts of data from each rank to a root.
184///
185/// The following arguments are required:
186/// - \ref kamping::send_buf() containing the data that is sent to the root.
187///
188/// The following parameter is optional but results in communication overhead if omitted:
189/// - \ref kamping::recv_counts() containing the number of elements to receive from each rank. Only the root rank uses
190/// the content of this buffer, all other ranks ignore it. However, if provided on any rank it must be provided on all
191/// ranks (possibly empty on non-root ranks). If each rank provides this parameter either as an output parameter or by
192/// passing \c recv_counts(kamping::ignore), then the \c recv_counts on root will be computed by a gather of all local
193/// send counts. This parameter is mandatory (as an in-parameter) if \ref kamping::recv_type() is given.
194///
195/// The following buffers are optional:
196/// - \ref kamping::send_count() [on all PEs] specifying the number of elements to send to the root rank. If not given,
197/// the size of the kamping::send_buf() will be used. This parameter is mandatory if \ref kamping::send_type() is given.
198///
199/// - \ref kamping::recv_buf() containing a buffer for the output. Afterwards, at the root, this buffer will contain
200/// all data from all send buffers. At all other ranks, the buffer will have size 0.
201///
202/// - \ref kamping::recv_displs() containing the offsets of the messages in recv_buf. The `recv_counts[i]` elements
203/// starting at `recv_buf[recv_displs[i]]` will be received from rank `i`. If omitted, this is calculated as the
204/// exclusive prefix-sum of `recv_counts`.
205///
206/// - \ref kamping::root() specifying an alternative root. If not present, the default root of the \c Communicator
207/// is used, see root().
208///
209/// @tparam Args Automatically deduced template parameters.
210/// @param args All required and any number of the optional parameters described above.
211/// @return Result object wrapping the output parameters to be returned by value.
212///
213/// @see \ref docs/parameter_handling.md for general information about parameter handling in KaMPIng.
214/// <hr>
215/// \include{doc} docs/resize_policy.dox
216template <
217 template <typename...>
218 typename DefaultContainerType,
219 template <typename, template <typename...> typename>
220 typename... Plugins>
221template <typename... Args>
223 using namespace kamping::internal;
225 Args,
228 );
229
230 // get send buffer
231 auto&& send_buf =
232 internal::select_parameter_type<internal::ParameterType::send_buf>(args...).construct_buffer_or_rebind();
233 using send_value_type = typename std::remove_reference_t<decltype(send_buf)>::value_type;
234
235 // get recv buffer
237 auto&& recv_buf =
238 internal::select_parameter_type_or_default<internal::ParameterType::recv_buf, default_recv_buf_type>(
239 std::tuple(),
240 args...
241 )
243 using recv_value_type = typename std::remove_reference_t<decltype(recv_buf)>::value_type;
244
245 // get root rank
246 auto&& root = internal::select_parameter_type_or_default<internal::ParameterType::root, internal::RootDataBuffer>(
247 std::tuple(this->root()),
248 args...
249 );
250
251 // get send and recv type
252 auto&& [send_type, recv_type] =
253 internal::determine_mpi_datatypes<send_value_type, recv_value_type, decltype(recv_buf)>(args...);
254 [[maybe_unused]] constexpr bool recv_type_is_in_param = !has_to_be_computed<decltype(recv_type)>;
255
256 // get recv counts
258 auto&& recv_counts =
259 internal::select_parameter_type_or_default<internal::ParameterType::recv_counts, default_recv_counts_type>(
260 std::tuple(),
261 args...
262 )
264 using recv_counts_type = typename std::remove_reference_t<decltype(recv_counts)>::value_type;
265 static_assert(std::is_same_v<std::remove_const_t<recv_counts_type>, int>, "Recv counts must be of type int");
266 using recv_counts_param_type = std::remove_reference_t<decltype(recv_counts)>;
267 constexpr bool recv_counts_is_ignore =
268 is_empty_data_buffer_v<
269 recv_counts_param_type> && recv_counts_param_type::buffer_type == internal::BufferType::ignore;
270
271 // because this check is asymmetric, we move it before any communication happens.
272 KASSERT(!this->is_root(root.rank_signed()) || !recv_counts_is_ignore, "Root cannot ignore recv counts.");
273
274 KASSERT(this->is_valid_rank(root.rank_signed()), "Invalid rank as root.");
275 KASSERT(
276 this->is_same_on_all_ranks(root.rank_signed()),
277 "Root has to be the same on all ranks.",
279 );
280
282 auto&& send_count =
283 internal::select_parameter_type_or_default<internal::ParameterType::send_count, default_send_count_type>(
284 std::tuple(),
285 args...
286 )
287 .construct_buffer_or_rebind();
289 if constexpr (do_compute_send_count) {
290 send_count.underlying() = asserting_cast<int>(send_buf.size());
291 }
292
293 // get recv displs
295 auto&& recv_displs =
296 internal::select_parameter_type_or_default<internal::ParameterType::recv_displs, default_recv_displs_type>(
297 std::tuple(),
298 args...
299 )
301 using recv_displs_type = typename std::remove_reference_t<decltype(recv_displs)>::value_type;
302 static_assert(std::is_same_v<std::remove_const_t<recv_displs_type>, int>, "Recv displs must be of type int");
303
304 // calculate recv_counts if necessary
305 constexpr bool do_calculate_recv_counts =
307 KASSERT(
308 is_same_on_all_ranks(do_calculate_recv_counts),
309 "Receive counts are given on some ranks and are omitted on others",
311 );
312
314 return asserting_cast<size_t>(this->size());
315 };
316 if constexpr (do_calculate_recv_counts) {
317 if (this->is_root(root.rank_signed())) {
319 KASSERT(
321 "Recv counts buffer is smaller than the number of PEs at the root PE.",
323 );
324 }
325 this->gather(
326 kamping::send_buf(send_count.underlying()),
330 kamping::root(root.rank_signed())
331 );
332 } else {
333 if (this->is_root(root.rank_signed())) {
334 KASSERT(
336 "Recv counts buffer is smaller than the number of PEs at the root PE.",
338 );
339 }
340 }
341
343
344 // calculate recv_displs if necessary
347 return asserting_cast<size_t>(this->size());
348 };
349 if constexpr (do_calculate_recv_displs) {
350 if (this->is_root(root.rank_signed())) {
352 std::exclusive_scan(recv_counts.data(), recv_counts.data() + this->size(), recv_displs.data(), 0);
353 }
354 }
355 if (this->is_root(root.rank_signed())) {
356 KASSERT(
358 "Recv displs buffer is smaller than the number of PEs at the root PE.",
360 );
361 }
362
363 if (this->is_root(root.rank_signed())) {
365 return compute_required_recv_buf_size_in_vectorized_communication(recv_counts, recv_displs, this->size());
366 };
367 recv_buf.resize_if_requested(compute_required_recv_buf_size);
368 KASSERT(
369 // if the recv type is user provided, kamping cannot make any assumptions about the required size of
370 // the recv buffer
372 "Recv buffer is not large enough to hold all received elements.",
374 );
375 }
376
377 // error code can be unused if KTHROW is removed at compile time
378 [[maybe_unused]] int err = MPI_Gatherv(
379 send_buf.data(), // send buffer
380 send_count.get_single_element(), // send count
381 send_type.get_single_element(), // send type
382 recv_buf.data(), // recv buffer
383 recv_counts.data(), // recv counts
384 recv_displs.data(), // recv displacements
385 recv_type.get_single_element(), // recv type
386 root.rank_signed(), // root rank
387 this->mpi_communicator() // communicator
388 );
389 this->mpi_error_hook(err, "MPI_Gather");
390 return make_mpi_result<std::tuple<Args...>>(
391 std::move(recv_buf),
392 std::move(recv_counts),
393 std::move(recv_displs),
394 std::move(send_count),
395 std::move(send_type),
396 std::move(recv_type)
397 );
398}
399/// @}
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 gather(Args... args) const
Wrapper for MPI_Gather.
Definition gather.hpp:80
auto gatherv(Args... args) const
Wrapper for MPI_Gatherv.
Definition gather.hpp:222
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_count(int count)
Passes count as send count to the underlying call.
Definition named_parameters.hpp:321
auto recv_counts(Container &&container)
Passes a container as recv counts to the underlying call, i.e. the container's storage must contain t...
Definition named_parameters.hpp:366
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_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 recv_counts_out()
Indicates to construct a container with type kamping::Communicator::default_container_type<int>,...
Definition named_parameters.hpp:478
auto recv_displs_out()
Indicates to construct a container with type kamping::Communicator::default_container_type<int>,...
Definition named_parameters.hpp:802
auto recv_displs(Container &&container)
Passes a container as receive displacements to the underlying call, i.e. the container's storage must...
Definition named_parameters.hpp:697
auto recv_count(int count)
Passes count as recv count to the underlying call.
Definition named_parameters.hpp:490
auto recv_type(MPI_Datatype recv_type)
Passes recv_type as recv type to the underlying call.
Definition named_parameters.hpp:1238
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
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.