KaMPIng 0.2.1
Flexible and (near) zero-overhead C++ bindings for MPI
Loading...
Searching...
No Matches
environment.hpp
Go to the documentation of this file.
1// This file is part of KaMPIng.
2//
3// Copyright 2022-2026 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/// @file
15/// @brief Wrapper for MPI functions that don't require a communicator.
16
17#pragma once
18
19#include <vector>
20
21#include <mpi.h>
22
23#include "kamping/assertion_levels.hpp"
26#include "kamping/kassert/kassert.hpp"
27#include "kamping/span.hpp"
29
30namespace kamping {
31
32namespace internal {
33/// @brief A global list of MPI data types registered to KaMPIng.
34inline std::vector<MPI_Datatype> registered_mpi_types;
35} // namespace internal
36
37/// @brief Configuration for the behavior of the constructors and destructor of \ref kamping::Environment.
38enum class InitMPIMode {
39 InitFinalize, ///< Call \c MPI_Init in the constructor of \ref Environment.
40 NoInitFinalize, ///< Do not call \c MPI_Init in the constructor of \ref Environment.
41 InitFinalizeIfNecessary ///< Call \c MPI_Init in the constructor of \ref Environment if \c MPI_Init has not been
42 ///< called before. Call \c MPI_Finalize in the destructor of \ref Environment if \c
43 ///< MPI_Init was called in the constructor.
44};
45
46/// @brief Wrapper for MPI functions that don't require a communicator. If the template parameter `init_finalize_mode`
47/// is set to \ref InitMPIMode::InitFinalize (default), \c MPI_Init is called in the constructor, and
48/// \c MPI_Finalize is called in the destructor.
49///
50/// Note that \c MPI_Init and \c MPI_Finalize are global, meaning that if they are called on an Environment object they
51/// must not be called again in any Environment object (or directly vie the \c MPI_* calls).
52template <InitMPIMode init_finalize_mode = InitMPIMode::InitFinalize>
54public:
55 /// @brief Calls MPI_Init with arguments.
56 ///
57 /// @param argc Number of arguments.
58 /// @param argv The arguments.
59 Environment(int& argc, char**& argv) {
60 if constexpr (init_finalize_mode == InitMPIMode::InitFinalize) {
61 init(argc, argv);
62 } else if constexpr (init_finalize_mode == InitMPIMode::InitFinalizeIfNecessary) {
63 if (!initialized()) {
64 init(argc, argv);
65 _finalize = true;
66 } else {
67 _finalize = false;
68 }
69 }
70 }
71
72 /// @brief Calls MPI_Init without arguments.
74 if constexpr (init_finalize_mode == InitMPIMode::InitFinalize) {
75 init();
76 } else if constexpr (init_finalize_mode == InitMPIMode::InitFinalizeIfNecessary) {
77 if (!initialized()) {
78 init();
79 _finalize = true;
80 } else {
81 _finalize = false;
82 }
83 }
84 }
85
86 /// @brief Calls MPI_Init_thread without arguments and doesn't check whether MPI has already been initialized.
87 /// @param thread_level The desired thread support level. Defaults to \c ThreadLevel::single.
88 /// @return The provided thread support level.
89 ThreadLevel init_unchecked(ThreadLevel thread_level = ThreadLevel::single) const {
90 KAMPING_ASSERT(!initialized(), "Trying to call MPI_Init_thread twice");
94 return static_cast<ThreadLevel>(provided_thread_level);
95 }
96
97 /// @brief Calls MPI_Init_thread with arguments and doesn't check whether MPI has already been initialized.
98 ///
99 /// @param argc Number of arguments.
100 /// @param argv The arguments.
101 /// @param thread_level The desired thread support level. Defaults to \c ThreadLevel::single.
102 /// @return The provided thread support level.
103 ThreadLevel init_unchecked(int& argc, char**& argv, ThreadLevel thread_level = ThreadLevel::single) const {
104 KAMPING_ASSERT(!initialized(), "Trying to call MPI_Init_thread twice");
105 int provided_thread_level = 0;
106 [[maybe_unused]] int err =
109 return static_cast<ThreadLevel>(provided_thread_level);
110 }
111
112 /// @brief Calls MPI_Init_thread without arguments. Checks whether MPI has already been initialized first. If this
113 /// is the case, the function returns immediately.
114 /// @param thread_level The desired thread support level. Defaults to \c ThreadLevel::single.
115 /// @return The provided thread support level. If MPI was already initialized, the current thread level is returned.
116 ThreadLevel init(ThreadLevel thread_level = ThreadLevel::single) const {
117 if (initialized()) {
118 return this->thread_level();
119 }
120 int provided_thread_level = 0;
123 return static_cast<ThreadLevel>(provided_thread_level);
124 }
125
126 /// @brief Calls MPI_Init_thread with arguments. Checks whether MPI has already been initialized first. If this is
127 /// the case, the function returns immediately.
128 ///
129 /// @param argc Number of arguments.
130 /// @param argv The arguments.
131 /// @param thread_level The desired thread support level. Defaults to \c ThreadLevel::single.
132 /// @return The provided thread support level. If MPI was already initialized, the current thread level is returned.
133 ThreadLevel init(int& argc, char**& argv, ThreadLevel thread_level = ThreadLevel::single) const {
134 if (initialized()) {
135 return this->thread_level();
136 }
137 int provided_thread_level = 0;
138 [[maybe_unused]] int err =
141 return static_cast<ThreadLevel>(provided_thread_level);
142 }
143
144 /// @brief Calls MPI_Finalize and frees all registered MPI data types.
145 ///
146 /// Even if you chose InitMPIMode::InitFinalize, you might want to call this function: As MPI_Finalize could
147 /// potentially return an error, this function can be used if you want to be able to handle that error. Otherwise
148 /// the destructor will call MPI_Finalize and not throw on any errors returned.
149 void finalize() const {
150 KAMPING_ASSERT(!finalized(), "Trying to call MPI_Finalize twice");
152 [[maybe_unused]] int err = MPI_Finalize();
154 }
155
156 /// @brief Checks whether MPI_Init has been called.
157 ///
158 /// @return \c true if MPI_Init has been called, \c false otherwise.
159 bool initialized() const {
160 int result;
163 return result == true;
164 }
165
166 /// @brief Checks whether MPI_Finalize has been called.
167 ///
168 /// @return \c true if MPI_Finalize has been called, \c false otherwise.
169 bool finalized() const {
170 int result;
173 return result == true;
174 }
175
176 /// @returns The current level of thread support, as provided after MPI initialization.
178 KAMPING_ASSERT(initialized(), "Cannot query thread level before MPI has been initialized");
179 int provided_thread_level = 0;
182 return static_cast<ThreadLevel>(provided_thread_level);
183 }
184
185 /// @brief Checks whether the calling thread is the main thread.
186 bool is_main_thread() const {
187 KAMPING_ASSERT(initialized(), "Cannot query main thread status before MPI has been initialized");
188 int result = 0;
191 return result != 0;
192 }
193
194 /// @brief Returns the elapsed time since an arbitrary time in the past.
195 ///
196 /// @return The elapsed time in seconds.
197 static double wtime() {
198 return MPI_Wtime();
199 }
200
201 /// @brief Returns the resolution of Environment::wtime().
202 ///
203 /// @return The resolution in seconds.
204 static double wtick() {
205 return MPI_Wtick();
206 }
207
208 /// @brief The upper bound on message tags defined by the MPI implementation.
209 /// @return The upper bound for tags.
210 [[nodiscard]] static int tag_upper_bound() {
211 int* tag_ub;
212 int flag;
214 KAMPING_ASSERT(flag, "Could not retrieve MPI_TAG_UB");
215 return *tag_ub;
216 }
217
218 /// @brief Checks if the given tag is a valid message tag.
219 /// @return Whether the tag is valid.
220 [[nodiscard]] static bool is_valid_tag(int tag) {
221 return tag >= 0 && tag <= tag_upper_bound();
222 }
223
224 /// @brief Register a new MPI data type to KaMPIng that will be freed when using Environment to finalize MPI.
225 /// @param type The MPI data type to register.
226 static void register_mpi_type(MPI_Datatype type) {
227 internal::registered_mpi_types.push_back(type);
228 }
229
230 /// @brief Commit an MPI data type (without registering it with KaMPIng).
231 static void commit(MPI_Datatype type) {
232 int err = MPI_Type_commit(&type);
234 }
235
236 /// @brief Free an MPI data type.
237 static void free(MPI_Datatype type) {
238 int err = MPI_Type_free(&type);
240 }
241
242 /// @brief Commit an MPI data type and register it with KaMPIng.
243 /// @see commit()
244 /// @see register_type()
246 commit(type);
247 register_mpi_type(type);
248 }
249
250 /// @brief Free all registered MPI data types.
251 ///
252 /// Only call this when you no longer want to use any MPI data types created by KaMPIng as other KaMPIng functions
253 /// will assume the created types still exist.
255 for (auto type: internal::registered_mpi_types) {
256 if (type != MPI_DATATYPE_NULL) {
257 MPI_Type_free(&type);
258 }
259 }
261 }
262
263 static inline size_t const bsend_overhead = MPI_BSEND_OVERHEAD; ///< Provides an upper bound on the additional
264 ///< memory required by buffered send operations.
265
266 /// @brief Attach a buffer to use for buffered send operations to the environment.
267 ///
268 /// @tparam T The type of the buffer.
269 /// @param buffer The buffer. The user is responsible for allocating the buffer, attaching it, detaching it and
270 /// freeing the memory after detaching. For convenience, the buffer may be a span of any type, but the type is
271 /// ignored by MPI.
272 template <typename T>
274#if KAMPING_ASSERT_ENABLED(KAMPING_ASSERTION_LEVEL_NORMAL)
275 KAMPING_ASSERT(!has_buffer_attached, "You may only attach one buffer at a time.");
276#endif
277 int err = MPI_Buffer_attach(buffer.data(), asserting_cast<int>(buffer.size() * sizeof(T)));
279#if KAMPING_ASSERT_ENABLED(KAMPING_ASSERTION_LEVEL_NORMAL)
280 has_buffer_attached = true;
281#endif
282 }
283
284 /// @todo: maybe we want buffer_allocate. This would require us keeping track of the buffer internally.
285
286 /// @brief Detach a buffer attached via \ref buffer_attach().
287 ///
288 /// @tparam T The type of the span to return. Defaults to \c std::byte.
289 /// @return A span pointing to the previously attached buffer. The type of the returned span can be controlled via
290 /// the parameter \c T. The pointer to the buffer stored internally by MPI is reinterpreted accordingly.
291 template <typename T = std::byte>
293#if KAMPING_ASSERT_ENABLED(KAMPING_ASSERTION_LEVEL_NORMAL)
294 KAMPING_ASSERT(has_buffer_attached, "There is currently no buffer attached.");
295#endif
296 void* buffer_ptr;
297 int buffer_size;
300#if KAMPING_ASSERT_ENABLED(KAMPING_ASSERTION_LEVEL_NORMAL)
301 has_buffer_attached = false;
302#endif
304 static_cast<size_t>(buffer_size) % sizeof(T) == size_t{0},
305 "The buffer size is not a multiple of the size of T."
306 );
307
308 // convert the returned pointer and size to a span of type T
309 return Span<T>{static_cast<T*>(buffer_ptr), asserting_cast<size_t>(buffer_size) / sizeof(T)};
310 }
311
312 /// @brief Calls MPI_Finalize if finalize() has not been called before. Also frees all registered MPI data types.
314 if (init_finalize_mode == InitMPIMode::InitFinalize
315 || (init_finalize_mode == InitMPIMode::InitFinalizeIfNecessary && _finalize)) {
316 bool is_already_finalized = false;
317 try {
319 } catch (MpiErrorException&) {
320 // Just kassert. We can't throw exceptions in the destructor.
321
322 // During testing we sometimes force KAMPING_ASSERT to throw exceptions. During the resulting stack
323 // unwinding, code in this destructor might be executed and thus throwing another exception will result
324 // in calling std::abort(). We're disabling the respective warning here.
325#if defined(__GNUC__) and not defined(__clang__)
326 #pragma GCC diagnostic push
327 #pragma GCC diagnostic ignored "-Wterminate"
328#endif
329 KAMPING_ASSERT(false, "MPI_Finalized call failed.");
330#if defined(__GNUC__) and not defined(__clang__)
331 #pragma GCC diagnostic pop
332#endif
333 }
336 [[maybe_unused]] int err = MPI_Finalize();
337 // see above
338#if defined(__GNUC__) and not defined(__clang__)
339 #pragma GCC diagnostic push
340 #pragma GCC diagnostic ignored "-Wterminate"
341#endif
342 KAMPING_ASSERT(err == MPI_SUCCESS, "MPI_Finalize call failed.");
343#if defined(__GNUC__) and not defined(__clang__)
344 #pragma GCC diagnostic pop
345#endif
346 }
347 }
348 }
349
350private:
351 bool _finalize = false;
352#if KAMPING_ASSERT_ENABLED(KAMPING_ASSERTION_LEVEL_NORMAL)
353 bool has_buffer_attached = false; ///< Is there currently an attached buffer?
354#endif
355
356}; // class Environment
357
358/// @brief A global environment object to use when you don't want to create a new Environment object.
359///
360/// Note that \c inline \c const results in external linkage since C++17 (see
361/// https://en.cppreference.com/w/cpp/language/inline).
363
364} // namespace kamping
Helper functions that make casts safer.
Wrapper for MPI functions that don't require a communicator. If the template parameter init_finalize_...
Definition environment.hpp:53
ThreadLevel init(int &argc, char **&argv, ThreadLevel thread_level=ThreadLevel::single) const
Calls MPI_Init_thread with arguments. Checks whether MPI has already been initialized first....
Definition environment.hpp:133
Environment(int &argc, char **&argv)
Calls MPI_Init with arguments.
Definition environment.hpp:59
void buffer_attach(Span< T > buffer)
Attach a buffer to use for buffered send operations to the environment.
Definition environment.hpp:273
ThreadLevel init(ThreadLevel thread_level=ThreadLevel::single) const
Calls MPI_Init_thread without arguments. Checks whether MPI has already been initialized first....
Definition environment.hpp:116
static int tag_upper_bound()
The upper bound on message tags defined by the MPI implementation.
Definition environment.hpp:210
bool initialized() const
Checks whether MPI_Init has been called.
Definition environment.hpp:159
bool is_main_thread() const
Checks whether the calling thread is the main thread.
Definition environment.hpp:186
static void free(MPI_Datatype type)
Free an MPI data type.
Definition environment.hpp:237
void finalize() const
Calls MPI_Finalize and frees all registered MPI data types.
Definition environment.hpp:149
static bool is_valid_tag(int tag)
Checks if the given tag is a valid message tag.
Definition environment.hpp:220
static void commit(MPI_Datatype type)
Commit an MPI data type (without registering it with KaMPIng).
Definition environment.hpp:231
static double wtime()
Returns the elapsed time since an arbitrary time in the past.
Definition environment.hpp:197
static size_t const bsend_overhead
Definition environment.hpp:263
static void free_registered_mpi_types()
Free all registered MPI data types.
Definition environment.hpp:254
static void register_mpi_type(MPI_Datatype type)
Register a new MPI data type to KaMPIng that will be freed when using Environment to finalize MPI.
Definition environment.hpp:226
bool finalized() const
Checks whether MPI_Finalize has been called.
Definition environment.hpp:169
~Environment()
Calls MPI_Finalize if finalize() has not been called before. Also frees all registered MPI data types...
Definition environment.hpp:313
ThreadLevel thread_level() const
Definition environment.hpp:177
static void commit_and_register(MPI_Datatype type)
Commit an MPI data type and register it with KaMPIng.
Definition environment.hpp:245
ThreadLevel init_unchecked(int &argc, char **&argv, ThreadLevel thread_level=ThreadLevel::single) const
Calls MPI_Init_thread with arguments and doesn't check whether MPI has already been initialized.
Definition environment.hpp:103
Span< T > buffer_detach()
Detach a buffer attached via buffer_attach().
Definition environment.hpp:292
ThreadLevel init_unchecked(ThreadLevel thread_level=ThreadLevel::single) const
Calls MPI_Init_thread without arguments and doesn't check whether MPI has already been initialized.
Definition environment.hpp:89
static double wtick()
Returns the resolution of Environment::wtime().
Definition environment.hpp:204
Environment()
Calls MPI_Init without arguments.
Definition environment.hpp:73
STL-compatible allocator for requesting memory using the builtin MPI allocator.
Definition allocator.hpp:32
The exception type used when an MPI call did not return MPI_SUCCESS.
Definition error_handling.hpp:49
InitMPIMode
Configuration for the behavior of the constructors and destructor of kamping::Environment.
Definition environment.hpp:38
@ InitFinalize
Call MPI_Init in the constructor of Environment.
@ NoInitFinalize
Do not call MPI_Init in the constructor of Environment.
Environment< InitMPIMode::NoInitFinalize > const mpi_env
A global environment object to use when you don't want to create a new Environment object.
Definition environment.hpp:362
Code for error handling.
#define THROW_IF_MPI_ERROR(error_code, function)
Wrapper around THROWING_KAMPING_ASSERT for MPI errors.
Definition error_handling.hpp:34
auto tag(internal::any_tag_t)
Indicates to use MPI_ANY_TAG as tag in the underlying call.
Definition named_parameters.hpp:1066
std::vector< MPI_Datatype > registered_mpi_types
A global list of MPI data types registered to KaMPIng.
Definition environment.hpp:34
MPI thread support levels.
ThreadLevel
MPI thread support levels defining the allowed concurrency of MPI calls relative to application threa...
Definition thread_levels.hpp:24