Reference documentation for deal.II version GIT relicensing-245-g36f19064f7 2024-03-29 07:20:02+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 - 2023 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
87 explicit AlignedVector(const size_type size, const T &init = T());
88
92 ~AlignedVector() = default;
93
100
106
114
120
143 void
144 resize_fast(const size_type new_size);
145
158 void
159 resize(const size_type new_size);
160
176 void
177 resize(const size_type new_size, const T &init);
178
199 void
200 reserve(const size_type new_allocated_size);
201
205 void
207
212 void
214
220 void
221 push_back(const T in_data);
222
228
233 back() const;
234
239 template <typename ForwardIterator>
240 void
241 insert_back(ForwardIterator begin, ForwardIterator end);
242
252 void
254
263 void
264 fill(const T &element);
265
353 void
355 const unsigned int root_process);
356
360 void
362
366 bool
367 empty() const;
368
373 size() const;
374
380 capacity() const;
381
386 operator[](const size_type index);
387
392 operator[](const size_type index) const;
393
397 pointer
399
404 data() const;
405
411
417
422 begin() const;
423
428 end() const;
429
437
443 template <class Archive>
444 void
445 save(Archive &ar, const unsigned int version) const;
446
452 template <class Archive>
453 void
454 load(Archive &ar, const unsigned int version);
455
456#ifdef DOXYGEN
462 template <class Archive>
463 void
464 serialize(Archive &archive, const unsigned int version);
465#else
466 // This macro defines the serialize() method that is compatible with
467 // the templated save() and load() method that have been implemented.
468 BOOST_SERIALIZATION_SPLIT_MEMBER()
469#endif
470
478 "Changing the vector after a call to "
479 "replicate_across_communicator() is not allowed.");
480
481private:
486 void
487 allocate_and_move(const size_t old_size,
488 const size_t new_size,
489 const size_t new_allocated_size);
490
583 {
584 public:
590 Deleter(AlignedVector<T> *owning_object);
591
592#ifdef DEAL_II_WITH_MPI
600 Deleter(AlignedVector<T> *owning_object,
601 const bool is_shmem_root,
602 T *aligned_shmem_pointer,
603 MPI_Comm shmem_group_communicator,
604 MPI_Win shmem_window);
605#endif
606
612 void
613 operator()(T *ptr);
614
622 void
623 reset_owning_object(const AlignedVector<T> *new_aligned_vector_ptr);
624
625 private:
630 {
631 public:
635 virtual ~DeleterActionBase() = default;
636
642 virtual void
644 };
645
646#ifdef DEAL_II_WITH_MPI
647
653 {
654 public:
662 MPI_Win shmem_window);
663
669 virtual void
670 delete_array(const AlignedVector<T> *aligned_vector, T *ptr);
671
672 private:
677 const bool is_shmem_root;
681 };
682#endif
683
688 std::unique_ptr<DeleterActionBase> deleter_action_object;
689
695 };
696
700 std::unique_ptr<T[], Deleter> elements;
701
706
711
716};
717
718
719// ------------------------------- inline functions --------------------------
720
726namespace internal
727{
746 template <typename T>
749 {
750 static const std::size_t minimum_parallel_grain_size =
751 160000 / sizeof(T) + 1;
752
753 public:
763 AlignedVectorCopyConstruct(const T *const source_begin,
764 const T *const source_end,
765 T *const destination)
766 : source_(source_begin)
767 , destination_(destination)
768 {
769 Assert(source_end >= source_begin, ExcInternalError());
770 Assert(source_end == source_begin || destination != nullptr,
772 const std::size_t size = source_end - source_begin;
775 else
777 }
778
783 virtual void
784 apply_to_subrange(const std::size_t begin,
785 const std::size_t end) const override
786 {
787 if (end == begin)
788 return;
789
790 // for classes trivial assignment can use memcpy. cast element to
791 // (void*) to silence compiler warning for virtual classes (they will
792 // never arrive here because they are non-trivial).
793
794 if (std::is_trivial_v<T> == true)
795 std::memcpy(static_cast<void *>(destination_ + begin),
796 static_cast<const void *>(source_ + begin),
797 (end - begin) * sizeof(T));
798 else
799 for (std::size_t i = begin; i < end; ++i)
800 new (&destination_[i]) T(source_[i]);
801 }
802
803 private:
804 const T *const source_;
805 T *const destination_;
806 };
807
808
815 template <typename T>
818 {
819 static const std::size_t minimum_parallel_grain_size =
820 160000 / sizeof(T) + 1;
821
822 public:
832 AlignedVectorMoveConstruct(T *const source_begin,
833 T *const source_end,
834 T *const destination)
835 : source_(source_begin)
836 , destination_(destination)
837 {
838 Assert(source_end >= source_begin, ExcInternalError());
839 Assert(source_end == source_begin || destination != nullptr,
841 const std::size_t size = source_end - source_begin;
844 else
846 }
847
852 virtual void
853 apply_to_subrange(const std::size_t begin,
854 const std::size_t end) const override
855 {
856 if (end == begin)
857 return;
858
859 // Classes with trivial assignment can use memcpy. cast element to
860 // (void*) to silence compiler warning for virtual classes (they will
861 // never arrive here because they are non-trivial).
862 if (std::is_trivial_v<T> == true)
863 std::memcpy(static_cast<void *>(destination_ + begin),
864 static_cast<void *>(source_ + begin),
865 (end - begin) * sizeof(T));
866 else
867 // For everything else just use the move constructor. The original
868 // object remains alive and will be destroyed elsewhere.
869 for (std::size_t i = begin; i < end; ++i)
870 new (&destination_[i]) T(std::move(source_[i]));
871 }
872
873 private:
874 T *const source_;
875 T *const destination_;
876 };
877
878
896 template <typename T, bool initialize_memory>
898 {
899 static const std::size_t minimum_parallel_grain_size =
900 160000 / sizeof(T) + 1;
901
902 public:
907 AlignedVectorInitialize(const std::size_t size,
908 const T &element,
909 T *const destination)
910 : element_(element)
911 , destination_(destination)
912 , trivial_element(false)
913 {
914 if (size == 0)
915 return;
916 Assert(destination != nullptr, ExcInternalError());
917
918 // do not use memcmp for long double because on some systems it does not
919 // completely fill its memory and may lead to false positives in
920 // e.g. valgrind
921 if (std::is_trivial_v<T> == true &&
922 std::is_same_v<T, long double> == false)
923 {
924 const unsigned char zero[sizeof(T)] = {};
925 // cast element to (void*) to silence compiler warning for virtual
926 // classes (they will never arrive here because they are
927 // non-trivial).
928 if (std::memcmp(zero,
929 static_cast<const void *>(&element),
930 sizeof(T)) == 0)
931 trivial_element = true;
932 }
935 else
937 }
938
942 virtual void
943 apply_to_subrange(const std::size_t begin,
944 const std::size_t end) const override
945 {
946 // for classes with trivial assignment of zero can use memset. cast
947 // element to (void*) to silence compiler warning for virtual
948 // classes (they will never arrive here because they are
949 // non-trivial).
950 if (std::is_trivial_v<T> == true && trivial_element)
951 std::memset(static_cast<void *>(destination_ + begin),
952 0,
953 (end - begin) * sizeof(T));
954 else
956 end,
957 std::bool_constant<initialize_memory>());
958 }
959
960 private:
961 const T &element_;
962 mutable T *destination_;
964
965 // copy assignment operation
966 void
967 copy_construct_or_assign(const std::size_t begin,
968 const std::size_t end,
969 std::bool_constant<false>) const
970 {
971 for (std::size_t i = begin; i < end; ++i)
973 }
974
975 // copy constructor (memory initialization)
976 void
977 copy_construct_or_assign(const std::size_t begin,
978 const std::size_t end,
979 std::bool_constant<true>) const
980 {
981 for (std::size_t i = begin; i < end; ++i)
982 new (&destination_[i]) T(element_);
983 }
984 };
985
986
987
1000 template <typename T, bool initialize_memory>
1003 {
1004 static const std::size_t minimum_parallel_grain_size =
1005 160000 / sizeof(T) + 1;
1006
1007 public:
1012 AlignedVectorDefaultInitialize(const std::size_t size, T *const destination)
1013 : destination_(destination)
1014 {
1015 if (size == 0)
1016 return;
1017 Assert(destination != nullptr, ExcInternalError());
1018
1019 if (size < minimum_parallel_grain_size)
1021 else
1023 }
1024
1028 virtual void
1029 apply_to_subrange(const std::size_t begin,
1030 const std::size_t end) const override
1031 {
1032 // for classes with trivial assignment of zero can use memset. cast
1033 // element to (void*) to silence compiler warning for virtual
1034 // classes (they will never arrive here because they are
1035 // non-trivial).
1036 if (std::is_trivial_v<T> == true)
1037 std::memset(static_cast<void *>(destination_ + begin),
1038 0,
1039 (end - begin) * sizeof(T));
1040 else
1042 end,
1043 std::bool_constant<initialize_memory>());
1044 }
1045
1046 private:
1047 mutable T *destination_;
1048
1049 // copy assignment operation
1050 void
1051 default_construct_or_assign(const std::size_t begin,
1052 const std::size_t end,
1053 std::bool_constant<false>) const
1054 {
1055 for (std::size_t i = begin; i < end; ++i)
1056 destination_[i] = std::move(T());
1057 }
1058
1059 // copy constructor (memory initialization)
1060 void
1061 default_construct_or_assign(const std::size_t begin,
1062 const std::size_t end,
1063 std::bool_constant<true>) const
1064 {
1065 for (std::size_t i = begin; i < end; ++i)
1066 new (&destination_[i]) T;
1067 }
1068 };
1069
1070} // end of namespace internal
1071
1072
1073#ifndef DOXYGEN
1074
1075
1076
1077template <typename T>
1079 : deleter_action_object(nullptr) // encode default action by using a nullptr
1080 , owning_aligned_vector(owning_object)
1081{}
1082
1083
1084# ifdef DEAL_II_WITH_MPI
1085
1086template <typename T>
1088 const bool is_shmem_root,
1089 T *aligned_shmem_pointer,
1090 MPI_Comm shmem_group_communicator,
1091 MPI_Win shmem_window)
1092 : deleter_action_object(
1093 std::make_unique<MPISharedMemDeleterAction>(is_shmem_root,
1094 aligned_shmem_pointer,
1095 shmem_group_communicator,
1096 shmem_window))
1097 , owning_aligned_vector(owning_object)
1098{}
1099# endif
1100
1101
1102template <typename T>
1103inline void
1105{
1106 // If no special action has been registered (i.e., if the action pointer is
1107 // nullptr), then just perform the default action right here.
1108 if (deleter_action_object == nullptr)
1109 {
1110 if (ptr != nullptr)
1111 {
1112 Assert(owning_aligned_vector->used_elements_end != nullptr,
1114
1115 if (std::is_trivial_v<T> == false)
1116 for (T *p = owning_aligned_vector->used_elements_end - 1; p >= ptr;
1117 --p)
1118 p->~T();
1119
1120 std::free(ptr);
1121 }
1122 }
1123 else
1124 // Otherwise, let the action object do what is necessary
1125 deleter_action_object->delete_array(owning_aligned_vector, ptr);
1126}
1127
1128
1129
1130template <typename T>
1131inline void
1133 const AlignedVector<T> *new_aligned_vector_ptr)
1134{
1135 owning_aligned_vector = new_aligned_vector_ptr;
1136}
1137
1138
1139# ifdef DEAL_II_WITH_MPI
1140
1141template <typename T>
1143 MPISharedMemDeleterAction(const bool is_shmem_root,
1144 T *aligned_shmem_pointer,
1145 MPI_Comm shmem_group_communicator,
1146 MPI_Win shmem_window)
1147 : is_shmem_root(is_shmem_root)
1148 , aligned_shmem_pointer(aligned_shmem_pointer)
1149 , shmem_group_communicator(shmem_group_communicator)
1150 , shmem_window(shmem_window)
1151{}
1152
1153
1154
1155template <typename T>
1156inline void
1158 const AlignedVector<T> *aligned_vector,
1159 T *ptr)
1160{
1161 (void)ptr;
1162 // It would be nice to assert that aligned_vector->elements.get() equals ptr,
1163 // but it is not guaranteed to work: clang, for example, sets elements.get()
1164 // to nullptr and then calls the deleter on a previously made copy. Hence we
1165 // must assume here that elements.get() (which is managed by the unique_ptr)
1166 // may be nullptr at this point.
1167 //
1168 // used_elements_end is a member variable of AlignedVector (i.e., we control
1169 // it, not unique_ptr) so it is still set to its correct value.
1170
1171 if (is_shmem_root)
1172 if (std::is_trivial_v<T> == false)
1173 for (T *p = aligned_vector->used_elements_end - 1; p >= ptr; --p)
1174 p->~T();
1175
1176 int ierr;
1177 ierr = MPI_Win_free(&shmem_window);
1178 AssertThrowMPI(ierr);
1179
1180 Utilities::MPI::free_communicator(shmem_group_communicator);
1181}
1182
1183# endif
1184
1185
1186template <class T>
1188 : elements(nullptr, Deleter(this))
1189 , used_elements_end(nullptr)
1190 , allocated_elements_end(nullptr)
1191# ifdef DEBUG
1192 , replicated_across_communicator(false)
1193# endif
1194{}
1195
1196
1197
1198template <class T>
1199inline AlignedVector<T>::AlignedVector(const size_type size, const T &init)
1200 : elements(nullptr, Deleter(this))
1201 , used_elements_end(nullptr)
1202 , allocated_elements_end(nullptr)
1203# ifdef DEBUG
1204 , replicated_across_communicator(false)
1205# endif
1206{
1207 if (size > 0)
1208 resize(size, init);
1209}
1210
1211
1212
1213template <class T>
1215 : elements(nullptr, Deleter(this))
1216 , used_elements_end(nullptr)
1217 , allocated_elements_end(nullptr)
1218# ifdef DEBUG
1219 , replicated_across_communicator(false)
1220# endif
1221{
1222 // copy the data from vec
1223 reserve(vec.size());
1224 used_elements_end = allocated_elements_end;
1227 elements.get());
1228}
1229
1230
1231
1232template <class T>
1235{
1236 // forward to the move operator
1237 *this = std::move(vec);
1238}
1239
1240
1241
1242template <class T>
1243inline AlignedVector<T> &
1245{
1246 const size_type new_size = vec.used_elements_end - vec.elements.get();
1247
1248 // First throw away everything and re-allocate memory but leave that
1249 // memory uninitialized for now:
1250 resize(0);
1251 reserve(new_size);
1252
1253 // Then copy the elements over by using the copy constructor on these
1254 // elements:
1257 elements.get());
1258
1259 // Finally adjust the pointer to the end of the elements that are used:
1260 used_elements_end = elements.get() + new_size;
1261
1262 return *this;
1263}
1264
1265
1266
1267template <class T>
1268inline AlignedVector<T> &
1270{
1271 clear();
1272
1273 // Move the actual data in the 'elements' object. One problem is that this
1274 // also moves the deleter object, but the deleter object
1275 // references 'this' (i.e., the 'this' pointer of the *moved-from*
1276 // object). The way this is implemented is that we have to move the
1277 // deleter as well, and then reset the pointer inside the deleter
1278 // that references the outer object.
1279 elements = std::move(vec.elements);
1280 elements.get_deleter().reset_owning_object(this);
1281
1282 // Then also steal the other pointers and clear them in the original object:
1283 used_elements_end = vec.used_elements_end;
1284 allocated_elements_end = vec.allocated_elements_end;
1285
1286 vec.used_elements_end = nullptr;
1287 vec.allocated_elements_end = nullptr;
1288
1289 return *this;
1290}
1291
1292
1293
1294template <class T>
1295inline void
1296AlignedVector<T>::resize_fast(const size_type new_size)
1297{
1298 const size_type old_size = size();
1299
1300 if (new_size == 0)
1301 clear();
1302 else if (new_size == old_size)
1303 {
1304 } // nothing to do here
1305 else if (new_size < old_size)
1306 {
1307 // call destructor on fields that are released, if the type requires it.
1308 // doing it backward releases the elements in reverse order as compared to
1309 // how they were created
1310 if (std::is_trivial_v<T> == false)
1311 for (T *p = used_elements_end - 1; p >= elements.get() + new_size; --p)
1312 p->~T();
1313 used_elements_end = elements.get() + new_size;
1314 }
1315 else // new_size > old_size
1316 {
1317 // Allocate more space, and claim that space as used
1318 reserve(new_size);
1319 used_elements_end = elements.get() + new_size;
1320
1321 // need to still set the values in case the class is non-trivial because
1322 // virtual classes etc. need to run their (default) constructor
1323 if (std::is_trivial_v<T> == false)
1325 new_size - old_size, elements.get() + old_size);
1326 }
1327}
1328
1329
1330
1331template <class T>
1332inline void
1333AlignedVector<T>::resize(const size_type new_size)
1334{
1335 const size_type old_size = size();
1336
1337 if (new_size == 0)
1338 clear();
1339 else if (new_size == old_size)
1340 {
1341 } // nothing to do here
1342 else if (new_size < old_size)
1343 {
1344 // call destructor on fields that are released, if the type requires it.
1345 // doing it backward releases the elements in reverse order as compared to
1346 // how they were created
1347 if (std::is_trivial_v<T> == false)
1348 for (T *p = used_elements_end - 1; p >= elements.get() + new_size; --p)
1349 p->~T();
1350 used_elements_end = elements.get() + new_size;
1351 }
1352 else // new_size > old_size
1353 {
1354 // Allocate more space, and claim that space as used
1355 reserve(new_size);
1356 used_elements_end = elements.get() + new_size;
1357
1358 // finally set the values to the default initializer
1360 new_size - old_size, elements.get() + old_size);
1361 }
1362}
1363
1364
1365
1366template <class T>
1367inline void
1368AlignedVector<T>::resize(const size_type new_size, const T &init)
1369{
1370 const size_type old_size = size();
1371
1372 if (new_size == 0)
1373 clear();
1374 else if (new_size == old_size)
1375 {
1376 } // nothing to do here
1377 else if (new_size < old_size)
1378 {
1379 // call destructor on fields that are released, if the type requires it.
1380 // doing it backward releases the elements in reverse order as compared to
1381 // how they were created
1382 if (std::is_trivial_v<T> == false)
1383 for (T *p = used_elements_end - 1; p >= elements.get() + new_size; --p)
1384 p->~T();
1385 used_elements_end = elements.get() + new_size;
1386 }
1387 else // new_size > old_size
1388 {
1389 // Allocate more space, and claim that space as used
1390 reserve(new_size);
1391 used_elements_end = elements.get() + new_size;
1392
1393 // finally set the desired init values
1395 new_size - old_size, init, elements.get() + old_size);
1396 }
1397}
1398
1399
1400
1401template <class T>
1402inline void
1403AlignedVector<T>::allocate_and_move(const size_t old_size,
1404 const size_t new_size,
1405 const size_t new_allocated_size)
1406{
1407 // allocate and align along 64-byte boundaries (this is enough for all
1408 // levels of vectorization currently supported by deal.II)
1409 T *new_data_ptr;
1410 Utilities::System::posix_memalign(reinterpret_cast<void **>(&new_data_ptr),
1411 64,
1412 new_size * sizeof(T));
1413
1414 // Now create a deleter that encodes what should happen when the object is
1415 // released: We need to destroy the objects that are currently alive (in
1416 // reverse order, and then release the memory. Note that we catch the
1417 // 'this' pointer because the number of elements currently alive might
1418 // change over time.
1419 Deleter deleter(this);
1420
1421 // copy whatever elements we need to retain
1422 if (new_allocated_size > 0)
1424 elements.get() + old_size,
1425 new_data_ptr);
1426
1427 // Now reset all the member variables of the current object
1428 // based on the allocation above. Assigning to a std::unique_ptr
1429 // object also releases the previously pointed to memory.
1430 //
1431 // Note that at the time of releasing the old memory, 'used_elements_end'
1432 // still points to its previous value, and this is important for the
1433 // deleter object of the previously allocated array (see how it loops over
1434 // the to-be-destroyed elements at the Deleter::DefaultDeleterAction
1435 // class).
1436 elements = decltype(elements)(new_data_ptr, std::move(deleter));
1437 used_elements_end = elements.get() + old_size;
1438 allocated_elements_end = elements.get() + new_size;
1439}
1440
1441
1442
1443template <class T>
1444inline void
1445AlignedVector<T>::reserve(const size_type new_allocated_size)
1446{
1447 const size_type old_size = used_elements_end - elements.get();
1448 const size_type old_allocated_size = allocated_elements_end - elements.get();
1449 if (new_allocated_size > old_allocated_size)
1450 {
1451 // if we continuously increase the size of the vector, we might be
1452 // reallocating a lot of times. therefore, try to increase the size more
1453 // aggressively
1454 const size_type new_size =
1455 std::max(new_allocated_size, 2 * old_allocated_size);
1456
1457 allocate_and_move(old_size, new_size, new_allocated_size);
1458 }
1459 else if (new_allocated_size == 0)
1460 clear();
1461 else // size_alloc < allocated_size
1462 {
1463 } // nothing to do here
1464}
1465
1466
1467
1468template <class T>
1469inline void
1471{
1472# ifdef DEBUG
1473 Assert(replicated_across_communicator == false,
1474 ExcAlignedVectorChangeAfterReplication());
1475# endif
1476 const size_type used_size = used_elements_end - elements.get();
1477 const size_type allocated_size = allocated_elements_end - elements.get();
1478 if (allocated_size > used_size)
1479 allocate_and_move(used_size, used_size, used_size);
1480}
1481
1482
1483
1484template <class T>
1485inline void
1487{
1488 // Just release the memory (which also calls the destructor of the elements),
1489 // and then set the auxiliary pointers to invalid values.
1490 //
1491 // Note that at the time of releasing the old memory, 'used_elements_end'
1492 // still points to its previous value, and this is important for the
1493 // deleter object of the previously allocated array (see how it loops over
1494 // the to-be-destroyed elements a few lines above).
1495 elements.reset();
1496 used_elements_end = nullptr;
1497 allocated_elements_end = nullptr;
1498}
1499
1500
1501
1502template <class T>
1503inline void
1504AlignedVector<T>::push_back(const T in_data)
1505{
1506 Assert(used_elements_end <= allocated_elements_end, ExcInternalError());
1507 if (used_elements_end == allocated_elements_end)
1508 reserve(std::max(2 * capacity(), static_cast<size_type>(16)));
1509 if (std::is_trivial_v<T> == false)
1510 new (used_elements_end++) T(in_data);
1511 else
1512 *used_elements_end++ = in_data;
1513}
1514
1515
1516
1517template <class T>
1518inline typename AlignedVector<T>::reference
1520{
1521 AssertIndexRange(0, size());
1522 T *field = used_elements_end - 1;
1523 return *field;
1524}
1525
1526
1527
1528template <class T>
1531{
1532 AssertIndexRange(0, size());
1533 const T *field = used_elements_end - 1;
1534 return *field;
1535}
1536
1537
1538
1539template <class T>
1540template <typename ForwardIterator>
1541inline void
1542AlignedVector<T>::insert_back(ForwardIterator begin, ForwardIterator end)
1543{
1544 const size_type old_size = size();
1545 reserve(old_size + (end - begin));
1546 for (; begin != end; ++begin, ++used_elements_end)
1547 {
1548 if (std::is_trivial_v<T> == false)
1549 new (used_elements_end) T;
1550 *used_elements_end = *begin;
1551 }
1552}
1553
1554
1555
1556template <class T>
1557inline void
1559{
1561 elements.get());
1562}
1563
1564
1565
1566template <class T>
1567inline void
1569{
1571 value,
1572 elements.get());
1573}
1574
1575
1576
1577template <class T>
1578inline void
1580 const unsigned int root_process)
1581{
1582# ifdef DEAL_II_WITH_MPI
1583
1584 // Let the root process broadcast its size. If it is zero, then all
1585 // processes just clear() their memory and reset themselves to a non-shared
1586 // empty object -- there is no point to run through complicated MPI
1587 // calls if the end result is an empty array. Otherwise, we continue on.
1588 const size_type new_size =
1589 Utilities::MPI::broadcast(communicator, size(), root_process);
1590 if (new_size == 0)
1591 {
1592 clear();
1593 return;
1594 }
1595
1596
1597 // **** Step 0 ****
1598 // All but the root process no longer need their data, so release the memory
1599 // used to store the previous elements.
1600 if (Utilities::MPI::this_mpi_process(communicator) != root_process)
1601 {
1602 elements.reset();
1603 used_elements_end = nullptr;
1604 allocated_elements_end = nullptr;
1605 }
1606
1607 // **** Step 1 ****
1608 // Create communicators for each group of processes that can use
1609 // shared memory areas. Within each of these groups, we don't care about
1610 // which rank each of the old processes gets except that we would like to
1611 // make sure that the (global) root process will have rank=0 within
1612 // its own sub-communicator. We can do that through the third argument of
1613 // MPI_Comm_split_type (the "key") which is an integer meant to indicate the
1614 // order of processes within the split communicators, and we should set it to
1615 // zero for the root processes and one for all others -- which means that
1616 // for all of these other processes, MPI can choose whatever order it
1617 // wants because they have the same key (MPI then documents that these ties
1618 // will be broken according to these processes' rank in the old group).
1619 //
1620 // At least that's the theory. In practice, the MPI implementation where
1621 // this function was developed on does not seem to do that. (Bug report
1622 // is here: https://github.com/open-mpi/ompi/issues/8854)
1623 // We work around this by letting MPI_Comm_split_type choose whatever
1624 // rank it wants, and then reshuffle with MPI_Comm_split in a second
1625 // step -- not elegant, nor efficient, but seems to work:
1626 MPI_Comm shmem_group_communicator;
1627 {
1628 MPI_Comm shmem_group_communicator_temp;
1629 int ierr = MPI_Comm_split_type(communicator,
1630 MPI_COMM_TYPE_SHARED,
1631 /* key */ 0,
1632 MPI_INFO_NULL,
1633 &shmem_group_communicator_temp);
1634 AssertThrowMPI(ierr);
1635
1636 const int key =
1637 (Utilities::MPI::this_mpi_process(communicator) == root_process ? 0 : 1);
1638 ierr = MPI_Comm_split(shmem_group_communicator_temp,
1639 /* color */ 0,
1640 key,
1641 &shmem_group_communicator);
1642 AssertThrowMPI(ierr);
1643
1644 // Verify the explanation from above
1645 if (Utilities::MPI::this_mpi_process(communicator) == root_process)
1646 Assert(Utilities::MPI::this_mpi_process(shmem_group_communicator) == 0,
1648
1649 // And get rid of the temporary communicator
1650 Utilities::MPI::free_communicator(shmem_group_communicator_temp);
1651 }
1652 const bool is_shmem_root =
1653 Utilities::MPI::this_mpi_process(shmem_group_communicator) == 0;
1654
1655 // **** Step 2 ****
1656 // We then have to send the state of the current object from the
1657 // root process to one exemplar in each shmem group. To this end,
1658 // we create another subcommunicator that includes the ranks zero
1659 // of all shmem groups, and because of the trick above, we know
1660 // that this also includes the original root process.
1661 //
1662 // There are different ways of creating a "shmem_roots_communicator".
1663 // The conceptually easiest way is to create an MPI_Group that only
1664 // includes the shmem roots and then create a communicator from this
1665 // via MPI_Comm_create or MPI_Comm_create_group. The problem
1666 // with this is that we would have to exchange among all processes
1667 // which ones are shmem roots and which are not. This is awkward.
1668 //
1669 // A simpler way is to use MPI_Comm_split that uses "colors" to
1670 // indicate which sub-communicator each process wants to be in.
1671 // We use color=0 to indicate the group of shmem roots, and color=1
1672 // for all other processes -- the latter will simply not ever do
1673 // anything among themselves with the communicator so created.
1674 //
1675 // Using MPI_Comm_split has the additional benefit that, just as above,
1676 // we can choose where each rank will end up in shmem_roots_communicator.
1677 // We again set key=0 for the original root_process, and key=1 for all other
1678 // ranks; then, the global root becomes rank=0 on the
1679 // shmem_roots_communicator. We don't care how the other processes are
1680 // ordered.
1681 MPI_Comm shmem_roots_communicator;
1682 {
1683 const int key =
1684 (Utilities::MPI::this_mpi_process(communicator) == root_process ? 0 : 1);
1685
1686 const int ierr = MPI_Comm_split(communicator,
1687 /*color=*/
1688 (is_shmem_root ? 0 : 1),
1689 key,
1690 &shmem_roots_communicator);
1691 AssertThrowMPI(ierr);
1692
1693 // Again verify the explanation from above
1694 if (Utilities::MPI::this_mpi_process(communicator) == root_process)
1695 Assert(Utilities::MPI::this_mpi_process(shmem_roots_communicator) == 0,
1697 }
1698
1699 const unsigned int shmem_roots_root_rank = 0;
1700 const bool is_shmem_roots_root =
1701 (is_shmem_root && (Utilities::MPI::this_mpi_process(
1702 shmem_roots_communicator) == shmem_roots_root_rank));
1703
1704 // Now let the original root_process broadcast the current object to all
1705 // shmem roots. We know that the last rank is the original root process that
1706 // has all of the data.
1707 if (is_shmem_root)
1708 {
1709 if (std::is_trivial_v<T>)
1710 {
1711 // The data is "trivial", i.e., we can copy things directly without
1712 // having to go through the serialization/deserialization machinery of
1713 // Utilities::MPI::broadcast.
1714 //
1715 // In that case, first tell all of the other shmem roots how many
1716 // elements we will have to deal with, and let them resize their
1717 // (non-shared) arrays.
1718 const size_type new_size =
1719 Utilities::MPI::broadcast(shmem_roots_communicator,
1720 size(),
1721 shmem_roots_root_rank);
1722 if (is_shmem_roots_root == false)
1723 resize(new_size);
1724
1725 // Then directly copy from the root process into these buffers
1726 int ierr = MPI_Bcast(elements.get(),
1727 sizeof(T) * new_size,
1728 MPI_CHAR,
1729 shmem_roots_root_rank,
1730 shmem_roots_communicator);
1731 AssertThrowMPI(ierr);
1732 }
1733 else
1734 {
1735 // The objects to be sent around are not "trivial", and so we have
1736 // to go through the serialization/deserialization machinery. On all
1737 // but the sending process, overwrite the current state with the
1738 // vector just broadcast.
1739 //
1740 // On the root rank, this would lead to resetting the 'entries'
1741 // pointer, which would trigger the deleter which would lead to a
1742 // deadlock. So we just send the result of the broadcast() call to
1743 // nirvana on the root process and keep our current state.
1744 if (Utilities::MPI::this_mpi_process(shmem_roots_communicator) == 0)
1745 Utilities::MPI::broadcast(shmem_roots_communicator,
1746 *this,
1747 shmem_roots_root_rank);
1748 else
1749 *this = Utilities::MPI::broadcast(shmem_roots_communicator,
1750 *this,
1751 shmem_roots_root_rank);
1752 }
1753 }
1754
1755 // We no longer need the shmem roots communicator, so get rid of it
1756 Utilities::MPI::free_communicator(shmem_roots_communicator);
1757
1758
1759 // **** Step 3 ****
1760 // At this point, all shmem groups have one shmem root process that has
1761 // a copy of the data. This is the point where each shmem group should
1762 // establish a shmem area to put the data into. As mentioned above,
1763 // we know that the shmem roots are the last rank in their respective
1764 // shmem_group_communicator.
1765 //
1766 // The process for all of this works as follows: While all processes in
1767 // the shmem group participate in the generation of the shmem memory window,
1768 // only the shmem root actually allocates any memory -- the rest just
1769 // allocate zero bytes of their own. We allocate space for exactly
1770 // size() elements (computed on the shmem_root that already has the data)
1771 // and add however many bytes are necessary so that we know that we can align
1772 // things to 64-byte boundaries. The worst case happens if the memory system
1773 // gives us a pointer to an address one byte past a desired alignment
1774 // boundary, and in that case aligning the memory will require us to waste the
1775 // first (align_by-1) bytes. So we have to ask for
1776 // size() * sizeof(T) + (align_by - 1)
1777 // bytes.
1778 //
1779 // Before MPI 4.0, there was no way to specify that we want memory aligned to
1780 // a certain number of bytes. This is going to come back to bite us further
1781 // down below when we try to get a properly aligned pointer to our memory
1782 // region, see the commentary there. Starting with MPI 4.0, one can set a
1783 // flag in an MPI_Info structure that requests a desired alignment, so we do
1784 // this for forward compatibility; MPI implementations ignore flags they don't
1785 // know anything about, and so setting this flag is backward compatible also
1786 // to older MPI versions.
1787 MPI_Win shmem_window;
1788 void *base_ptr;
1789 const MPI_Aint align_by = 64;
1790 const MPI_Aint alloc_size =
1791 Utilities::MPI::broadcast(shmem_group_communicator,
1792 (size() * sizeof(T) + (align_by - 1)),
1793 0);
1794
1795 {
1796 int ierr;
1797
1798 MPI_Info mpi_info;
1799 ierr = MPI_Info_create(&mpi_info);
1800 AssertThrowMPI(ierr);
1801 ierr = MPI_Info_set(mpi_info,
1802 "mpi_minimum_memory_alignment",
1803 std::to_string(align_by).c_str());
1804 AssertThrowMPI(ierr);
1805 ierr = MPI_Win_allocate_shared((is_shmem_root ? alloc_size : 0),
1806 /* disp_unit = */ 1,
1807 mpi_info,
1808 shmem_group_communicator,
1809 &base_ptr,
1810 &shmem_window);
1811 AssertThrowMPI(ierr);
1812
1813 ierr = MPI_Info_free(&mpi_info);
1814 AssertThrowMPI(ierr);
1815 }
1816
1817
1818 // **** Step 4 ****
1819 // The next step is to teach all non-shmem root processes what the pointer to
1820 // the array is that the shmem-root created. MPI has a nifty way for this
1821 // given that only a single process actually allocated memory in the window:
1822 // When calling MPI_Win_shared_query, the MPI documentation says that
1823 // "When rank is MPI_PROC_NULL, the pointer, disp_unit, and size returned are
1824 // the pointer, disp_unit, and size of the memory segment belonging the lowest
1825 // rank that specified size > 0. If all processes in the group attached to the
1826 // window specified size = 0, then the call returns size = 0 and a baseptr as
1827 // if MPI_ALLOC_MEM was called with size = 0."
1828 //
1829 // This will allow us to obtain the pointer to the shmem root's memory area,
1830 // which is the only one we care about. (None of the other processes have
1831 // even allocated any memory.)
1832 //
1833 // We don't need to do this on the shmem root process: This process has
1834 // already gotten its base_ptr correctly set above, and we can determine the
1835 // array size by just calling size().
1836 if (is_shmem_root == false)
1837 {
1838 int disp_unit;
1839 MPI_Aint alloc_size; // not actually used
1840 const int ierr = MPI_Win_shared_query(
1841 shmem_window, MPI_PROC_NULL, &alloc_size, &disp_unit, &base_ptr);
1842 AssertThrowMPI(ierr);
1843
1844 // Make sure we actually got a pointer, and check that the disp_unit is
1845 // equal to 1 (as set above)
1846 Assert(base_ptr != nullptr, ExcInternalError());
1847 Assert(disp_unit == 1, ExcInternalError());
1848 }
1849
1850
1851 // **** Step 5 ****
1852 // Now that all processes know the address of the space that is visible to
1853 // everyone, we need to figure out whether it is properly aligned and if not,
1854 // find the next aligned address.
1855 //
1856 // std::align does that, but it also modifies its last two arguments. The
1857 // documentation of that function at
1858 // https://en.cppreference.com/w/cpp/memory/align is not entirely clear, but I
1859 // *think* that the following should do given that we do not use base_ptr and
1860 // available_space any further after the call to std::align.
1861 std::size_t available_space = alloc_size;
1862 void *base_ptr_backup = base_ptr;
1863 T *aligned_shmem_pointer = static_cast<T *>(
1864 std::align(align_by, new_size * sizeof(T), base_ptr, available_space));
1865 Assert(aligned_shmem_pointer != nullptr, ExcInternalError());
1866
1867 // There is one step to guard against. It is *conceivable* that the base_ptr
1868 // we have previously obtained from MPI_Win_shared_query is mapped so
1869 // awkwardly into the different MPI processes' memory spaces that it is
1870 // aligned in one memory space, but not another. In that case, different
1871 // processes would align base_ptr differently, and adjust available_space
1872 // differently. We can check that by making sure that the max (or min) over
1873 // all processes is equal to every process's value. If that's not the case,
1874 // then the whole idea of aligning above is wrong and we need to rethink what
1875 // it means to align data in a shared memory space.
1876 //
1877 // One might be tempted to think that this is not how MPI implementations
1878 // actually arrange things. Alas, when developing this functionality in 2021,
1879 // this is really how at least OpenMPI ends up doing things. (This is with an
1880 // OpenMPI implementation of MPI 3.1, so it does not support the flag we set
1881 // in the MPI_Info structure above when allocating the memory window.) Indeed,
1882 // when running this code on three processes, one ends up with base_ptr values
1883 // of
1884 // base_ptr=0x7f0842f02108
1885 // base_ptr=0x7fc0a47881d0
1886 // base_ptr=0x7f64872db108
1887 // which, most annoyingly, are aligned to 8 and 16 byte boundaries -- so there
1888 // is no common offset std::align could find that leads to a 64-byte
1889 // aligned memory address in all three memory spaces. That's a tremendous
1890 // nuisance and there is really nothing we can do about this other than just
1891 // fall back on the (unaligned) base_ptr in that case.
1892 if (Utilities::MPI::min(available_space, shmem_group_communicator) !=
1893 Utilities::MPI::max(available_space, shmem_group_communicator))
1894 aligned_shmem_pointer = static_cast<T *>(base_ptr_backup);
1895
1896
1897 // **** Step 6 ****
1898 // If this is the shmem root process, we need to copy the data into the
1899 // shared memory space.
1900 if (is_shmem_root)
1901 {
1902 if (std::is_trivial_v<T> == true)
1903 std::memcpy(aligned_shmem_pointer, elements.get(), sizeof(T) * size());
1904 else
1905 for (std::size_t i = 0; i < size(); ++i)
1906 new (&aligned_shmem_pointer[i]) T(std::move(elements[i]));
1907 }
1908
1909 // Make sure that the shared memory host has copied the data before we try to
1910 // access it.
1911 const int ierr = MPI_Barrier(shmem_group_communicator);
1912 AssertThrowMPI(ierr);
1913
1914 // **** Step 7 ****
1915 // Finally, we need to set the pointers of this object to what we just
1916 // learned. This also releases all memory that may have been in use
1917 // previously.
1918 //
1919 // The part that is a bit tricky is how to write the deleter of this
1920 // shared memory object. When we want to get rid of it, we need to
1921 // also release the MPI_Win object along with the shmem_group_communicator
1922 // object. That's because as long as we use the shared memory, we still need
1923 // to hold on to the MPI_Win object, and the MPI_Win object is based on the
1924 // communicator. (The former is definitely true, the latter is not quite clear
1925 // from the MPI documentation, but seems reasonable.) So we need to have a
1926 // deleter for the pointer that ensures that upon release of the memory, we
1927 // not only call the destructor of these memory elements (but only once, on
1928 // the shmem root!) but also destroy the MPI_Win and the communicator. All of
1929 // that is encapsulated in the following call where the deleter makes copies
1930 // of the arguments in the lambda capture.
1931 elements = decltype(elements)(aligned_shmem_pointer,
1932 Deleter(this,
1933 is_shmem_root,
1934 aligned_shmem_pointer,
1935 shmem_group_communicator,
1936 shmem_window));
1937
1938 // We then also have to set the other two pointers that define the state of
1939 // the current object. Note that the new buffer size is exactly as large as
1940 // necessary, i.e., can store size() elements, regardless of the number of
1941 // allocated elements in the original objects.
1942 used_elements_end = elements.get() + new_size;
1943 allocated_elements_end = used_elements_end;
1944
1945 // **** Consistency check ****
1946 // At this point, each process should have a copy of the data.
1947 // Verify this in some sort of round-about way
1948# ifdef DEBUG
1949 replicated_across_communicator = true;
1950 const std::vector<char> packed_data = Utilities::pack(*this);
1951 const int hash =
1952 std::accumulate(packed_data.begin(), packed_data.end(), int(0));
1953 Assert(Utilities::MPI::max(hash, communicator) == hash, ExcInternalError());
1954# endif
1955
1956# else
1957 // No MPI -> nothing to replicate
1958 (void)communicator;
1959 (void)root_process;
1960# endif
1961}
1962
1963
1964
1965template <class T>
1966inline void
1968{
1969 // Swap the data in the 'elements' objects. Then also make sure that
1970 // their respective deleter objects point to the right place.
1971 std::swap(elements, vec.elements);
1972 elements.get_deleter().reset_owning_object(this);
1973 vec.elements.get_deleter().reset_owning_object(&vec);
1974
1975 // Now also swap the remaining members.
1976 std::swap(used_elements_end, vec.used_elements_end);
1977 std::swap(allocated_elements_end, vec.allocated_elements_end);
1978}
1979
1980
1981
1982template <class T>
1983inline bool
1985{
1986 return used_elements_end == elements.get();
1987}
1988
1989
1990
1991template <class T>
1992inline typename AlignedVector<T>::size_type
1994{
1995 return used_elements_end - elements.get();
1996}
1997
1998
1999
2000template <class T>
2001inline typename AlignedVector<T>::size_type
2003{
2004 return allocated_elements_end - elements.get();
2005}
2006
2007
2008
2009template <class T>
2010inline typename AlignedVector<T>::reference
2011AlignedVector<T>::operator[](const size_type index)
2012{
2013 AssertIndexRange(index, size());
2014 return elements[index];
2015}
2016
2017
2018
2019template <class T>
2021AlignedVector<T>::operator[](const size_type index) const
2022{
2023 AssertIndexRange(index, size());
2024 return elements[index];
2025}
2026
2027
2028
2029template <typename T>
2030inline typename AlignedVector<T>::pointer
2032{
2033 return elements.get();
2034}
2035
2036
2037
2038template <typename T>
2039inline typename AlignedVector<T>::const_pointer
2041{
2042 return elements.get();
2043}
2044
2045
2046
2047template <class T>
2048inline typename AlignedVector<T>::iterator
2050{
2051 return elements.get();
2052}
2053
2054
2055
2056template <class T>
2057inline typename AlignedVector<T>::iterator
2059{
2060 return used_elements_end;
2061}
2062
2063
2064
2065template <class T>
2068{
2069 return elements.get();
2070}
2071
2072
2073
2074template <class T>
2077{
2078 return used_elements_end;
2079}
2080
2081
2082
2083template <class T>
2084template <class Archive>
2085inline void
2086AlignedVector<T>::save(Archive &ar, const unsigned int) const
2087{
2088 size_type vec_size = size();
2089 ar &vec_size;
2090 if (vec_size > 0)
2091 ar &boost::serialization::make_array(elements.get(), vec_size);
2092}
2093
2094
2095
2096template <class T>
2097template <class Archive>
2098inline void
2099AlignedVector<T>::load(Archive &ar, const unsigned int)
2100{
2101 size_type vec_size = 0;
2102 ar &vec_size;
2103
2104 if (vec_size > 0)
2105 {
2106 reserve(vec_size);
2107 ar &boost::serialization::make_array(elements.get(), vec_size);
2108 used_elements_end = elements.get() + vec_size;
2109 }
2110}
2111
2112
2113
2114template <class T>
2115inline typename AlignedVector<T>::size_type
2117{
2118 size_type memory = sizeof(*this);
2119 for (const T *t = elements.get(); t != used_elements_end; ++t)
2121 memory += sizeof(T) * (allocated_elements_end - used_elements_end);
2122 return memory;
2123}
2124
2125
2126#endif // ifndef DOXYGEN
2127
2128
2134template <class T>
2135bool
2137{
2138 if (lhs.size() != rhs.size())
2139 return false;
2140 for (typename AlignedVector<T>::const_iterator lit = lhs.begin(),
2141 rit = rhs.begin();
2142 lit != lhs.end();
2143 ++lit, ++rit)
2144 if (*lit != *rit)
2145 return false;
2146 return true;
2147}
2148
2149
2150
2156template <class T>
2157bool
2159{
2160 return !(operator==(lhs, rhs));
2161}
2162
2163
2165
2166#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 swap(AlignedVector< T > &vec)
void resize(const size_type new_size, const T &init)
AlignedVector & operator=(AlignedVector< T > &&vec) noexcept
size_type capacity() const
value_type & reference
AlignedVector & operator=(const AlignedVector< T > &vec)
const value_type * const_pointer
bool replicated_across_communicator
void push_back(const T in_data)
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
virtual void apply_to_subrange(const std::size_t begin, const std::size_t end) const override
AlignedVectorCopyConstruct(const T *const source_begin, const T *const source_end, T *const destination)
static const std::size_t minimum_parallel_grain_size
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)
static const std::size_t minimum_parallel_grain_size
AlignedVectorMoveConstruct(T *const source_begin, T *const source_end, T *const destination)
virtual void apply_to_subrange(const std::size_t begin, const std::size_t end) const override
#define DEAL_II_NAMESPACE_OPEN
Definition config.h:502
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:503
#define Assert(cond, exc)
#define AssertThrowMPI(error_code)
#define AssertIndexRange(index, range)
#define DeclExceptionMsg(Exception, defaulttext)
Definition exceptions.h:494
static ::ExceptionBase & ExcInternalError()
static ::ExceptionBase & ExcAlignedVectorChangeAfterReplication()
spacedim const Point< spacedim > & p
Definition grid_tools.h:980
types::global_dof_index size_type
std::enable_if_t< std::is_fundamental_v< T >, std::size_t > memory_consumption(const T &t)
std::enable_if_t< is_mpi_type< T >==false, T > broadcast(const MPI_Comm comm, const T &object_to_send, const unsigned int root_process=0)
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:143
void free_communicator(MPI_Comm mpi_communicator)
Definition mpi.cc:190
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:1378
const Iterator const std_cxx20::type_identity_t< Iterator > & end
Definition parallel.h:610
const Iterator & begin
Definition parallel.h:609
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:742