deal.II version GIT relicensing-2581-gae2745de1b 2025-02-07 22:30:00+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: LGPL-2.1-or-later
4// Copyright (C) 2014 - 2024 by the deal.II authors
5//
6// This file is part of the deal.II library.
7//
8// Part of the source code is dual licensed under Apache-2.0 WITH
9// LLVM-exception OR LGPL-2.1-or-later. Detailed license information
10// governing the source code and code contributions can be found in
11// LICENSE.md and CONTRIBUTING.md at the top level directory of deal.II.
12//
13// ------------------------------------------------------------------------
14
15
16#ifndef dealii_aligned_vector_h
17#define dealii_aligned_vector_h
18
19#include <deal.II/base/config.h>
20
23#include <deal.II/base/mpi.h>
26
27// boost::serialization::make_array used to be in array.hpp, but was
28// moved to a different file in BOOST 1.64
29#include <boost/version.hpp>
30#if BOOST_VERSION >= 106400
31# include <boost/serialization/array_wrapper.hpp>
32#else
33# include <boost/serialization/array.hpp>
34#endif
35#include <boost/serialization/split_member.hpp>
36
37#include <cstring>
38#include <memory>
39#include <type_traits>
40
41
42
44
45
59template <class T>
61{
62public:
67 using value_type = T;
69 using const_pointer = const value_type *;
71 using const_iterator = const value_type *;
73 using const_reference = const value_type &;
74 using size_type = std::size_t;
75
80
89 template <
90 typename RandomAccessIterator,
91 typename = std::enable_if_t<std::is_convertible_v<
92 typename std::iterator_traits<RandomAccessIterator>::iterator_category,
93 std::random_access_iterator_tag>>>
94 AlignedVector(RandomAccessIterator begin, RandomAccessIterator end);
95
102 explicit AlignedVector(const size_type size, const T &init = T());
103
107 ~AlignedVector() = default;
108
115
121
129
135
155 void
156 resize_fast(const size_type new_size);
157
170 void
171 resize(const size_type new_size);
172
188 void
189 resize(const size_type new_size, const T &init);
190
211 void
212 reserve(const size_type new_allocated_size);
213
217 void
219
224 void
226
232 void
233 push_back(const T in_data);
234
240
245 back() const;
246
251 template <typename ForwardIterator>
252 void
253 insert_back(ForwardIterator begin, ForwardIterator end);
254
263 template <
264 typename RandomAccessIterator,
265 typename = std::enable_if_t<std::is_convertible_v<
266 typename std::iterator_traits<RandomAccessIterator>::iterator_category,
267 std::random_access_iterator_tag>>>
270 RandomAccessIterator begin,
271 RandomAccessIterator end);
272
282 void
284
293 void
294 fill(const T &element);
295
383 void
385 const unsigned int root_process);
386
390 void
391 swap(AlignedVector<T> &vec) noexcept;
392
396 bool
397 empty() const;
398
403 size() const;
404
410 capacity() const;
411
416 operator[](const size_type index);
417
422 operator[](const size_type index) const;
423
427 pointer
429
434 data() const;
435
441
447
452 begin() const;
453
458 end() const;
459
467
473 template <class Archive>
474 void
475 save(Archive &ar, const unsigned int version) const;
476
482 template <class Archive>
483 void
484 load(Archive &ar, const unsigned int version);
485
486#ifdef DOXYGEN
492 template <class Archive>
493 void
494 serialize(Archive &archive, const unsigned int version);
495#else
496 // This macro defines the serialize() method that is compatible with
497 // the templated save() and load() method that have been implemented.
498 BOOST_SERIALIZATION_SPLIT_MEMBER()
499#endif
500
508 "Changing the vector after a call to "
509 "replicate_across_communicator() is not allowed.");
510
511private:
516 void
517 allocate_and_move(const size_t old_size,
518 const size_t new_size,
519 const size_t new_allocated_size);
520
613 {
614 public:
620 Deleter(AlignedVector<T> *owning_object);
621
622#ifdef DEAL_II_WITH_MPI
630 Deleter(AlignedVector<T> *owning_object,
631 const bool is_shmem_root,
632 T *aligned_shmem_pointer,
633 MPI_Comm shmem_group_communicator,
634 MPI_Win shmem_window);
635#endif
636
642 void
643 operator()(T *ptr);
644
652 void
653 reset_owning_object(const AlignedVector<T> *new_aligned_vector_ptr);
654
655 private:
660 {
661 public:
665 virtual ~DeleterActionBase() = default;
666
672 virtual void
674 };
675
676#ifdef DEAL_II_WITH_MPI
677
683 {
684 public:
692 MPI_Win shmem_window);
693
699 virtual void
700 delete_array(const AlignedVector<T> *aligned_vector, T *ptr);
701
702 private:
707 const bool is_shmem_root;
711 };
712#endif
713
718 std::unique_ptr<DeleterActionBase> deleter_action_object;
719
725 };
726
730 std::unique_ptr<T[], Deleter> elements;
731
736
741
746};
747
748
749// ------------------------------- inline functions --------------------------
750
756namespace internal
757{
776 template <typename RandomAccessIterator, typename T>
779 {
780 static const std::size_t minimum_parallel_grain_size =
781 160000 / sizeof(T) + 1;
782
783 public:
793 AlignedVectorCopyConstruct(RandomAccessIterator source_begin,
794 RandomAccessIterator source_end,
795 T *const destination)
796 : source_(source_begin)
797 , destination_(destination)
798 {
799 Assert(source_end >= source_begin, ExcInternalError());
800 Assert(source_end == source_begin || destination != nullptr,
802 const std::size_t size = source_end - source_begin;
805 else
807 }
808
813 virtual void
814 apply_to_subrange(const std::size_t begin,
815 const std::size_t end) const override
816 {
817 if (end == begin)
818 return;
819
820 // We can use memcpy() with trivially copyable objects.
821 if constexpr (std::is_trivially_copyable_v<T> == true &&
822 (std::is_same_v<T *, RandomAccessIterator> ||
823 std::is_same_v<const T *, RandomAccessIterator>) == true)
824 std::memcpy(destination_ + begin,
825 source_ + begin,
826 (end - begin) * sizeof(T));
827 else
828 for (std::size_t i = begin; i < end; ++i)
829 new (&destination_[i]) T(*(source_ + i));
830 }
831
832 private:
833 RandomAccessIterator source_;
834 T *const destination_;
835 };
836
837
844 template <typename RandomAccessIterator, typename T>
847 {
848 static const std::size_t minimum_parallel_grain_size =
849 160000 / sizeof(T) + 1;
850
851 public:
861 AlignedVectorMoveConstruct(RandomAccessIterator source_begin,
862 RandomAccessIterator source_end,
863 T *const destination)
864 : source_(source_begin)
865 , destination_(destination)
866 {
867 Assert(source_end >= source_begin, ExcInternalError());
868 Assert(source_end == source_begin || destination != nullptr,
870 const std::size_t size = source_end - source_begin;
873 else
875 }
876
881 virtual void
882 apply_to_subrange(const std::size_t begin,
883 const std::size_t end) const override
884 {
885 if (end == begin)
886 return;
887
888 // We can use memcpy() with trivially copyable objects.
889 if constexpr (std::is_trivially_copyable_v<T> == true &&
890 (std::is_same_v<T *, RandomAccessIterator> ||
891 std::is_same_v<const T *, RandomAccessIterator>) == true)
892 std::memcpy(destination_ + begin,
893 source_ + begin,
894 (end - begin) * sizeof(T));
895 else
896 // For everything else just use the move constructor. The original
897 // object remains alive and will be destroyed elsewhere.
898 for (std::size_t i = begin; i < end; ++i)
899 new (&destination_[i]) T(std::move(*(source_ + i)));
900 }
901
902 private:
903 RandomAccessIterator source_;
904 T *const destination_;
905 };
906
907
925 template <typename T, bool initialize_memory>
927 {
928 static const std::size_t minimum_parallel_grain_size =
929 160000 / sizeof(T) + 1;
930
931 public:
936 AlignedVectorInitialize(const std::size_t size,
937 const T &element,
938 T *const destination)
939 : element_(element)
940 , destination_(destination)
941 , trivial_element(false)
942 {
943 if (size == 0)
944 return;
945 Assert(destination != nullptr, ExcInternalError());
946
947 // do not use memcmp() for long double because on some systems it does not
948 // completely fill its memory and may lead to false positives in e.g.
949 // valgrind
950 if constexpr (std::is_trivially_default_constructible_v<T> == true &&
951 std::is_same_v<T, long double> == false)
952 {
953 const unsigned char zero[sizeof(T)] = {};
954 if (std::memcmp(zero, &element, sizeof(T)) == 0)
955 trivial_element = true;
956 }
959 else
961 }
962
966 virtual void
967 apply_to_subrange(const std::size_t begin,
968 const std::size_t end) const override
969 {
970 // Only use memset() with types whose default constructors don't do
971 // anything.
972 if constexpr (std::is_trivially_default_constructible_v<T> == true)
973 if (trivial_element)
974 {
975 std::memset(destination_ + begin, 0, (end - begin) * sizeof(T));
976 return;
977 }
978
980 end,
981 std::bool_constant<initialize_memory>());
982 }
983
984 private:
985 const T &element_;
986 mutable T *destination_;
988
989 // copy assignment operation
990 void
991 copy_construct_or_assign(const std::size_t begin,
992 const std::size_t end,
993 std::bool_constant<false>) const
994 {
995 for (std::size_t i = begin; i < end; ++i)
997 }
998
999 // copy constructor (memory initialization)
1000 void
1001 copy_construct_or_assign(const std::size_t begin,
1002 const std::size_t end,
1003 std::bool_constant<true>) const
1004 {
1005 for (std::size_t i = begin; i < end; ++i)
1006 new (&destination_[i]) T(element_);
1007 }
1008 };
1009
1010
1011
1024 template <typename T, bool initialize_memory>
1027 {
1028 static const std::size_t minimum_parallel_grain_size =
1029 160000 / sizeof(T) + 1;
1030
1031 public:
1036 AlignedVectorDefaultInitialize(const std::size_t size, T *const destination)
1037 : destination_(destination)
1038 {
1039 if (size == 0)
1040 return;
1041 Assert(destination != nullptr, ExcInternalError());
1042
1045 else
1047 }
1048
1052 virtual void
1053 apply_to_subrange(const std::size_t begin,
1054 const std::size_t end) const override
1055 {
1056 // Only use memset() with types whose default constructors don't do
1057 // anything.
1058 if constexpr (std::is_trivially_default_constructible_v<T> == true)
1059 std::memset(destination_ + begin, 0, (end - begin) * sizeof(T));
1060 else
1062 end,
1063 std::bool_constant<initialize_memory>());
1064 }
1065
1066 private:
1067 mutable T *destination_;
1068
1069 // copy assignment operation
1070 void
1071 default_construct_or_assign(const std::size_t begin,
1072 const std::size_t end,
1073 std::bool_constant<false>) const
1074 {
1075 for (std::size_t i = begin; i < end; ++i)
1076 destination_[i] = std::move(T());
1077 }
1078
1079 // copy constructor (memory initialization)
1080 void
1081 default_construct_or_assign(const std::size_t begin,
1082 const std::size_t end,
1083 std::bool_constant<true>) const
1084 {
1085 for (std::size_t i = begin; i < end; ++i)
1086 new (&destination_[i]) T;
1087 }
1088 };
1089
1090} // end of namespace internal
1091
1092
1093#ifndef DOXYGEN
1094
1095
1096
1097template <typename T>
1099 : deleter_action_object(nullptr) // encode default action by using a nullptr
1100 , owning_aligned_vector(owning_object)
1101{}
1102
1103
1104# ifdef DEAL_II_WITH_MPI
1105
1106template <typename T>
1108 const bool is_shmem_root,
1109 T *aligned_shmem_pointer,
1110 MPI_Comm shmem_group_communicator,
1111 MPI_Win shmem_window)
1112 : deleter_action_object(
1113 std::make_unique<MPISharedMemDeleterAction>(is_shmem_root,
1114 aligned_shmem_pointer,
1115 shmem_group_communicator,
1116 shmem_window))
1117 , owning_aligned_vector(owning_object)
1118{}
1119# endif
1120
1121
1122template <typename T>
1123inline void
1125{
1126 // If no special action has been registered (i.e., if the action pointer is
1127 // nullptr), then just perform the default action right here.
1128 if (deleter_action_object == nullptr)
1129 {
1130 if (ptr != nullptr)
1131 {
1132 Assert(owning_aligned_vector->used_elements_end != nullptr,
1134
1135 if (std::is_trivially_destructible_v<T> == false)
1136 for (T *p = owning_aligned_vector->used_elements_end - 1; p >= ptr;
1137 --p)
1138 p->~T();
1139
1140 std::free(ptr);
1141 }
1142 }
1143 else
1144 // Otherwise, let the action object do what is necessary
1145 deleter_action_object->delete_array(owning_aligned_vector, ptr);
1146}
1147
1148
1149
1150template <typename T>
1151inline void
1153 const AlignedVector<T> *new_aligned_vector_ptr)
1154{
1155 owning_aligned_vector = new_aligned_vector_ptr;
1156}
1157
1158
1159# ifdef DEAL_II_WITH_MPI
1160
1161template <typename T>
1163 MPISharedMemDeleterAction(const bool is_shmem_root,
1164 T *aligned_shmem_pointer,
1165 MPI_Comm shmem_group_communicator,
1166 MPI_Win shmem_window)
1167 : is_shmem_root(is_shmem_root)
1168 , aligned_shmem_pointer(aligned_shmem_pointer)
1169 , shmem_group_communicator(shmem_group_communicator)
1170 , shmem_window(shmem_window)
1171{}
1172
1173
1174
1175template <typename T>
1176inline void
1178 const AlignedVector<T> *aligned_vector,
1179 T *ptr)
1180{
1181 (void)ptr;
1182 // It would be nice to assert that aligned_vector->elements.get() equals ptr,
1183 // but it is not guaranteed to work: clang, for example, sets elements.get()
1184 // to nullptr and then calls the deleter on a previously made copy. Hence we
1185 // must assume here that elements.get() (which is managed by the unique_ptr)
1186 // may be nullptr at this point.
1187 //
1188 // used_elements_end is a member variable of AlignedVector (i.e., we control
1189 // it, not unique_ptr) so it is still set to its correct value.
1190
1191 if (is_shmem_root)
1192 if (std::is_trivially_destructible_v<T> == false)
1193 for (T *p = aligned_vector->used_elements_end - 1; p >= ptr; --p)
1194 p->~T();
1195
1196 int ierr;
1197 ierr = MPI_Win_free(&shmem_window);
1198 AssertThrowMPI(ierr);
1199
1200 Utilities::MPI::free_communicator(shmem_group_communicator);
1201}
1202
1203# endif
1204
1205
1206template <class T>
1208 : elements(nullptr, Deleter(this))
1209 , used_elements_end(nullptr)
1210 , allocated_elements_end(nullptr)
1211 , replicated_across_communicator(false)
1212{}
1213
1214
1215
1216template <class T>
1217template <typename RandomAccessIterator, typename>
1218inline AlignedVector<T>::AlignedVector(RandomAccessIterator begin,
1219 RandomAccessIterator end)
1220 : elements(nullptr, Deleter(this))
1221 , used_elements_end(nullptr)
1222 , allocated_elements_end(nullptr)
1223 , replicated_across_communicator(false)
1224{
1225 allocate_and_move(0u, end - begin, end - begin);
1226 used_elements_end = allocated_elements_end;
1228 end,
1229 data());
1230}
1231
1232
1233template <class T>
1234inline AlignedVector<T>::AlignedVector(const size_type size, const T &init)
1235 : elements(nullptr, Deleter(this))
1236 , used_elements_end(nullptr)
1237 , allocated_elements_end(nullptr)
1238 , replicated_across_communicator(false)
1239{
1240 if (size > 0)
1241 resize(size, init);
1242}
1243
1244
1245
1246template <class T>
1248 : elements(nullptr, Deleter(this))
1249 , used_elements_end(nullptr)
1250 , allocated_elements_end(nullptr)
1251 , replicated_across_communicator(false)
1252{
1253 // copy the data from vec
1254 reserve(vec.size());
1255 used_elements_end = allocated_elements_end;
1258 elements.get());
1259}
1260
1261
1262
1263template <class T>
1266{
1267 // forward to the move operator
1268 *this = std::move(vec);
1269}
1270
1271
1272
1273template <class T>
1274inline AlignedVector<T> &
1276{
1277 const size_type new_size = vec.used_elements_end - vec.elements.get();
1278
1279 // First throw away everything and re-allocate memory but leave that
1280 // memory uninitialized for now:
1281 resize(0);
1282 reserve(new_size);
1283
1284 // Then copy the elements over by using the copy constructor on these
1285 // elements:
1288 elements.get());
1289
1290 // Finally adjust the pointer to the end of the elements that are used:
1291 used_elements_end = elements.get() + new_size;
1292
1293 return *this;
1294}
1295
1296
1297
1298template <class T>
1299inline AlignedVector<T> &
1301{
1302 clear();
1303
1304 // Move the actual data in the 'elements' object. One problem is that this
1305 // also moves the deleter object, but the deleter object
1306 // references 'this' (i.e., the 'this' pointer of the *moved-from*
1307 // object). The way this is implemented is that we have to move the
1308 // deleter as well, and then reset the pointer inside the deleter
1309 // that references the outer object.
1310 elements = std::move(vec.elements);
1311 elements.get_deleter().reset_owning_object(this);
1312
1313 // Then also steal the other pointers and clear them in the original object:
1314 used_elements_end = vec.used_elements_end;
1315 allocated_elements_end = vec.allocated_elements_end;
1316
1317 vec.used_elements_end = nullptr;
1318 vec.allocated_elements_end = nullptr;
1319
1320 return *this;
1321}
1322
1323
1324
1325template <class T>
1326inline void
1327AlignedVector<T>::resize_fast(const size_type new_size)
1328{
1329 const size_type old_size = size();
1330
1331 if (new_size == 0)
1332 clear();
1333 else if (new_size == old_size)
1334 {
1335 } // nothing to do here
1336 else if (new_size < old_size)
1337 {
1338 // call destructor on fields that are released, if the type requires it.
1339 // doing it backward releases the elements in reverse order as compared to
1340 // how they were created
1341 if (std::is_trivially_destructible_v<T> == false)
1342 for (T *p = used_elements_end - 1; p >= elements.get() + new_size; --p)
1343 p->~T();
1344 used_elements_end = elements.get() + new_size;
1345 }
1346 else // new_size > old_size
1347 {
1348 // Allocate more space, and claim that space as used
1349 reserve(new_size);
1350 used_elements_end = elements.get() + new_size;
1351
1352 // Leave the new array entries as-is (with undefined values) unless T's
1353 // default constructor is nontrivial (i.e., it is not a no-op)
1354 if (std::is_trivially_default_constructible_v<T> == false)
1356 new_size - old_size, elements.get() + old_size);
1357 }
1358}
1359
1360
1361
1362template <class T>
1363inline void
1364AlignedVector<T>::resize(const size_type new_size)
1365{
1366 const size_type old_size = size();
1367
1368 if (new_size == 0)
1369 clear();
1370 else if (new_size == old_size)
1371 {
1372 } // nothing to do here
1373 else if (new_size < old_size)
1374 {
1375 // call destructor on fields that are released, if the type requires it.
1376 // doing it backward releases the elements in reverse order as compared to
1377 // how they were created
1378 if (std::is_trivially_destructible_v<T> == false)
1379 for (T *p = used_elements_end - 1; p >= elements.get() + new_size; --p)
1380 p->~T();
1381 used_elements_end = elements.get() + new_size;
1382 }
1383 else // new_size > old_size
1384 {
1385 // Allocate more space, and claim that space as used
1386 reserve(new_size);
1387 used_elements_end = elements.get() + new_size;
1388
1389 // finally set the values to the default initializer
1391 new_size - old_size, elements.get() + old_size);
1392 }
1393}
1394
1395
1396
1397template <class T>
1398inline void
1399AlignedVector<T>::resize(const size_type new_size, const T &init)
1400{
1401 const size_type old_size = size();
1402
1403 if (new_size == 0)
1404 clear();
1405 else if (new_size == old_size)
1406 {
1407 } // nothing to do here
1408 else if (new_size < old_size)
1409 {
1410 // call destructor on fields that are released, if the type requires it.
1411 // doing it backward releases the elements in reverse order as compared to
1412 // how they were created
1413 if (std::is_trivially_destructible_v<T> == false)
1414 for (T *p = used_elements_end - 1; p >= elements.get() + new_size; --p)
1415 p->~T();
1416 used_elements_end = elements.get() + new_size;
1417 }
1418 else // new_size > old_size
1419 {
1420 // Allocate more space, and claim that space as used
1421 reserve(new_size);
1422 used_elements_end = elements.get() + new_size;
1423
1424 // finally set the desired init values
1426 new_size - old_size, init, elements.get() + old_size);
1427 }
1428}
1429
1430
1431
1432template <class T>
1433inline void
1434AlignedVector<T>::allocate_and_move(const size_t old_size,
1435 const size_t new_size,
1436 const size_t new_allocated_size)
1437{
1438 // allocate and align along 64-byte boundaries (this is enough for all
1439 // levels of vectorization currently supported by deal.II)
1440 T *new_data_ptr;
1441 Utilities::System::posix_memalign(reinterpret_cast<void **>(&new_data_ptr),
1442 64,
1443 new_size * sizeof(T));
1444
1445 // Now create a deleter that encodes what should happen when the object is
1446 // released: We need to destroy the objects that are currently alive (in
1447 // reverse order, and then release the memory. Note that we catch the
1448 // 'this' pointer because the number of elements currently alive might
1449 // change over time.
1450 Deleter deleter(this);
1451
1452 // copy whatever elements we need to retain
1453 if (new_allocated_size > 0)
1455 elements.get(), elements.get() + old_size, new_data_ptr);
1456
1457 // Now reset all the member variables of the current object
1458 // based on the allocation above. Assigning to a std::unique_ptr
1459 // object also releases the previously pointed to memory.
1460 //
1461 // Note that at the time of releasing the old memory, 'used_elements_end'
1462 // still points to its previous value, and this is important for the
1463 // deleter object of the previously allocated array (see how it loops over
1464 // the to-be-destroyed elements at the Deleter::DefaultDeleterAction
1465 // class).
1466 elements = decltype(elements)(new_data_ptr, std::move(deleter));
1467 used_elements_end = elements.get() + old_size;
1468 allocated_elements_end = elements.get() + new_size;
1469}
1470
1471
1472
1473template <class T>
1474inline void
1475AlignedVector<T>::reserve(const size_type new_allocated_size)
1476{
1477 const size_type old_size = used_elements_end - elements.get();
1478 const size_type old_allocated_size = allocated_elements_end - elements.get();
1479 if (new_allocated_size > old_allocated_size)
1480 {
1481 // if we continuously increase the size of the vector, we might be
1482 // reallocating a lot of times. therefore, try to increase the size more
1483 // aggressively
1484 const size_type new_size =
1485 std::max(new_allocated_size, 2 * old_allocated_size);
1486
1487 allocate_and_move(old_size, new_size, new_allocated_size);
1488 }
1489 else if (new_allocated_size == 0)
1490 clear();
1491 else // size_alloc < allocated_size
1492 {
1493 } // nothing to do here
1494}
1495
1496
1497
1498template <class T>
1499inline void
1501{
1502# ifdef DEBUG
1503 Assert(replicated_across_communicator == false,
1504 ExcAlignedVectorChangeAfterReplication());
1505# endif
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 (std::is_trivially_copyable_v<T> == true)
1780 {
1781 // The data is trivially copyable, i.e., we can copy things directly
1782 // without having to go through the serialization/deserialization
1783 // machinery of Utilities::MPI::broadcast.
1784 //
1785 // In that case, first tell all of the other shmem roots how many
1786 // elements we will have to deal with, and let them resize their
1787 // (non-shared) arrays.
1788 const size_type new_size =
1789 Utilities::MPI::broadcast(shmem_roots_communicator,
1790 size(),
1791 shmem_roots_root_rank);
1792 if (is_shmem_roots_root == false)
1793 resize(new_size);
1794
1795 // Then directly copy from the root process into these buffers
1796 int ierr = MPI_Bcast(elements.get(),
1797 sizeof(T) * new_size,
1798 MPI_CHAR,
1799 shmem_roots_root_rank,
1800 shmem_roots_communicator);
1801 AssertThrowMPI(ierr);
1802 }
1803 else
1804 {
1805 // The objects to be sent around are not "trivial", and so we have
1806 // to go through the serialization/deserialization machinery. On all
1807 // but the sending process, overwrite the current state with the
1808 // vector just broadcast.
1809 //
1810 // On the root rank, this would lead to resetting the 'entries'
1811 // pointer, which would trigger the deleter which would lead to a
1812 // deadlock. So we just send the result of the broadcast() call to
1813 // nirvana on the root process and keep our current state.
1814 if (Utilities::MPI::this_mpi_process(shmem_roots_communicator) == 0)
1815 Utilities::MPI::broadcast(shmem_roots_communicator,
1816 *this,
1817 shmem_roots_root_rank);
1818 else
1819 *this = Utilities::MPI::broadcast(shmem_roots_communicator,
1820 *this,
1821 shmem_roots_root_rank);
1822 }
1823 }
1824
1825 // We no longer need the shmem roots communicator, so get rid of it
1826 Utilities::MPI::free_communicator(shmem_roots_communicator);
1827
1828
1829 // **** Step 3 ****
1830 // At this point, all shmem groups have one shmem root process that has
1831 // a copy of the data. This is the point where each shmem group should
1832 // establish a shmem area to put the data into. As mentioned above,
1833 // we know that the shmem roots are the last rank in their respective
1834 // shmem_group_communicator.
1835 //
1836 // The process for all of this works as follows: While all processes in
1837 // the shmem group participate in the generation of the shmem memory window,
1838 // only the shmem root actually allocates any memory -- the rest just
1839 // allocate zero bytes of their own. We allocate space for exactly
1840 // size() elements (computed on the shmem_root that already has the data)
1841 // and add however many bytes are necessary so that we know that we can align
1842 // things to 64-byte boundaries. The worst case happens if the memory system
1843 // gives us a pointer to an address one byte past a desired alignment
1844 // boundary, and in that case aligning the memory will require us to waste the
1845 // first (align_by-1) bytes. So we have to ask for
1846 // size() * sizeof(T) + (align_by - 1)
1847 // bytes.
1848 //
1849 // Before MPI 4.0, there was no way to specify that we want memory aligned to
1850 // a certain number of bytes. This is going to come back to bite us further
1851 // down below when we try to get a properly aligned pointer to our memory
1852 // region, see the commentary there. Starting with MPI 4.0, one can set a
1853 // flag in an MPI_Info structure that requests a desired alignment, so we do
1854 // this for forward compatibility; MPI implementations ignore flags they don't
1855 // know anything about, and so setting this flag is backward compatible also
1856 // to older MPI versions.
1857 MPI_Win shmem_window;
1858 void *base_ptr;
1859 const MPI_Aint align_by = 64;
1860 const MPI_Aint alloc_size =
1861 Utilities::MPI::broadcast(shmem_group_communicator,
1862 (size() * sizeof(T) + (align_by - 1)),
1863 0);
1864
1865 {
1866 int ierr;
1867
1868 MPI_Info mpi_info;
1869 ierr = MPI_Info_create(&mpi_info);
1870 AssertThrowMPI(ierr);
1871 ierr = MPI_Info_set(mpi_info,
1872 "mpi_minimum_memory_alignment",
1873 std::to_string(align_by).c_str());
1874 AssertThrowMPI(ierr);
1875 ierr = MPI_Win_allocate_shared((is_shmem_root ? alloc_size : 0),
1876 /* disp_unit = */ 1,
1877 mpi_info,
1878 shmem_group_communicator,
1879 &base_ptr,
1880 &shmem_window);
1881 AssertThrowMPI(ierr);
1882
1883 ierr = MPI_Info_free(&mpi_info);
1884 AssertThrowMPI(ierr);
1885 }
1886
1887
1888 // **** Step 4 ****
1889 // The next step is to teach all non-shmem root processes what the pointer to
1890 // the array is that the shmem-root created. MPI has a nifty way for this
1891 // given that only a single process actually allocated memory in the window:
1892 // When calling MPI_Win_shared_query, the MPI documentation says that
1893 // "When rank is MPI_PROC_NULL, the pointer, disp_unit, and size returned are
1894 // the pointer, disp_unit, and size of the memory segment belonging the lowest
1895 // rank that specified size > 0. If all processes in the group attached to the
1896 // window specified size = 0, then the call returns size = 0 and a baseptr as
1897 // if MPI_ALLOC_MEM was called with size = 0."
1898 //
1899 // This will allow us to obtain the pointer to the shmem root's memory area,
1900 // which is the only one we care about. (None of the other processes have
1901 // even allocated any memory.)
1902 //
1903 // We don't need to do this on the shmem root process: This process has
1904 // already gotten its base_ptr correctly set above, and we can determine the
1905 // array size by just calling size().
1906 if (is_shmem_root == false)
1907 {
1908 int disp_unit;
1909 MPI_Aint alloc_size; // not actually used
1910 const int ierr = MPI_Win_shared_query(
1911 shmem_window, MPI_PROC_NULL, &alloc_size, &disp_unit, &base_ptr);
1912 AssertThrowMPI(ierr);
1913
1914 // Make sure we actually got a pointer, and check that the disp_unit is
1915 // equal to 1 (as set above)
1916 Assert(base_ptr != nullptr, ExcInternalError());
1917 Assert(disp_unit == 1, ExcInternalError());
1918 }
1919
1920
1921 // **** Step 5 ****
1922 // Now that all processes know the address of the space that is visible to
1923 // everyone, we need to figure out whether it is properly aligned and if not,
1924 // find the next aligned address.
1925 //
1926 // std::align does that, but it also modifies its last two arguments. The
1927 // documentation of that function at
1928 // https://en.cppreference.com/w/cpp/memory/align is not entirely clear, but I
1929 // *think* that the following should do given that we do not use base_ptr and
1930 // available_space any further after the call to std::align.
1931 std::size_t available_space = alloc_size;
1932 void *base_ptr_backup = base_ptr;
1933 T *aligned_shmem_pointer = static_cast<T *>(
1934 std::align(align_by, new_size * sizeof(T), base_ptr, available_space));
1935 Assert(aligned_shmem_pointer != nullptr, ExcInternalError());
1936
1937 // There is one step to guard against. It is *conceivable* that the base_ptr
1938 // we have previously obtained from MPI_Win_shared_query is mapped so
1939 // awkwardly into the different MPI processes' memory spaces that it is
1940 // aligned in one memory space, but not another. In that case, different
1941 // processes would align base_ptr differently, and adjust available_space
1942 // differently. We can check that by making sure that the max (or min) over
1943 // all processes is equal to every process's value. If that's not the case,
1944 // then the whole idea of aligning above is wrong and we need to rethink what
1945 // it means to align data in a shared memory space.
1946 //
1947 // One might be tempted to think that this is not how MPI implementations
1948 // actually arrange things. Alas, when developing this functionality in 2021,
1949 // this is really how at least OpenMPI ends up doing things. (This is with an
1950 // OpenMPI implementation of MPI 3.1, so it does not support the flag we set
1951 // in the MPI_Info structure above when allocating the memory window.) Indeed,
1952 // when running this code on three processes, one ends up with base_ptr values
1953 // of
1954 // base_ptr=0x7f0842f02108
1955 // base_ptr=0x7fc0a47881d0
1956 // base_ptr=0x7f64872db108
1957 // which, most annoyingly, are aligned to 8 and 16 byte boundaries -- so there
1958 // is no common offset std::align could find that leads to a 64-byte
1959 // aligned memory address in all three memory spaces. That's a tremendous
1960 // nuisance and there is really nothing we can do about this other than just
1961 // fall back on the (unaligned) base_ptr in that case.
1962 if (Utilities::MPI::min(available_space, shmem_group_communicator) !=
1963 Utilities::MPI::max(available_space, shmem_group_communicator))
1964 aligned_shmem_pointer = static_cast<T *>(base_ptr_backup);
1965
1966
1967 // **** Step 6 ****
1968 // If this is the shmem root process, we need to copy the data into the
1969 // shared memory space.
1970 if (is_shmem_root)
1971 {
1972 if (std::is_trivially_copyable_v<T> == true)
1973 std::memcpy(aligned_shmem_pointer, elements.get(), sizeof(T) * size());
1974 else
1975 for (std::size_t i = 0; i < size(); ++i)
1976 new (&aligned_shmem_pointer[i]) T(std::move(elements[i]));
1977 }
1978
1979 // Make sure that the shared memory host has copied the data before we try to
1980 // access it.
1981 const int ierr = MPI_Barrier(shmem_group_communicator);
1982 AssertThrowMPI(ierr);
1983
1984 // **** Step 7 ****
1985 // Finally, we need to set the pointers of this object to what we just
1986 // learned. This also releases all memory that may have been in use
1987 // previously.
1988 //
1989 // The part that is a bit tricky is how to write the deleter of this
1990 // shared memory object. When we want to get rid of it, we need to
1991 // also release the MPI_Win object along with the shmem_group_communicator
1992 // object. That's because as long as we use the shared memory, we still need
1993 // to hold on to the MPI_Win object, and the MPI_Win object is based on the
1994 // communicator. (The former is definitely true, the latter is not quite clear
1995 // from the MPI documentation, but seems reasonable.) So we need to have a
1996 // deleter for the pointer that ensures that upon release of the memory, we
1997 // not only call the destructor of these memory elements (but only once, on
1998 // the shmem root!) but also destroy the MPI_Win and the communicator. All of
1999 // that is encapsulated in the following call where the deleter makes copies
2000 // of the arguments in the lambda capture.
2001 elements = decltype(elements)(aligned_shmem_pointer,
2002 Deleter(this,
2003 is_shmem_root,
2004 aligned_shmem_pointer,
2005 shmem_group_communicator,
2006 shmem_window));
2007
2008 // We then also have to set the other two pointers that define the state of
2009 // the current object. Note that the new buffer size is exactly as large as
2010 // necessary, i.e., can store size() elements, regardless of the number of
2011 // allocated elements in the original objects.
2012 used_elements_end = elements.get() + new_size;
2013 allocated_elements_end = used_elements_end;
2014
2015 // **** Consistency check ****
2016 // At this point, each process should have a copy of the data.
2017 // Verify this in some sort of round-about way
2018# ifdef DEBUG
2019 replicated_across_communicator = true;
2020 const std::vector<char> packed_data = Utilities::pack(*this);
2021 const int hash =
2022 std::accumulate(packed_data.begin(), packed_data.end(), int(0));
2023 Assert(Utilities::MPI::max(hash, communicator) == hash, ExcInternalError());
2024# endif
2025
2026# else
2027 // No MPI -> nothing to replicate
2028 (void)communicator;
2029 (void)root_process;
2030# endif
2031}
2032
2033
2034
2035template <class T>
2036inline void
2038{
2039 // Swap the data in the 'elements' objects. Then also make sure that
2040 // their respective deleter objects point to the right place.
2041 std::swap(elements, vec.elements);
2042 elements.get_deleter().reset_owning_object(this);
2043 vec.elements.get_deleter().reset_owning_object(&vec);
2044
2045 // Now also swap the remaining members.
2046 std::swap(used_elements_end, vec.used_elements_end);
2047 std::swap(allocated_elements_end, vec.allocated_elements_end);
2048}
2049
2050
2051
2052template <class T>
2053inline bool
2055{
2056 return used_elements_end == elements.get();
2057}
2058
2059
2060
2061template <class T>
2062inline typename AlignedVector<T>::size_type
2064{
2065 return used_elements_end - elements.get();
2066}
2067
2068
2069
2070template <class T>
2071inline typename AlignedVector<T>::size_type
2073{
2074 return allocated_elements_end - elements.get();
2075}
2076
2077
2078
2079template <class T>
2080inline typename AlignedVector<T>::reference
2081AlignedVector<T>::operator[](const size_type index)
2082{
2084 return elements[index];
2085}
2086
2087
2088
2089template <class T>
2091AlignedVector<T>::operator[](const size_type index) const
2092{
2094 return elements[index];
2095}
2096
2097
2098
2099template <typename T>
2100inline typename AlignedVector<T>::pointer
2102{
2103 return elements.get();
2104}
2105
2106
2107
2108template <typename T>
2109inline typename AlignedVector<T>::const_pointer
2111{
2112 return elements.get();
2113}
2114
2115
2116
2117template <class T>
2118inline typename AlignedVector<T>::iterator
2120{
2121 return elements.get();
2122}
2123
2124
2125
2126template <class T>
2127inline typename AlignedVector<T>::iterator
2129{
2130 return used_elements_end;
2131}
2132
2133
2134
2135template <class T>
2138{
2139 return elements.get();
2140}
2141
2142
2143
2144template <class T>
2147{
2148 return used_elements_end;
2149}
2150
2151
2152
2153template <class T>
2154template <class Archive>
2155inline void
2156AlignedVector<T>::save(Archive &ar, const unsigned int) const
2157{
2158 size_type vec_size = size();
2159 ar &vec_size;
2160 if (vec_size > 0)
2161 ar &boost::serialization::make_array(elements.get(), vec_size);
2162}
2163
2164
2165
2166template <class T>
2167template <class Archive>
2168inline void
2169AlignedVector<T>::load(Archive &ar, const unsigned int)
2170{
2171 size_type vec_size = 0;
2172 ar &vec_size;
2173
2174 if (vec_size > 0)
2175 {
2176 reserve(vec_size);
2177 ar &boost::serialization::make_array(elements.get(), vec_size);
2178 used_elements_end = elements.get() + vec_size;
2179 }
2180}
2181
2182
2183
2184template <class T>
2185inline typename AlignedVector<T>::size_type
2187{
2188 size_type memory = sizeof(*this);
2189 for (const T *t = elements.get(); t != used_elements_end; ++t)
2191 memory += sizeof(T) * (allocated_elements_end - used_elements_end);
2192 return memory;
2193}
2194
2195
2196#endif // ifndef DOXYGEN
2197
2198
2204template <class T>
2205bool
2207{
2208 if (lhs.size() != rhs.size())
2209 return false;
2210 for (typename AlignedVector<T>::const_iterator lit = lhs.begin(),
2211 rit = rhs.begin();
2212 lit != lhs.end();
2213 ++lit, ++rit)
2214 if (*lit != *rit)
2215 return false;
2216 return true;
2217}
2218
2219
2220
2226template <class T>
2227bool
2229{
2230 return !(operator==(lhs, rhs));
2231}
2232
2233
2235
2236#endif
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)
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
~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 allocate_and_move(const size_t old_size, const size_t new_size, const size_t new_allocated_size)
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:513
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:514
#define Assert(cond, exc)
#define AssertThrowMPI(error_code)
#define AssertIndexRange(index, range)
#define DeclExceptionMsg(Exception, defaulttext)
Definition exceptions.h:491
static ::ExceptionBase & ExcInternalError()
static ::ExceptionBase & ExcMessage(std::string arg1)
static ::ExceptionBase & ExcAlignedVectorChangeAfterReplication()
std::vector< index_type > data
Definition mpi.cc:735
std::size_t size
Definition mpi.cc:734
std::enable_if_t< std::is_fundamental_v< T >, std::size_t > memory_consumption(const T &t)
VectorType::value_type * end(VectorType &V)
VectorType::value_type * begin(VectorType &V)
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:107
void free_communicator(MPI_Comm mpi_communicator)
Definition mpi.cc:154
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)
size_t pack(const T &object, std::vector< char > &dest_buffer, const bool allow_compression=true)
Definition utilities.h:1381
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:830