Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement AlignedVector::shrink_to_fit() #15974

Merged
merged 3 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
129 changes: 99 additions & 30 deletions include/deal.II/base/aligned_vector.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,12 @@ class AlignedVector
void
reserve(const size_type new_allocated_size);

/**
* Releases the memory allocated but not used.
*/
void
shrink_to_fit();

/**
* Releases all previously allocated memory and leaves the vector in a state
* equivalent to the state after the default constructor has been called.
Expand Down Expand Up @@ -463,7 +469,26 @@ class AlignedVector
BOOST_SERIALIZATION_SPLIT_MEMBER()
#endif

/**
* Exception message for changing the vector after a call to
* replicate_across_communicator().
*
* @ingroup Exceptions
*/
DeclExceptionMsg(ExcAlignedVectorChangeAfterReplication,
"Changing the vector after a call to "
"replicate_across_communicator() is not allowed.");
Comment on lines +478 to +480
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have introduced this exception message which we can use for all non-const functions in a follow-up.


private:
/**
* Allocates a new vector and moves the data from the old vector to the new
* vector. Deletes the old vector and releases the memory.
bergbauer marked this conversation as resolved.
Show resolved Hide resolved
*/
void
allocate_and_move(const size_t old_size,
const size_t new_size,
const size_t new_allocated_size);

/**
* A class that is used as the "deleter" for a `std::unique_ptr` object that
* AlignedVector uses to store the memory used for the elements.
Expand Down Expand Up @@ -684,6 +709,11 @@ class AlignedVector
* Pointer to the end of the allocated memory.
*/
T *allocated_elements_end;

/**
* Flag indicating if replicate_across_communicator() has been called.
*/
bool replicated_across_communicator;
};


Expand Down Expand Up @@ -1157,6 +1187,9 @@ inline AlignedVector<T>::AlignedVector()
: elements(nullptr, Deleter(this))
, used_elements_end(nullptr)
, allocated_elements_end(nullptr)
# ifdef DEBUG
, replicated_across_communicator(false)
# endif
{}


