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