deal.II version GIT relicensing-6809-ge913b9bb34 2026-09-25 17:20:01+00:00
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
aligned_vector.h
Go to the documentation of this file.
1// -----------------------------------------------------------------------------
2//
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception OR LGPL-2.1-or-later
4// Copyright (C) 2014 - 2026 by the deal.II authors
5//
6// This file is part of the deal.II library.
7//
8// Detailed license information governing the source code and contributions
9// can be found in LICENSE.md and CONTRIBUTING.md at the top level directory.
10//
11// -----------------------------------------------------------------------------
12
13
14#ifndef dealii_aligned_vector_h
15#define dealii_aligned_vector_h
16
17#include <deal.II/base/config.h>
18
21#include <deal.II/base/mpi.h>
24
25// boost::serialization::make_array used to be in array.hpp, but was
26// moved to a different file in BOOST 1.64
27#include <boost/version.hpp>
28#if BOOST_VERSION >= 106400
29# include <boost/serialization/array_wrapper.hpp>
30#else
31# include <boost/serialization/array.hpp>
32#endif
33#include <boost/serialization/binary_object.hpp>
34#include <boost/serialization/split_member.hpp>
35
36#include <cstring>
37#include <memory>
38#include <type_traits>
39
40
41
43
44
58template <class T>
60{
61public:
66 using value_type = T;
68 using const_pointer = const value_type *;
70 using const_iterator = const value_type *;
72 using const_reference = const value_type &;
73 using size_type = std::size_t;
74
79
88 template <
89 typename RandomAccessIterator,
90 typename = std::enable_if_t<std::is_convertible_v<
91 typename std::iterator_traits<RandomAccessIterator>::iterator_category,
92 std::random_access_iterator_tag>>>
93 AlignedVector(RandomAccessIterator begin, RandomAccessIterator end);
94
101 explicit AlignedVector(const size_type size, const T &init = T());
102
106 ~AlignedVector() = default;
107
114
120
128
134
154 void
155 resize_fast(const size_type new_size);
156
169 void
170 resize(const size_type new_size);
171
187 void
188 resize(const size_type new_size, const T &init);
189
210 void
211 reserve(const size_type new_allocated_size);
212
216 void
218
223 void
225
231 void
232 push_back(const T in_data);
233
239
244 back() const;
245
250 template <typename ForwardIterator>
251 void
252 insert_back(ForwardIterator begin, ForwardIterator end);
253
262 template <
263 typename RandomAccessIterator,
264 typename = std::enable_if_t<std::is_convertible_v<
265 typename std::iterator_traits<RandomAccessIterator>::iterator_category,
266 std::random_access_iterator_tag>>>
269 RandomAccessIterator begin,
270 RandomAccessIterator end);
271
281 void
283
292 void
293 fill(const T &element);
294
382 void
384 const unsigned int root_process);
385
389 void
390 swap(AlignedVector<T> &vec) noexcept;
391
395 bool
396 empty() const;
397
402 size() const;
403
409 capacity() const;
410
415 operator[](const size_type index);
416
421 operator[](const size_type index) const;
422
426 pointer
428
433 data() const;
434
440
446
451 begin() const;
452
457 end() const;
458
466
472 template <class Archive>
473 void
474 save(Archive &ar, const unsigned int version) const;
475
481 template <class Archive>
482 void
483 load(Archive &ar, const unsigned int version);
484
485#ifdef DOXYGEN
491 template <class Archive>
492 void
493 serialize(Archive &archive, const unsigned int version);
494#else
495 // This macro defines the serialize() method that is compatible with
496 // the templated save() and load() method that have been implemented.
497 BOOST_SERIALIZATION_SPLIT_MEMBER()
498#endif
499
507 "Changing the vector after a call to "
508 "replicate_across_communicator() is not allowed.");
509
510private:
515 void
516 allocate_and_move(const std::size_t old_size,
517 const std::size_t new_size,
518 const std::size_t new_allocated_size);
519
612 {
613 public:
619 Deleter(AlignedVector<T> *owning_object);
620
621#ifdef DEAL_II_WITH_MPI
629 Deleter(AlignedVector<T> *owning_object,
630 const bool is_shmem_root,
631 T *aligned_shmem_pointer,
632 MPI_Comm shmem_group_communicator,
633 MPI_Win shmem_window);
634#endif
635
641 void
642 operator()(T *ptr);
643
651 void
652 reset_owning_object(const AlignedVector<T> *new_aligned_vector_ptr);
653
654 private:
659 {
660 public:
664 virtual ~DeleterActionBase() = default;
665
671 virtual void
673 };
674
675#ifdef DEAL_II_WITH_MPI
676
682 {
683 public:
691 MPI_Win shmem_window);
692
698 virtual void
699 delete_array(const AlignedVector<T> *aligned_vector, T *ptr) override;
700
701 private:
706 const bool is_shmem_root;
710 };
711#endif
712
717 std::unique_ptr<DeleterActionBase> deleter_action_object;
718
724 };
725
729 std::unique_ptr<T[], Deleter> elements;
730
735
740
745};
746
747
748// ------------------------------- inline functions --------------------------
749
755namespace internal
756{
775 template <typename RandomAccessIterator, typename T>
778 {
779 static const std::size_t minimum_parallel_grain_size =
780 160000 / sizeof(T) + 1;
781
782 public:
792 AlignedVectorCopyConstruct(RandomAccessIterator source_begin,
793 RandomAccessIterator source_end,
794 T *const destination)
795 : source_(source_begin)
796 , destination_(destination)
797 {
798 Assert(source_end >= source_begin, ExcInternalError());
799 Assert(source_end == source_begin || destination != nullptr,
801 const std::size_t size = source_end - source_begin;
804 else
806 }
807
812 virtual void
813 apply_to_subrange(const std::size_t begin,
814 const std::size_t end) const override
815 {
816 if (end == begin)
817 return;
818
819 // We can use memcpy() with trivially copyable objects.
820 if constexpr (std::is_trivially_copyable_v<T> == true &&
821 (std::is_same_v<T *, RandomAccessIterator> ||
822 std::is_same_v<const T *, RandomAccessIterator>) == true)
823 std::memcpy(destination_ + begin,
824 source_ + begin,
825 (end - begin) * sizeof(T));
826 else
827 for (std::size_t i = begin; i < end; ++i)
828 new (&destination_[i]) T(*(source_ + i));
829 }
830
831 private:
832 RandomAccessIterator source_;
833 T *const destination_;
834 };
835
836
843 template <typename RandomAccessIterator, typename T>
846 {
847 static const std::size_t minimum_parallel_grain_size =
848 160000 / sizeof(T) + 1;
849
850 public:
860 AlignedVectorMoveConstruct(RandomAccessIterator source_begin,
861 RandomAccessIterator source_end,
862 T *const destination)
863 : source_(source_begin)
864 , destination_(destination)
865 {
866 Assert(source_end >= source_begin, ExcInternalError());
867 Assert(source_end == source_begin || destination != nullptr,
869 const std::size_t size = source_end - source_begin;
872 else
874 }
875
880 virtual void
881 apply_to_subrange(const std::size_t begin,
882 const std::size_t end) const override
883 {
884 if (end == begin)
885 return;
886
887 // We can use memcpy() with trivially copyable objects.
888 if constexpr (std::is_trivially_copyable_v<T> == true &&
889 (std::is_same_v<T *, RandomAccessIterator> ||
890 std::is_same_v<const T *, RandomAccessIterator>) == true)
891 std::memcpy(destination_ + begin,
892 source_ + begin,
893 (end - begin) * sizeof(T));
894 else
895 // For everything else just use the move constructor. The original
896 // object remains alive and will be destroyed elsewhere.
897 for (std::size_t i = begin; i < end; ++i)
898 new (&destination_[i]) T(std::move(*(source_ + i)));
899 }
900
901 private:
902 RandomAccessIterator source_;
903 T *const destination_;
904 };
905
906
924 template <typename T, bool initialize_memory>
926 {
927 static const std::size_t minimum_parallel_grain_size =
928 160000 / sizeof(T) + 1;
929
930 public:
935 AlignedVectorInitialize(const std::size_t size,
936 const T &element,
937 T *const destination)
938 : element_(element)
939 , destination_(destination)
940 , trivial_element(false)
941 {
942 if (size == 0)
943 return;
944 Assert(destination != nullptr, ExcInternalError());
945
946 // do not use memcmp() for long double because on some systems it does not
947 // completely fill its memory and may lead to false positives in e.g.
948 // valgrind
949 if constexpr (std::is_trivially_default_constructible_v<T> == true &&
950 std::is_same_v<T, long double> == false)
951 {
952 const unsigned char zero[sizeof(T)] = {};
953 if (std::memcmp(zero, &element, sizeof(T)) == 0)
954 trivial_element = true;
955 }
958 else
960 }
961
965 virtual void
966 apply_to_subrange(const std::size_t begin,
967 const std::size_t end) const override
968 {
969 // Only use memset() with types whose default constructors don't do
970 // anything.
971 if constexpr (std::is_trivially_default_constructible_v<T> == true)
972 if (trivial_element)
973 {
974 std::memset(destination_ + begin, 0, (end - begin) * sizeof(T));
975 return;
976 }
977
979 end,
980 std::bool_constant<initialize_memory>());
981 }
982
983 private:
984 const T &element_;
985 mutable T *destination_;
987
988 // copy assignment operation
989 void
991 const std::size_t end,
992 std::bool_constant<false>) const
993 {
994 for (std::size_t i = begin; i < end; ++i)
996 }
997
998 // copy constructor (memory initialization)
999 void
1001 const std::size_t end,
1002 std::bool_constant<true>) const
1003 {
1004 for (std::size_t i = begin; i < end; ++i)
1005 new (&destination_[i]) T(element_);
1006 }
1007 };
1008
1009
1010
1023 template <typename T, bool initialize_memory>
1026 {
1027 static const std::size_t minimum_parallel_grain_size =
1028 160000 / sizeof(T) + 1;
1029
1030 public:
1035 AlignedVectorDefaultInitialize(const std::size_t size, T *const destination)
1036 : destination_(destination)
1037 {
1038 if (size == 0)
1039 return;
1040 Assert(destination != nullptr, ExcInternalError());
1041
1044 else
1046 }
1047
1051 virtual void
1052 apply_to_subrange(const std::size_t begin,
1053 const std::size_t end) const override
1054 {
1055 // Only use memset() with types whose default constructors don't do
1056 // anything.
1057 if constexpr (std::is_trivially_default_constructible_v<T> == true)
1058 std::memset(destination_ + begin, 0, (end - begin) * sizeof(T));
1059 else
1061 end,
1062 std::bool_constant<initialize_memory>());
1063 }
1064
1065 private:
1066 mutable T *destination_;
1067
1068 // copy assignment operation
1069 void
1071 const std::size_t end,
1072 std::bool_constant<false>) const
1073 {
1074 for (std::size_t i = begin; i < end; ++i)
1075 destination_[i] = std::move(T());
1076 }
1077
1078 // copy constructor (memory initialization)
1079 void
1081 const std::size_t end,
1082 std::bool_constant<true>) const
1083 {
1084 for (std::size_t i = begin; i < end; ++i)
1085 new (&destination_[i]) T;
1086 }
1087 };
1088
1089} // end of namespace internal
1090
1091
1092#ifndef DOXYGEN
1093
1094
1095
1096template <typename T>
1098 : deleter_action_object(nullptr) // encode default action by using a nullptr
1099 , owning_aligned_vector(owning_object)
1100{}
1101
1102
1103# ifdef DEAL_II_WITH_MPI
1104
1105template <typename T>
1107 const bool is_shmem_root,
1108 T *aligned_shmem_pointer,
1109 MPI_Comm shmem_group_communicator,
1110 MPI_Win shmem_window)
1111 : deleter_action_object(
1112 std::make_unique<MPISharedMemDeleterAction>(is_shmem_root,
1113 aligned_shmem_pointer,
1114 shmem_group_communicator,
1115 shmem_window))
1116 , owning_aligned_vector(owning_object)
1117{}
1118# endif
1119
1120
1121template <typename T>
1122inline void
1124{
1125 // If no special action has been registered (i.e., if the action pointer is
1126 // nullptr), then just perform the default action right here.
1127 if (deleter_action_object == nullptr)
1128 {
1129 if (ptr != nullptr)
1130 {
1131 Assert(owning_aligned_vector->used_elements_end != nullptr,
1133
1134 if (std::is_trivially_destructible_v<T> == false)
1135 for (T *p = owning_aligned_vector->used_elements_end - 1; p >= ptr;
1136 --p)
1137 p->~T();
1138
1139 std::free(ptr);
1140 }
1141 }
1142 else
1143 // Otherwise, let the action object do what is necessary
1144 deleter_action_object->delete_array(owning_aligned_vector, ptr);
1145}
1146
1147
1148
1149template <typename T>
1150inline void
1152 const AlignedVector<T> *new_aligned_vector_ptr)
1153{
1154 owning_aligned_vector = new_aligned_vector_ptr;
1155}
1156
1157
1158# ifdef DEAL_II_WITH_MPI
1159
1160template <typename T>
1162 MPISharedMemDeleterAction(const bool is_shmem_root,
1163 T *aligned_shmem_pointer,
1164 MPI_Comm shmem_group_communicator,
1165 MPI_Win shmem_window)
1166 : is_shmem_root(is_shmem_root)
1167 , aligned_shmem_pointer(aligned_shmem_pointer)
1168 , shmem_group_communicator(shmem_group_communicator)
1169 , shmem_window(shmem_window)
1170{}
1171
1172
1173
1174template <typename T>
1175inline void
1177 const AlignedVector<T> *aligned_vector,
1178 T *ptr)
1179{
1180 (void)ptr;
1181 // It would be nice to assert that aligned_vector->elements.get() equals ptr,
1182 // but it is not guaranteed to work: clang, for example, sets elements.get()
1183 // to nullptr and then calls the deleter on a previously made copy. Hence we
1184 // must assume here that elements.get() (which is managed by the unique_ptr)
1185 // may be nullptr at this point.
1186 //
1187 // used_elements_end is a member variable of AlignedVector (i.e., we control
1188 // it, not unique_ptr) so it is still set to its correct value.
1189
1190 if (is_shmem_root)
1191 if (std::is_trivially_destructible_v<T> == false)
1192 for (T *p = aligned_vector->used_elements_end - 1; p >= ptr; --p)
1193 p->~T();
1194
1195 int ierr;
1196 ierr = MPI_Win_free(&shmem_window);
1197 AssertThrowMPI(ierr);
1198
1199 Utilities::MPI::free_communicator(shmem_group_communicator);
1200}
1201
1202# endif
1203
1204
1205template <class T>
1207 : elements(nullptr, Deleter(this))
1208 , used_elements_end(nullptr)
1209 , allocated_elements_end(nullptr)
1210 , replicated_across_communicator(false)
1211{}
1212
1213
1214
1215template <class T>
1216template <typename RandomAccessIterator, typename>
1217inline AlignedVector<T>::AlignedVector(RandomAccessIterator begin,
1218 RandomAccessIterator end)
1219 : elements(nullptr, Deleter(this))
1220 , used_elements_end(nullptr)
1221 , allocated_elements_end(nullptr)
1222 , replicated_across_communicator(false)
1223{
1224 allocate_and_move(0u, end - begin, end - begin);
1225 used_elements_end = allocated_elements_end;
1227 end,
1228 data());
1229}
1230
1231
1232template <class T>
1233inline AlignedVector<T>::AlignedVector(const size_type size, const T &init)
1234 : elements(nullptr, Deleter(this))
1235 , used_elements_end(nullptr)
1236 , allocated_elements_end(nullptr)
1237 , replicated_across_communicator(false)
1238{
1239 if (size > 0)
1240 resize(size, init);
1241}
1242
1243
1244
1245template <class T>
1247 : elements(nullptr, Deleter(this))
1248 , used_elements_end(nullptr)
1249 , allocated_elements_end(nullptr)
1250 , replicated_across_communicator(false)
1251{
1252 // copy the data from vec
1253 reserve(vec.size());
1254 used_elements_end = allocated_elements_end;
1257 elements.get());
1258}
1259
1260
1261
1262template <class T>
1265{
1266 // forward to the move operator
1267 *this = std::move(vec);
1268}
1269
1270
1271
1272template <class T>
1273inline AlignedVector<T> &
1275{
1276 const size_type new_size = vec.used_elements_end - vec.elements.get();
1277
1278 // First throw away everything and re-allocate memory but leave that
1279 // memory uninitialized for now:
1280 resize(0);
1281 reserve(new_size);
1282
1283 // Then copy the elements over by using the copy constructor on these
1284 // elements:
1287 elements.get());
1288
1289 // Finally adjust the pointer to the end of the elements that are used:
1290 used_elements_end = elements.get() + new_size;
1291
1292 return *this;
1293}
1294
1295
1296
1297template <class T>
1298inline AlignedVector<T> &
1300{
1301 clear();
1302
1303 // Move the actual data in the 'elements' object. One problem is that this
1304 // also moves the deleter object, but the deleter object
1305 // references 'this' (i.e., the 'this' pointer of the *moved-from*
1306 // object). The way this is implemented is that we have to move the
1307 // deleter as well, and then reset the pointer inside the deleter
1308 // that references the outer object.
1309 elements = std::move(vec.elements);
1310 elements.get_deleter().reset_owning_object(this);
1311
1312 // Then also steal the other pointers and clear them in the original object:
1313 used_elements_end = vec.used_elements_end;
1314 allocated_elements_end = vec.allocated_elements_end;
1315
1316 vec.used_elements_end = nullptr;
1317 vec.allocated_elements_end = nullptr;
1318
1319 return *this;
1320}
1321
1322
1323
1324template <class T>
1325inline void
1326AlignedVector<T>::resize_fast(const size_type new_size)
1327{
1328 const size_type old_size = size();
1329
1330 if (new_size == 0)
1331 clear();
1332 else if (new_size == old_size)
1333 {
1334 } // nothing to do here
1335 else if (new_size < old_size)
1336 {
1337 // call destructor on fields that are released, if the type requires it.
1338 // doing it backward releases the elements in reverse order as compared to
1339 // how they were created
1340 if (std::is_trivially_destructible_v<T> == false)
1341 for (T *p = used_elements_end - 1; p >= elements.get() + new_size; --p)
1342 p->~T();
1343 used_elements_end = elements.get() + new_size;
1344 }
1345 else // new_size > old_size
1346 {
1347 // Allocate more space, and claim that space as used
1348 reserve(new_size);
1349 used_elements_end = elements.get() + new_size;
1350
1351 // Leave the new array entries as-is (with undefined values) unless T's
1352 // default constructor is nontrivial (i.e., it is not a no-op)
1353 if (std::is_trivially_default_constructible_v<T> == false)
1355 new_size - old_size, elements.get() + old_size);
1356 }
1357}
1358
1359
1360
1361template <class T>
1362inline void
1363AlignedVector<T>::resize(const size_type new_size)
1364{
1365 const size_type old_size = size();
1366
1367 if (new_size == 0)
1368 clear();
1369 else if (new_size == old_size)
1370 {
1371 } // nothing to do here
1372 else if (new_size < old_size)
1373 {
1374 // call destructor on fields that are released, if the type requires it.
1375 // doing it backward releases the elements in reverse order as compared to
1376 // how they were created
1377 if (std::is_trivially_destructible_v<T> == false)
1378 for (T *p = used_elements_end - 1; p >= elements.get() + new_size; --p)
1379 p->~T();
1380 used_elements_end = elements.get() + new_size;
1381 }
1382 else // new_size > old_size
1383 {
1384 // Allocate more space, and claim that space as used
1385 reserve(new_size);
1386 used_elements_end = elements.get() + new_size;
1387
1388 // finally set the values to the default initializer
1390 new_size - old_size, elements.get() + old_size);
1391 }
1392}
1393
1394
1395
1396template <class T>
1397inline void
1398AlignedVector<T>::resize(const size_type new_size, const T &init)
1399{
1400 const size_type old_size = size();
1401
1402 if (new_size == 0)
1403 clear();
1404 else if (new_size == old_size)
1405 {
1406 } // nothing to do here
1407 else if (new_size < old_size)
1408 {
1409 // call destructor on fields that are released, if the type requires it.
1410 // doing it backward releases the elements in reverse order as compared to
1411 // how they were created
1412 if (std::is_trivially_destructible_v<T> == false)
1413 for (T *p = used_elements_end - 1; p >= elements.get() + new_size; --p)
1414 p->~T();
1415 used_elements_end = elements.get() + new_size;
1416 }
1417 else // new_size > old_size
1418 {
1419 // Allocate more space, and claim that space as used
1420 reserve(new_size);
1421 used_elements_end = elements.get() + new_size;
1422
1423 // finally set the desired init values
1425 new_size - old_size, init, elements.get() + old_size);
1426 }
1427}
1428
1429
1430
1431template <class T>
1432inline void
1433AlignedVector<T>::allocate_and_move(const std::size_t old_size,
1434 const std::size_t new_size,
1435 const std::size_t new_allocated_size)
1436{
1437 // allocate and align along 64-byte boundaries (this is enough for all
1438 // levels of vectorization currently supported by deal.II)
1439 T *new_data_ptr;
1440 Utilities::System::posix_memalign(reinterpret_cast<void **>(&new_data_ptr),
1441 64,
1442 new_size * sizeof(T));
1443
1444 // Now create a deleter that encodes what should happen when the object is
1445 // released: We need to destroy the objects that are currently alive (in
1446 // reverse order, and then release the memory. Note that we catch the
1447 // 'this' pointer because the number of elements currently alive might
1448 // change over time.
1449 Deleter deleter(this);
1450
1451 // copy whatever elements we need to retain
1452 if (new_allocated_size > 0)
1454 elements.get(), elements.get() + old_size, new_data_ptr);
1455
1456 // Now reset all the member variables of the current object
1457 // based on the allocation above. Assigning to a std::unique_ptr
1458 // object also releases the previously pointed to memory.
1459 //
1460 // Note that at the time of releasing the old memory, 'used_elements_end'
1461 // still points to its previous value, and this is important for the
1462 // deleter object of the previously allocated array (see how it loops over
1463 // the to-be-destroyed elements at the Deleter::DefaultDeleterAction
1464 // class).
1465 elements = decltype(elements)(new_data_ptr, std::move(deleter));
1466 used_elements_end = elements.get() + old_size;
1467 allocated_elements_end = elements.get() + new_size;
1468}
1469
1470
1471
1472template <class T>
1473inline void
1474AlignedVector<T>::reserve(const size_type new_allocated_size)
1475{
1476 const size_type old_size = used_elements_end - elements.get();
1477 const size_type old_allocated_size = allocated_elements_end - elements.get();
1478 if (new_allocated_size > old_allocated_size)
1479 {
1480 // if we continuously increase the size of the vector, we might be
1481 // reallocating a lot of times. therefore, try to increase the size more
1482 // aggressively
1483 const size_type new_size =
1484 std::max(new_allocated_size, 2 * old_allocated_size);
1485
1486 allocate_and_move(old_size, new_size, new_allocated_size);
1487 }
1488 else if (new_allocated_size == 0)
1489 clear();
1490 else // size_alloc < allocated_size
1491 {
1492 } // nothing to do here
1493}
1494
1495
1496
1497template <class T>
1498inline void
1500{
1501 if constexpr (running_in_debug_mode())
1502 {
1503 Assert(replicated_across_communicator == false,
1504 ExcAlignedVectorChangeAfterReplication());
1505 }
1506 const size_type used_size = used_elements_end - elements.get();
1507 const size_type allocated_size = allocated_elements_end - elements.get();
1508 if (allocated_size > used_size)
1509 allocate_and_move(used_size, used_size, used_size);
1510}
1511
1512
1513
1514template <class T>
1515inline void
1517{
1518 // Just release the memory (which also calls the destructor of the elements),
1519 // and then set the auxiliary pointers to invalid values.
1520 //
1521 // Note that at the time of releasing the old memory, 'used_elements_end'
1522 // still points to its previous value, and this is important for the
1523 // deleter object of the previously allocated array (see how it loops over
1524 // the to-be-destroyed elements a few lines above).
1525 elements.reset();
1526 used_elements_end = nullptr;
1527 allocated_elements_end = nullptr;
1528}
1529
1530
1531
1532template <class T>
1533inline void
1534AlignedVector<T>::push_back(const T in_data)
1535{
1536 Assert(used_elements_end <= allocated_elements_end, ExcInternalError());
1537 if (used_elements_end == allocated_elements_end)
1538 reserve(std::max(2 * capacity(), static_cast<size_type>(16)));
1539 new (used_elements_end++) T(in_data);
1540}
1541
1542
1543
1544template <class T>
1545inline typename AlignedVector<T>::reference
1547{
1548 AssertIndexRange(0, size());
1549 T *field = used_elements_end - 1;
1550 return *field;
1551}
1552
1553
1554
1555template <class T>
1558{
1559 AssertIndexRange(0, size());
1560 const T *field = used_elements_end - 1;
1561 return *field;
1562}
1563
1564
1565
1566template <class T>
1567template <typename ForwardIterator>
1568inline void
1569AlignedVector<T>::insert_back(ForwardIterator begin, ForwardIterator end)
1570{
1571 const size_type old_size = size();
1572 reserve(old_size + (end - begin));
1573 for (; begin != end; ++begin, ++used_elements_end)
1574 new (used_elements_end) T(*begin);
1575}
1576
1577
1578
1579template <class T>
1580template <typename RandomAccessIterator, typename>
1581inline typename AlignedVector<T>::iterator
1582AlignedVector<T>::insert(const_iterator position,
1583 RandomAccessIterator begin,
1584 RandomAccessIterator end)
1585{
1586 Assert(replicated_across_communicator == false,
1587 ExcAlignedVectorChangeAfterReplication());
1588 Assert(this->begin() <= position && position <= this->end(),
1589 ExcMessage("The position iterator is not valid."));
1590 const auto offset = position - this->begin();
1591
1592 const size_type old_size = size();
1593 const size_type range_size = end - begin;
1594 const size_type new_size = old_size + range_size;
1595 if (range_size != 0)
1596 {
1597 // This is similar to allocate_and_move(), except that we need to move
1598 // whatever was before position and whatever is after it into two
1599 // different places
1600 T *new_data_ptr = nullptr;
1602 reinterpret_cast<void **>(&new_data_ptr), 64, new_size * sizeof(T));
1603
1604 // Correctly handle the case where the range is inside the present array
1605 // by creating a temporary.
1606 AlignedVector<T> temporary(begin, end);
1608 elements.get(), elements.get() + offset, new_data_ptr);
1610 temporary.begin(), temporary.end(), new_data_ptr + offset);
1612 elements.get() + offset,
1613 elements.get() + old_size,
1614 new_data_ptr + offset + range_size);
1615
1616 Deleter deleter(this);
1617 elements = decltype(elements)(new_data_ptr, std::move(deleter));
1618 used_elements_end = elements.get() + new_size;
1619 allocated_elements_end = elements.get() + new_size;
1620 }
1621 return this->begin() + offset;
1622}
1623
1624
1625
1626template <class T>
1627inline void
1629{
1631 elements.get());
1632}
1633
1634
1635
1636template <class T>
1637inline void
1639{
1641 value,
1642 elements.get());
1643}
1644
1645
1646
1647template <class T>
1648inline void
1650 const unsigned int root_process)
1651{
1652# ifdef DEAL_II_WITH_MPI
1653
1654 // Let the root process broadcast its size. If it is zero, then all
1655 // processes just clear() their memory and reset themselves to a non-shared
1656 // empty object -- there is no point to run through complicated MPI
1657 // calls if the end result is an empty array. Otherwise, we continue on.
1658 const size_type new_size =
1659 Utilities::MPI::broadcast(communicator, size(), root_process);
1660 if (new_size == 0)
1661 {
1662 clear();
1663 return;
1664 }
1665
1666
1667 // **** Step 0 ****
1668 // All but the root process no longer need their data, so release the memory
1669 // used to store the previous elements.
1670 if (Utilities::MPI::this_mpi_process(communicator) != root_process)
1671 {
1672 elements.reset();
1673 used_elements_end = nullptr;
1674 allocated_elements_end = nullptr;
1675 }
1676
1677 // **** Step 1 ****
1678 // Create communicators for each group of processes that can use
1679 // shared memory areas. Within each of these groups, we don't care about
1680 // which rank each of the old processes gets except that we would like to
1681 // make sure that the (global) root process will have rank=0 within
1682 // its own sub-communicator. We can do that through the third argument of
1683 // MPI_Comm_split_type (the "key") which is an integer meant to indicate the
1684 // order of processes within the split communicators, and we should set it to
1685 // zero for the root processes and one for all others -- which means that
1686 // for all of these other processes, MPI can choose whatever order it
1687 // wants because they have the same key (MPI then documents that these ties
1688 // will be broken according to these processes' rank in the old group).
1689 //
1690 // At least that's the theory. In practice, the MPI implementation where
1691 // this function was developed on does not seem to do that. (Bug report
1692 // is here: https://github.com/open-mpi/ompi/issues/8854)
1693 // We work around this by letting MPI_Comm_split_type choose whatever
1694 // rank it wants, and then reshuffle with MPI_Comm_split in a second
1695 // step -- not elegant, nor efficient, but seems to work:
1696 MPI_Comm shmem_group_communicator;
1697 {
1698 MPI_Comm shmem_group_communicator_temp;
1699 int ierr = MPI_Comm_split_type(communicator,
1700 MPI_COMM_TYPE_SHARED,
1701 /* key */ 0,
1702 MPI_INFO_NULL,
1703 &shmem_group_communicator_temp);
1704 AssertThrowMPI(ierr);
1705
1706 const int key =
1707 (Utilities::MPI::this_mpi_process(communicator) == root_process ? 0 : 1);
1708 ierr = MPI_Comm_split(shmem_group_communicator_temp,
1709 /* color */ 0,
1710 key,
1711 &shmem_group_communicator);
1712 AssertThrowMPI(ierr);
1713
1714 // Verify the explanation from above
1715 if (Utilities::MPI::this_mpi_process(communicator) == root_process)
1716 Assert(Utilities::MPI::this_mpi_process(shmem_group_communicator) == 0,
1718
1719 // And get rid of the temporary communicator
1720 Utilities::MPI::free_communicator(shmem_group_communicator_temp);
1721 }
1722 const bool is_shmem_root =
1723 Utilities::MPI::this_mpi_process(shmem_group_communicator) == 0;
1724
1725 // **** Step 2 ****
1726 // We then have to send the state of the current object from the
1727 // root process to one exemplar in each shmem group. To this end,
1728 // we create another subcommunicator that includes the ranks zero
1729 // of all shmem groups, and because of the trick above, we know
1730 // that this also includes the original root process.
1731 //
1732 // There are different ways of creating a "shmem_roots_communicator".
1733 // The conceptually easiest way is to create an MPI_Group that only
1734 // includes the shmem roots and then create a communicator from this
1735 // via MPI_Comm_create or MPI_Comm_create_group. The problem
1736 // with this is that we would have to exchange among all processes
1737 // which ones are shmem roots and which are not. This is awkward.
1738 //
1739 // A simpler way is to use MPI_Comm_split that uses "colors" to
1740 // indicate which sub-communicator each process wants to be in.
1741 // We use color=0 to indicate the group of shmem roots, and color=1
1742 // for all other processes -- the latter will simply not ever do
1743 // anything among themselves with the communicator so created.
1744 //
1745 // Using MPI_Comm_split has the additional benefit that, just as above,
1746 // we can choose where each rank will end up in shmem_roots_communicator.
1747 // We again set key=0 for the original root_process, and key=1 for all other
1748 // ranks; then, the global root becomes rank=0 on the
1749 // shmem_roots_communicator. We don't care how the other processes are
1750 // ordered.
1751 MPI_Comm shmem_roots_communicator;
1752 {
1753 const int key =
1754 (Utilities::MPI::this_mpi_process(communicator) == root_process ? 0 : 1);
1755
1756 const int ierr = MPI_Comm_split(communicator,
1757 /*color=*/
1758 (is_shmem_root ? 0 : 1),
1759 key,
1760 &shmem_roots_communicator);
1761 AssertThrowMPI(ierr);
1762
1763 // Again verify the explanation from above
1764 if (Utilities::MPI::this_mpi_process(communicator) == root_process)
1765 Assert(Utilities::MPI::this_mpi_process(shmem_roots_communicator) == 0,
1767 }
1768
1769 const unsigned int shmem_roots_root_rank = 0;
1770 const bool is_shmem_roots_root =
1771 (is_shmem_root && (Utilities::MPI::this_mpi_process(
1772 shmem_roots_communicator) == shmem_roots_root_rank));
1773
1774 // Now let the original root_process broadcast the current object to all
1775 // shmem roots. We know that the last rank is the original root process that
1776 // has all of the data.
1777 if (is_shmem_root)
1778 {
1779 if constexpr (Utilities::MPI::is_mpi_type<T>)
1780 {
1781 // The data is an MPI type, so it can be sent as an array
1782 // without having to go through the
1783 // serialization/deserialization machinery. We want to use
1784 // Utilities::MPI::broadcast() over MPI_Bcast() because the
1785 // former can deal with more elements than just a signed int
1786 // can hold. That's useful for very large tables.
1787 //
1788 // In that case, first tell all of the other shmem roots how many
1789 // elements we will have to deal with, and let them resize their
1790 // (non-shared) arrays.
1791 const size_type new_size =
1792 Utilities::MPI::broadcast(shmem_roots_communicator,
1793 size(),
1794 shmem_roots_root_rank);
1795 if (is_shmem_roots_root == false)
1796 resize(new_size);
1797
1798 // Then directly copy from the root process into these buffers
1799
1800 Utilities::MPI::broadcast(elements.get(),
1801 new_size,
1802 shmem_roots_root_rank,
1803 shmem_roots_communicator);
1804 }
1805 else
1806 {
1807 // The objects to be sent around are not "trivial", and so we have
1808 // to go through the serialization/deserialization machinery. On all
1809 // but the sending process, overwrite the current state with the
1810 // vector just broadcast.
1811 //
1812 // On the root rank, this would lead to resetting the 'entries'
1813 // pointer, which would trigger the deleter which would lead to a
1814 // deadlock. So we just send the result of the broadcast() call to
1815 // nirvana on the root process and keep our current state.
1816 if (Utilities::MPI::this_mpi_process(shmem_roots_communicator) == 0)
1817 std::ignore = Utilities::MPI::broadcast(shmem_roots_communicator,
1818 *this,
1819 shmem_roots_root_rank);
1820 else
1821 *this = Utilities::MPI::broadcast(shmem_roots_communicator,
1822 *this,
1823 shmem_roots_root_rank);
1824 }
1825 }
1826
1827 // We no longer need the shmem roots communicator, so get rid of it
1828 Utilities::MPI::free_communicator(shmem_roots_communicator);
1829
1830
1831 // **** Step 3 ****
1832 // At this point, all shmem groups have one shmem root process that has
1833 // a copy of the data. This is the point where each shmem group should
1834 // establish a shmem area to put the data into. As mentioned above,
1835 // we know that the shmem roots are the last rank in their respective
1836 // shmem_group_communicator.
1837 //
1838 // The process for all of this works as follows: While all processes in
1839 // the shmem group participate in the generation of the shmem memory window,
1840 // only the shmem root actually allocates any memory -- the rest just
1841 // allocate zero bytes of their own. We allocate space for exactly
1842 // size() elements (computed on the shmem_root that already has the data)
1843 // and add however many bytes are necessary so that we know that we can align
1844 // things to 64-byte boundaries. The worst case happens if the memory system
1845 // gives us a pointer to an address one byte past a desired alignment
1846 // boundary, and in that case aligning the memory will require us to waste the
1847 // first (align_by-1) bytes. So we have to ask for
1848 // size() * sizeof(T) + (align_by - 1)
1849 // bytes.
1850 //
1851 // Before MPI 4.0, there was no way to specify that we want memory aligned to
1852 // a certain number of bytes. This is going to come back to bite us further
1853 // down below when we try to get a properly aligned pointer to our memory
1854 // region, see the commentary there. Starting with MPI 4.0, one can set a
1855 // flag in an MPI_Info structure that requests a desired alignment, so we do
1856 // this for forward compatibility; MPI implementations ignore flags they don't
1857 // know anything about, and so setting this flag is backward compatible also
1858 // to older MPI versions.
1859 MPI_Win shmem_window;
1860 void *base_ptr;
1861 const MPI_Aint align_by = 64;
1862 const MPI_Aint alloc_size =
1863 Utilities::MPI::broadcast(shmem_group_communicator,
1864 (size() * sizeof(T) + (align_by - 1)),
1865 0);
1866
1867 {
1868 int ierr;
1869
1870 MPI_Info mpi_info;
1871 ierr = MPI_Info_create(&mpi_info);
1872 AssertThrowMPI(ierr);
1873 ierr = MPI_Info_set(mpi_info,
1874 "mpi_minimum_memory_alignment",
1875 std::to_string(align_by).c_str());
1876 AssertThrowMPI(ierr);
1877 ierr = MPI_Win_allocate_shared((is_shmem_root ? alloc_size : 0),
1878 /* disp_unit = */ 1,
1879 mpi_info,
1880 shmem_group_communicator,
1881 &base_ptr,
1882 &shmem_window);
1883 AssertThrowMPI(ierr);
1884
1885 ierr = MPI_Info_free(&mpi_info);
1886 AssertThrowMPI(ierr);
1887 }
1888
1889
1890 // **** Step 4 ****
1891 // The next step is to teach all non-shmem root processes what the pointer to
1892 // the array is that the shmem-root created. MPI has a nifty way for this
1893 // given that only a single process actually allocated memory in the window:
1894 // When calling MPI_Win_shared_query, the MPI documentation says that
1895 // "When rank is MPI_PROC_NULL, the pointer, disp_unit, and size returned are
1896 // the pointer, disp_unit, and size of the memory segment belonging the lowest
1897 // rank that specified size > 0. If all processes in the group attached to the
1898 // window specified size = 0, then the call returns size = 0 and a baseptr as
1899 // if MPI_ALLOC_MEM was called with size = 0."
1900 //
1901 // This will allow us to obtain the pointer to the shmem root's memory area,
1902 // which is the only one we care about. (None of the other processes have
1903 // even allocated any memory.)
1904 //
1905 // We don't need to do this on the shmem root process: This process has
1906 // already gotten its base_ptr correctly set above, and we can determine the
1907 // array size by just calling size().
1908 if (is_shmem_root == false)
1909 {
1910 int disp_unit;
1911 MPI_Aint alloc_size; // not actually used
1912 const int ierr = MPI_Win_shared_query(
1913 shmem_window, MPI_PROC_NULL, &alloc_size, &disp_unit, &base_ptr);
1914 AssertThrowMPI(ierr);
1915
1916 // Make sure we actually got a pointer, and check that the disp_unit is
1917 // equal to 1 (as set above)
1918 Assert(base_ptr != nullptr, ExcInternalError());
1919 Assert(disp_unit == 1, ExcInternalError());
1920 }
1921
1922
1923 // **** Step 5 ****
1924 // Now that all processes know the address of the space that is visible to
1925 // everyone, we need to figure out whether it is properly aligned and if not,
1926 // find the next aligned address.
1927 //
1928 // std::align does that, but it also modifies its last two arguments. The
1929 // documentation of that function at
1930 // https://en.cppreference.com/w/cpp/memory/align is not entirely clear, but I
1931 // *think* that the following should do given that we do not use base_ptr and
1932 // available_space any further after the call to std::align.
1933 std::size_t available_space = alloc_size;
1934 void *base_ptr_backup = base_ptr;
1935 T *aligned_shmem_pointer = static_cast<T *>(
1936 std::align(align_by, new_size * sizeof(T), base_ptr, available_space));
1937 Assert(aligned_shmem_pointer != nullptr, ExcInternalError());
1938
1939 // There is one step to guard against. It is *conceivable* that the base_ptr
1940 // we have previously obtained from MPI_Win_shared_query is mapped so
1941 // awkwardly into the different MPI processes' memory spaces that it is
1942 // aligned in one memory space, but not another. In that case, different
1943 // processes would align base_ptr differently, and adjust available_space
1944 // differently. We can check that by making sure that the max (or min) over
1945 // all processes is equal to every process's value. If that's not the case,
1946 // then the whole idea of aligning above is wrong and we need to rethink what
1947 // it means to align data in a shared memory space.
1948 //
1949 // One might be tempted to think that this is not how MPI implementations
1950 // actually arrange things. Alas, when developing this functionality in 2021,
1951 // this is really how at least OpenMPI ends up doing things. (This is with an
1952 // OpenMPI implementation of MPI 3.1, so it does not support the flag we set
1953 // in the MPI_Info structure above when allocating the memory window.) Indeed,
1954 // when running this code on three processes, one ends up with base_ptr values
1955 // of
1956 // base_ptr=0x7f0842f02108
1957 // base_ptr=0x7fc0a47881d0
1958 // base_ptr=0x7f64872db108
1959 // which, most annoyingly, are aligned to 8 and 16 byte boundaries -- so there
1960 // is no common offset std::align could find that leads to a 64-byte
1961 // aligned memory address in all three memory spaces. That's a tremendous
1962 // nuisance and there is really nothing we can do about this other than just
1963 // fall back on the (unaligned) base_ptr in that case.
1964 if (Utilities::MPI::min(available_space, shmem_group_communicator) !=
1965 Utilities::MPI::max(available_space, shmem_group_communicator))
1966 aligned_shmem_pointer = static_cast<T *>(base_ptr_backup);
1967
1968
1969 // **** Step 6 ****
1970 // If this is the shmem root process, we need to copy the data into the
1971 // shared memory space.
1972 if (is_shmem_root)
1973 {
1974 if (std::is_trivially_copyable_v<T> == true)
1975 std::memcpy(aligned_shmem_pointer, elements.get(), sizeof(T) * size());
1976 else
1977 for (std::size_t i = 0; i < size(); ++i)
1978 new (&aligned_shmem_pointer[i]) T(std::move(elements[i]));
1979 }
1980
1981 // Make sure that the shared memory host has copied the data before we try to
1982 // access it.
1983 const int ierr = MPI_Barrier(shmem_group_communicator);
1984 AssertThrowMPI(ierr);
1985
1986 // **** Step 7 ****
1987 // Finally, we need to set the pointers of this object to what we just
1988 // learned. This also releases all memory that may have been in use
1989 // previously.
1990 //
1991 // The part that is a bit tricky is how to write the deleter of this
1992 // shared memory object. When we want to get rid of it, we need to
1993 // also release the MPI_Win object along with the shmem_group_communicator
1994 // object. That's because as long as we use the shared memory, we still need
1995 // to hold on to the MPI_Win object, and the MPI_Win object is based on the
1996 // communicator. (The former is definitely true, the latter is not quite clear
1997 // from the MPI documentation, but seems reasonable.) So we need to have a
1998 // deleter for the pointer that ensures that upon release of the memory, we
1999 // not only call the destructor of these memory elements (but only once, on
2000 // the shmem root!) but also destroy the MPI_Win and the communicator. All of
2001 // that is encapsulated in the following call where the deleter makes copies
2002 // of the arguments in the lambda capture.
2003 elements = decltype(elements)(aligned_shmem_pointer,
2004 Deleter(this,
2005 is_shmem_root,
2006 aligned_shmem_pointer,
2007 shmem_group_communicator,
2008 shmem_window));
2009
2010 // We then also have to set the other two pointers that define the state of
2011 // the current object. Note that the new buffer size is exactly as large as
2012 // necessary, i.e., can store size() elements, regardless of the number of
2013 // allocated elements in the original objects.
2014 used_elements_end = elements.get() + new_size;
2015 allocated_elements_end = used_elements_end;
2016
2017 // **** Consistency check ****
2018 // At this point, each process should have a copy of the data.
2019 // Verify this in some sort of round-about way
2020 if constexpr (running_in_debug_mode())
2021 {
2022 replicated_across_communicator = true;
2023 const std::vector<char> packed_data = Utilities::pack(*this);
2024 const int hash =
2025 std::accumulate(packed_data.begin(), packed_data.end(), int(0));
2026 Assert(Utilities::MPI::max(hash, communicator) == hash,
2028 }
2029
2030# else
2031 // No MPI -> nothing to replicate
2032 (void)communicator;
2033 (void)root_process;
2034# endif
2035}
2036
2037
2038
2039template <class T>
2040inline void
2042{
2043 // Swap the data in the 'elements' objects. Then also make sure that
2044 // their respective deleter objects point to the right place.
2045 std::swap(elements, vec.elements);
2046 elements.get_deleter().reset_owning_object(this);
2047 vec.elements.get_deleter().reset_owning_object(&vec);
2048
2049 // Now also swap the remaining members.
2050 std::swap(used_elements_end, vec.used_elements_end);
2051 std::swap(allocated_elements_end, vec.allocated_elements_end);
2052}
2053
2054
2055
2056template <class T>
2057inline bool
2059{
2060 return used_elements_end == elements.get();
2061}
2062
2063
2064
2065template <class T>
2066inline typename AlignedVector<T>::size_type
2068{
2069 return used_elements_end - elements.get();
2070}
2071
2072
2073
2074template <class T>
2075inline typename AlignedVector<T>::size_type
2077{
2078 return allocated_elements_end - elements.get();
2079}
2080
2081
2082
2083template <class T>
2084inline typename AlignedVector<T>::reference
2085AlignedVector<T>::operator[](const size_type index)
2086{
2088 return elements[index];
2089}
2090
2091
2092
2093template <class T>
2095AlignedVector<T>::operator[](const size_type index) const
2096{
2098 return elements[index];
2099}
2100
2101
2102
2103template <typename T>
2104inline typename AlignedVector<T>::pointer
2106{
2107 return elements.get();
2108}
2109
2110
2111
2112template <typename T>
2113inline typename AlignedVector<T>::const_pointer
2115{
2116 return elements.get();
2117}
2118
2119
2120
2121template <class T>
2122inline typename AlignedVector<T>::iterator
2124{
2125 return elements.get();
2126}
2127
2128
2129
2130template <class T>
2131inline typename AlignedVector<T>::iterator
2133{
2134 return used_elements_end;
2135}
2136
2137
2138
2139template <class T>
2142{
2143 return elements.get();
2144}
2145
2146
2147
2148template <class T>
2151{
2152 return used_elements_end;
2153}
2154
2155
2156
2157template <class T>
2158template <class Archive>
2159inline void
2160AlignedVector<T>::save(Archive &ar, const unsigned int) const
2161{
2162 size_type vec_size = size();
2163 ar &vec_size;
2164 if (vec_size > 0)
2165 {
2166 if constexpr (std::is_trivially_copyable_v<T>)
2167 ar &boost::serialization::make_binary_object(elements.get(),
2168 vec_size * sizeof(T));
2169 else
2170 ar &boost::serialization::make_array(elements.get(), vec_size);
2171 }
2172}
2173
2174
2175
2176template <class T>
2177template <class Archive>
2178inline void
2179AlignedVector<T>::load(Archive &ar, const unsigned int)
2180{
2181 size_type vec_size = 0;
2182 ar &vec_size;
2183
2184 resize(vec_size);
2185
2186 if (vec_size > 0)
2187 {
2188 if constexpr (std::is_trivially_copyable_v<T>)
2189 ar &boost::serialization::make_binary_object(elements.get(),
2190 vec_size * sizeof(T));
2191 else
2192 ar &boost::serialization::make_array(elements.get(), vec_size);
2193 }
2194}
2195
2196
2197
2198template <class T>
2199inline typename AlignedVector<T>::size_type
2201{
2202 size_type memory = sizeof(*this);
2203 for (const T *t = elements.get(); t != used_elements_end; ++t)
2205 memory += sizeof(T) * (allocated_elements_end - used_elements_end);
2206 return memory;
2207}
2208
2209
2210#endif // ifndef DOXYGEN
2211
2212
2218template <class T>
2219bool
2221{
2222 if (lhs.size() != rhs.size())
2223 return false;
2224 for (typename AlignedVector<T>::const_iterator lit = lhs.begin(),
2225 rit = rhs.begin();
2226 lit != lhs.end();
2227 ++lit, ++rit)
2228 if (*lit != *rit)
2229 return false;
2230 return true;
2231}
2232
2233
2234
2240template <class T>
2241bool
2243{
2244 return !(operator==(lhs, rhs));
2245}
2246
2247
2249
2250#endif
*  iterator end()
*  *  iterator begin()
bool operator==(const AlignedVector< T > &lhs, const AlignedVector< T > &rhs)
virtual void delete_array(const AlignedVector< T > *owning_aligned_vector, T *ptr)=0
virtual void delete_array(const AlignedVector< T > *aligned_vector, T *ptr) override
MPISharedMemDeleterAction(const bool is_shmem_root, T *aligned_shmem_pointer, MPI_Comm shmem_group_communicator, MPI_Win shmem_window)
void operator()(T *ptr)
Deleter(AlignedVector< T > *owning_object, const bool is_shmem_root, T *aligned_shmem_pointer, MPI_Comm shmem_group_communicator, MPI_Win shmem_window)
Deleter(AlignedVector< T > *owning_object)
std::unique_ptr< DeleterActionBase > deleter_action_object
void reset_owning_object(const AlignedVector< T > *new_aligned_vector_ptr)
const AlignedVector< T > * owning_aligned_vector
iterator end()
void replicate_across_communicator(const MPI_Comm communicator, const unsigned int root_process)
size_type memory_consumption() const
void resize_fast(const size_type new_size)
std::unique_ptr< T[], Deleter > elements
reference operator[](const size_type index)
void fill(const T &element)
iterator begin()
const_iterator end() const
void allocate_and_move(const std::size_t old_size, const std::size_t new_size, const std::size_t new_allocated_size)
~AlignedVector()=default
AlignedVector(AlignedVector< T > &&vec) noexcept
void reserve(const size_type new_allocated_size)
void serialize(Archive &archive, const unsigned int version)
void shrink_to_fit()
bool operator!=(const AlignedVector< T > &lhs, const AlignedVector< T > &rhs)
const_reference operator[](const size_type index) const
pointer data()
void resize(const size_type new_size, const T &init)
AlignedVector & operator=(AlignedVector< T > &&vec) noexcept
AlignedVector(RandomAccessIterator begin, RandomAccessIterator end)
size_type capacity() const
value_type & reference
AlignedVector & operator=(const AlignedVector< T > &vec)
void swap(AlignedVector< T > &vec) noexcept
const value_type * const_pointer
bool replicated_across_communicator
void push_back(const T in_data)
iterator insert(const_iterator position, RandomAccessIterator begin, RandomAccessIterator end)
const_iterator begin() const
AlignedVector(const size_type size, const T &init=T())
bool empty() const
AlignedVector(const AlignedVector< T > &vec)
size_type size() const
const_reference back() const
std::size_t size_type
bool operator==(const AlignedVector< T > &lhs, const AlignedVector< T > &rhs)
const value_type * const_iterator
void resize(const size_type new_size)
void load(Archive &ar, const unsigned int version)
void save(Archive &ar, const unsigned int version) const
void insert_back(ForwardIterator begin, ForwardIterator end)
const value_type & const_reference
reference back()
const_pointer data() const
value_type * pointer
value_type * iterator
AlignedVectorCopyConstruct(RandomAccessIterator source_begin, RandomAccessIterator source_end, T *const destination)
static const std::size_t minimum_parallel_grain_size
virtual void apply_to_subrange(const std::size_t begin, const std::size_t end) const override
AlignedVectorDefaultInitialize(const std::size_t size, T *const destination)
static const std::size_t minimum_parallel_grain_size
virtual void apply_to_subrange(const std::size_t begin, const std::size_t end) const override
void default_construct_or_assign(const std::size_t begin, const std::size_t end, std::bool_constant< false >) const
void default_construct_or_assign(const std::size_t begin, const std::size_t end, std::bool_constant< true >) const
void copy_construct_or_assign(const std::size_t begin, const std::size_t end, std::bool_constant< false >) const
static const std::size_t minimum_parallel_grain_size
virtual void apply_to_subrange(const std::size_t begin, const std::size_t end) const override
void copy_construct_or_assign(const std::size_t begin, const std::size_t end, std::bool_constant< true >) const
AlignedVectorInitialize(const std::size_t size, const T &element, T *const destination)
AlignedVectorMoveConstruct(RandomAccessIterator source_begin, RandomAccessIterator source_end, T *const destination)
virtual void apply_to_subrange(const std::size_t begin, const std::size_t end) const override
static const std::size_t minimum_parallel_grain_size
#define DEAL_II_NAMESPACE_OPEN
Definition config.h:38
constexpr bool running_in_debug_mode()
Definition config.h:76
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:39
#define Assert(cond, exc)
#define AssertThrowMPI(error_code)
#define AssertIndexRange(index, range)
#define DeclExceptionMsg(Exception, defaulttext)
static ::ExceptionBase & ExcInternalError()
static ::ExceptionBase & ExcMessage(std::string arg1)
static ::ExceptionBase & ExcAlignedVectorChangeAfterReplication()
std::vector< index_type > data
Definition mpi.cc:734
std::size_t size
Definition mpi.cc:733
constexpr char T
std::enable_if_t< std::is_fundamental_v< T >, std::size_t > memory_consumption(const T &t)
*  state_parameters reserve(get_material_parameter_count())
T max(const T &t, const MPI_Comm mpi_communicator)
T min(const T &t, const MPI_Comm mpi_communicator)
unsigned int this_mpi_process(const MPI_Comm mpi_communicator)
Definition mpi.cc:118
void free_communicator(MPI_Comm mpi_communicator)
Definition mpi.cc:165
T broadcast(const MPI_Comm comm, const T &object_to_send, const unsigned int root_process=0)
void posix_memalign(void **memptr, std::size_t alignment, std::size_t size)
std::size_t pack(const T &object, std::vector< char > &dest_buffer, const bool allow_compression=true)
Definition utilities.h:1352
STL namespace.
::VectorizedArray< Number, width > max(const ::VectorizedArray< Number, width > &, const ::VectorizedArray< Number, width > &)
void apply_parallel(const std::size_t begin, const std::size_t end, const std::size_t minimum_parallel_grain_size) const
Definition parallel.h:636