Expand All @@ -1166,6 +1199,9 @@ inline AlignedVector<T>::AlignedVector(const size_type size, const T &init)
: elements(nullptr, Deleter(this))
, used_elements_end(nullptr)
, allocated_elements_end(nullptr)
# ifdef DEBUG
, replicated_across_communicator(false)
# endif
{
if (size > 0)
resize(size, init);
Expand All @@ -1178,6 +1214,9 @@ inline AlignedVector<T>::AlignedVector(const AlignedVector<T> &vec)
: elements(nullptr, Deleter(this))
, used_elements_end(nullptr)
, allocated_elements_end(nullptr)
# ifdef DEBUG
, replicated_across_communicator(false)
# endif
{
// copy the data from vec
reserve(vec.size());
Expand Down Expand Up @@ -1358,6 +1397,48 @@ AlignedVector<T>::resize(const size_type new_size, const T &init)



template <class T>
inline void
AlignedVector<T>::allocate_and_move(const size_t old_size,
const size_t new_size,
const size_t new_allocated_size)
{
// allocate and align along 64-byte boundaries (this is enough for all
// levels of vectorization currently supported by deal.II)
T *new_data_ptr;
Utilities::System::posix_memalign(reinterpret_cast<void **>(&new_data_ptr),
64,
new_size * sizeof(T));

// Now create a deleter that encodes what should happen when the object is
// released: We need to destroy the objects that are currently alive (in
// reverse order, and then release the memory. Note that we catch the
// 'this' pointer because the number of elements currently alive might
// change over time.
Deleter deleter(this);

// copy whatever elements we need to retain
if (new_allocated_size > 0)
dealii::internal::AlignedVectorMoveConstruct<T>(elements.get(),
elements.get() + old_size,
new_data_ptr);

// Now reset all the member variables of the current object
// based on the allocation above. Assigning to a std::unique_ptr
// object also releases the previously pointed to memory.
//
// Note that at the time of releasing the old memory, 'used_elements_end'
// still points to its previous value, and this is important for the
// deleter object of the previously allocated array (see how it loops over
// the to-be-destroyed elements at the Deleter::DefaultDeleterAction
// class).
elements = decltype(elements)(new_data_ptr, std::move(deleter));
used_elements_end = elements.get() + old_size;
allocated_elements_end = elements.get() + new_size;
}



template <class T>
inline void
AlignedVector<T>::reserve(const size_type new_allocated_size)
Expand All @@ -1372,36 +1453,7 @@ AlignedVector<T>::reserve(const size_type new_allocated_size)
const size_type new_size =
std::max(new_allocated_size, 2 * old_allocated_size);

// allocate and align along 64-byte boundaries (this is enough for all
// levels of vectorization currently supported by deal.II)
T *new_data_ptr;
Utilities::System::posix_memalign(
reinterpret_cast<void **>(&new_data_ptr), 64, new_size * sizeof(T));

// Now create a deleter that encodes what should happen when the object is
// released: We need to destroy the objects that are currently alive (in
// reverse order, and then release the memory. Note that we catch the
// 'this' pointer because the number of elements currently alive might
// change over time.
Deleter deleter(this);

// copy whatever elements we need to retain
if (new_allocated_size > 0)
dealii::internal::AlignedVectorMoveConstruct<T>(
elements.get(), elements.get() + old_size, new_data_ptr);

// Now reset all of the member variables of the current object
// based on the allocation above. Assigning to a std::unique_ptr
// object also releases the previously pointed to memory.
//
// Note that at the time of releasing the old memory, 'used_elements_end'
// still points to its previous value, and this is important for the
// deleter object of the previously allocated array (see how it loops over
// the to-be-destroyed elements a the Deleter::DefaultDeleterAction
// class).
elements = decltype(elements)(new_data_ptr, std::move(deleter));
used_elements_end = elements.get() + old_size;
allocated_elements_end = elements.get() + new_size;
allocate_and_move(old_size, new_size, new_allocated_size);
}
else if (new_allocated_size == 0)
clear();
Expand All @@ -1412,6 +1464,22 @@ AlignedVector<T>::reserve(const size_type new_allocated_size)



template <class T>
inline void
AlignedVector<T>::shrink_to_fit()
{
# ifdef DEBUG
Assert(replicated_across_communicator == false,
ExcAlignedVectorChangeAfterReplication());
# endif
Comment on lines +1471 to +1474
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we don't need to guard this. Assert is only performed in debug mode.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In FEEvaluationData the Asserts are also guarded by #ifdef DEBUG. I can remove it if it is not necessary.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Let's keep it this way to be consistent.

const size_type used_size = used_elements_end - elements.get();
const size_type allocated_size = allocated_elements_end - elements.get();
if (allocated_size > used_size)
allocate_and_move(used_size, used_size, used_size);
}



template <class T>
inline void
AlignedVector<T>::clear()
Expand Down Expand Up @@ -1877,6 +1945,7 @@ AlignedVector<T>::replicate_across_communicator(const MPI_Comm communicator,
// At this point, each process should have a copy of the data.
// Verify this in some sort of round-about way
# ifdef DEBUG
replicated_across_communicator = true;
const std::vector<char> packed_data = Utilities::pack(*this);
const int hash =
std::accumulate(packed_data.begin(), packed_data.end(), int(0));
Expand Down
4 changes: 4 additions & 0 deletions tests/base/aligned_vector_01.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ test()
deallog << a[i] << ' ';
deallog << std::endl;

deallog << "Memory Shrinking: ";
deallog << a.memory_consumption() << " to ";
a.resize(4);
a.shrink_to_fit();
deallog << a.memory_consumption() << std::endl;
deallog << "Shrinking: ";
for (unsigned int i = 0; i < a.size(); ++i)
deallog << a[i] << ' ';
Expand Down
1 change: 1 addition & 0 deletions tests/base/aligned_vector_01.output
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

DEAL::Constructor: 0 0 0 0
DEAL::Insertion: 0 0 1 0 5 42 0 0 1 0 5 42 27
DEAL::Memory Shrinking: 112 to 64
DEAL::Shrinking: 0 0 1 0
DEAL::Reserve: 0 0 1 0
DEAL::Assignment: 0 0 1 0 5 42 27
Expand Down