ENH: improve DynamicList shrink and swapping

- shrink_to_fit()
  corresponds to std::vector naming.
  For DynamicList it is a *binding* request.

- shrink_unsafe()
  simply adjusts the capacity() to match the
  current size() without forcing a re-allocation.

  Useful when collapsing to a non-dynamic list to avoid reallocation
  and copying contents. The memory cleanup will still occur properly
  at a later stage.

- DynamicList::swap(List&)
  simple recovery of content into a non-dynamic List that also
  ensures that the capacity is correctly updated.

STYLE: promote List::capacity() to public visibility (like std::vector)

STYLE: remove unused expandStorage() method

- simply a wrapper for resize(capacity())
This commit is contained in:
Mark Olesen 2023-09-25 10:58:13 +02:00 committed by Andrew Heather
parent 1d43e45fdd
commit ce1260cf70
12 changed files with 220 additions and 109 deletions

View File

@ -5,7 +5,7 @@
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2021-2022 OpenCFD Ltd.
Copyright (C) 2021-2023 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -144,6 +144,29 @@ int main(int argc, char *argv[])
list1.setCapacity(3);
printInfo("", list1);
std::fill_n(std::back_inserter(list1), 10, 5);
Info<< "back_inserter to fill some values" << nl;
printInfo("", list1);
// Not very efficient, but just test for capability
DynamicList<label, 64> list2;
list2.resize(5);
ListOps::identity(list2);
Info<< "initial list" << nl;
printInfo("", list2);
labelRange range(10, 5);
std::copy(range.begin(), range.end(), std::back_inserter(list2));
Info<< "back_inserter to append some values" << nl;
printInfo("", list2);
range.reset(0, 4);
std::copy_n(range.begin(), range.size(), std::back_inserter(list2));
Info<< "back_inserter to append more values" << nl;
printInfo("", list2);
}
Info<< "\nEnd\n";

View File

@ -167,7 +167,7 @@ public:
// Member Functions
// Access
// Capacity
//- Normal lower capacity limit - the SizeMin template parameter
static constexpr label min_size() noexcept { return SizeMin; }
@ -241,20 +241,23 @@ public:
//- Clear the list and delete storage.
inline void clearStorage();
//- Expand the addressable size to fit the allocated capacity.
// Returns the previous addressable size.
inline label expandStorage() noexcept;
//- Shrink the allocated space to the number of elements used.
inline void shrinkStorage();
inline void shrink_to_fit();
//- Shrink the allocated space to the number of elements used.
// Returns a reference to the DynamicList.
//- Shrink the internal bookkeeping of the allocated space to the
//- number of addressed elements without affecting allocation.
// \note when empty() it will delete any allocated memory.
inline void shrink_unsafe();
//- Calls shrink_to_fit() and returns a reference to the DynamicList.
inline DynamicList<T, SizeMin>& shrink();
// Edit
//- Swap with plain List content. Implies shrink_to_fit().
inline void swap(List<T>& list);
//- Swap content, independent of sizing parameter
template<int AnySizeMin>
inline void swap(DynamicList<T, AnySizeMin>& other) noexcept;

View File

@ -422,41 +422,64 @@ inline void Foam::DynamicList<T, SizeMin>::clearStorage()
template<class T, int SizeMin>
inline Foam::label Foam::DynamicList<T, SizeMin>::expandStorage() noexcept
{
const label currLen = List<T>::size();
// Address into the entire list
List<T>::setAddressableSize(capacity_);
return currLen;
}
template<class T, int SizeMin>
inline void Foam::DynamicList<T, SizeMin>::shrinkStorage()
inline void Foam::DynamicList<T, SizeMin>::shrink_to_fit()
{
const label currLen = List<T>::size();
if (currLen < capacity_)
{
// Adjust addressable size to trigger proper resizing
List<T>::setAddressableSize(currLen+1);
List<T>::resize(currLen);
capacity_ = List<T>::size();
}
}
template<class T, int SizeMin>
inline void Foam::DynamicList<T, SizeMin>::shrink_unsafe()
{
if (List<T>::empty())
{
// Delete storage if empty
List<T>::clear();
}
capacity_ = List<T>::size();
}
template<class T, int SizeMin>
inline Foam::DynamicList<T, SizeMin>&
Foam::DynamicList<T, SizeMin>::shrink()
{
this->shrinkStorage();
this->shrink_to_fit();
return *this;
}
template<class T, int SizeMin>
inline void
Foam::DynamicList<T, SizeMin>::swap(List<T>& list)
{
if
(
static_cast<const List<T>*>(this)
== static_cast<const List<T>*>(&list)
)
{
return; // Self-swap is a no-op
}
// Remove unused storage
this->shrink_to_fit();
// Swap storage and addressable size
UList<T>::swap(list);
// Update capacity
capacity_ = List<T>::size();
}
template<class T, int SizeMin>
template<int AnySizeMin>
inline void Foam::DynamicList<T, SizeMin>::swap
@ -485,9 +508,8 @@ template<class T, int SizeMin>
inline void
Foam::DynamicList<T, SizeMin>::transfer(List<T>& list)
{
// Take over storage, clear addressing for list
capacity_ = list.size();
List<T>::transfer(list);
capacity_ = List<T>::size();
}
@ -508,12 +530,11 @@ Foam::DynamicList<T, SizeMin>::transfer
return; // Self-assignment is a no-op
}
// Take over storage as-is (without shrink, without using SizeMin)
// clear addressing and storage for old lst.
// Take over storage as-is (without shrink)
capacity_ = list.capacity();
List<T>::transfer(static_cast<List<T>&>(list));
list.clearStorage(); // Ensure capacity=0
list.clearStorage(); // capacity=0 etc.
}
@ -662,7 +683,7 @@ inline void Foam::DynamicList<T, SizeMin>::push_back
)
{
push_back(std::move(static_cast<List<T>&>(list)));
list.clearStorage(); // Ensure capacity=0
list.clearStorage(); // Deletion, capacity=0 etc.
}

View File

@ -351,12 +351,13 @@ template<class T>
template<int SizeMin>
void Foam::List<T>::transfer(DynamicList<T, SizeMin>& list)
{
// Shrink the allocated space to the number of elements used
list.shrink();
transfer(static_cast<List<T>&>(list));
// Remove existing contents before anything else.
clear();
// Ensure DynamicList has proper capacity=0 too
list.clearStorage();
// Shrink the allocated space to the number of elements used
list.shrink_to_fit();
transfer(static_cast<List<T>&>(list));
list.clearStorage(); // Deletion, capacity=0 etc.
}

View File

@ -115,9 +115,6 @@ class List
// Methods as per DynamicList to simplify code maintenance
//- Stub method for internal naming as per DynamicList
label capacity() const noexcept { return UList<T>::size(); }
//- Stub method for internal naming as per DynamicList
void setCapacity_nocopy(const label len) { resize_nocopy(len); }
@ -289,7 +286,7 @@ public:
// Member Operators
//- Assignment to UList operator. Takes linear time
void operator=(const UList<T>& a);
void operator=(const UList<T>& list);
//- Assignment operator. Takes linear time
void operator=(const List<T>& list);

View File

@ -482,9 +482,12 @@ public:
//- True if List is empty (ie, size() is zero)
bool empty() const noexcept { return !size_; }
//- The number of elements in the List
//- The number of elements in the container
label size() const noexcept { return size_; }
//- Size of the underlying storage.
label capacity() const noexcept { return size_; }
//- The size of the largest possible UList
static constexpr label max_size() noexcept { return labelMax; }
@ -497,22 +500,22 @@ public:
//- Equality operation on ULists of the same type.
// Returns true when the ULists are element-wise equal
// (using UList::value_type::operator==). Takes linear time
bool operator==(const UList<T>& a) const;
bool operator==(const UList<T>& list) const;
//- The opposite of the equality operation. Takes linear time
bool operator!=(const UList<T>& a) const;
bool operator!=(const UList<T>& list) const;
//- Compare two ULists lexicographically. Takes linear time
bool operator<(const UList<T>& list) const;
//- Compare two ULists lexicographically. Takes linear time
bool operator>(const UList<T>& a) const;
bool operator>(const UList<T>& list) const;
//- Return true if !(a > b). Takes linear time
bool operator<=(const UList<T>& a) const;
bool operator<=(const UList<T>& list) const;
//- Return true if !(a < b). Takes linear time
bool operator>=(const UList<T>& a) const;
bool operator>=(const UList<T>& list) const;
// Reading/writing

View File

@ -121,12 +121,16 @@ public:
//- Clear the list and delete storage.
inline void clearStorage();
//- Expand the addressable size to fit the allocated capacity.
// Returns the previous addressable size.
inline label expandStorage() noexcept;
//- Shrink the allocated space to the number of elements used.
inline void shrink();
inline void shrink_to_fit();
//- Shrink the internal bookkeeping of the allocated space to the
//- number of addressed elements without affecting allocation.
// \note when empty() it will delete any allocated memory.
inline void shrink_unsafe();
//- Calls shrink_to_fit()
void shrink() { shrink_to_fit(); }
// Edit
@ -136,9 +140,12 @@ public:
// \return the number of non-null entries
inline label squeezeNull();
//- Swap with plain PtrList content. Implies shrink_to_fit().
inline void swap(PtrList<T>& list);
//- Swap content, independent of sizing parameter
template<int AnySizeMin>
inline void swap(PtrDynList<T, AnySizeMin>& other);
inline void swap(PtrDynList<T, AnySizeMin>& other) noexcept;
//- Transfer contents of the argument PtrList into this.
inline void transfer(PtrList<T>& list);

View File

@ -177,32 +177,31 @@ inline void Foam::PtrDynList<T, SizeMin>::clearStorage()
template<class T, int SizeMin>
inline Foam::label Foam::PtrDynList<T, SizeMin>::expandStorage() noexcept
{
const label currLen = PtrList<T>::size();
// Allow addressing into the entire list
PtrList<T>::setAddressableSize(capacity_);
return currLen;
}
template<class T, int SizeMin>
inline void Foam::PtrDynList<T, SizeMin>::shrink()
inline void Foam::PtrDynList<T, SizeMin>::shrink_to_fit()
{
const label currLen = PtrList<T>::size();
if (currLen < capacity_)
{
// Adjust addressable size to trigger proper resizing
PtrList<T>::setAddressableSize(currLen+1);
PtrList<T>::resize(currLen);
capacity_ = PtrList<T>::size();
}
}
template<class T, int SizeMin>
inline void Foam::PtrDynList<T, SizeMin>::shrink_unsafe()
{
if (PtrList<T>::empty())
{
// Delete empty list
PtrList<T>::clear();
}
capacity_ = PtrList<T>::size();
}
template<class T, int SizeMin>
inline Foam::label Foam::PtrDynList<T, SizeMin>::squeezeNull()
{
@ -212,12 +211,35 @@ inline Foam::label Foam::PtrDynList<T, SizeMin>::squeezeNull()
}
template<class T, int SizeMin>
inline void Foam::PtrDynList<T, SizeMin>::swap(PtrList<T>& list)
{
if
(
static_cast<const PtrList<T>*>(this)
== static_cast<const PtrList<T>*>(&list)
)
{
return; // Self-swap is a no-op
}
// Remove unused storage
this->shrink_to_fit();
// Swap storage and addressable size
UPtrList<T>::swap(list);
// Update capacity
capacity_ = PtrList<T>::size();
}
template<class T, int SizeMin>
template<int AnySizeMin>
inline void Foam::PtrDynList<T, SizeMin>::swap
(
PtrDynList<T, AnySizeMin>& other
)
) noexcept
{
if
(
@ -248,9 +270,8 @@ inline void Foam::PtrDynList<T, SizeMin>::transfer(PtrList<T>& list)
return; // Self assignment is a no-op
}
// Take over storage, clear addressing for list
capacity_ = list.size();
PtrList<T>::transfer(list);
capacity_ = PtrList<T>::size();
}
@ -270,10 +291,11 @@ inline void Foam::PtrDynList<T, SizeMin>::transfer
return; // Self assignment is a no-op
}
// Take over storage as-is (without shrink, without using SizeMin)
// Take over storage as-is (without shrink)
capacity_ = list.capacity();
PtrList<T>::transfer(list);
list.clearStorage(); // Ensure capacity=0
PtrList<T>::transfer(static_cast<PtrList<T>&>(list));
list.clearStorage(); // capacity=0 etc.
}
@ -496,7 +518,7 @@ template<class T, int SizeMin>
inline void Foam::PtrDynList<T, SizeMin>::reorder(const labelUList& oldToNew)
{
// Shrinking first is a bit annoying, but saves needing a special version.
shrink();
this->shrink_to_fit();
PtrList<T>::reorder(oldToNew);
}

View File

@ -254,6 +254,9 @@ public:
//- The number of entries in the list
inline label size() const noexcept;
//- Size of the underlying storage.
inline label capacity() const noexcept;
//- The number of non-null entries in the list
inline label count() const noexcept;
@ -322,7 +325,7 @@ public:
inline void push_back(UPtrList<T>&& other);
//- Swap content
inline void swap(UPtrList<T>& list);
inline void swap(UPtrList<T>& list) noexcept;
//- Transfer contents into this list and annul the argument
inline void transfer(UPtrList<T>& list);

View File

@ -123,6 +123,13 @@ inline Foam::label Foam::UPtrList<T>::size() const noexcept
}
template<class T>
inline Foam::label Foam::UPtrList<T>::capacity() const noexcept
{
return ptrs_.capacity();
}
template<class T>
inline Foam::label Foam::UPtrList<T>::count() const noexcept
{
@ -213,7 +220,7 @@ inline void Foam::UPtrList<T>::free()
template<class T>
inline void Foam::UPtrList<T>::swap(UPtrList<T>& list)
inline void Foam::UPtrList<T>::swap(UPtrList<T>& list) noexcept
{
ptrs_.swap(list.ptrs_);
}

View File

@ -178,7 +178,7 @@ public:
// Member Functions
// Sizing
// Capacity
//- Normal lower capacity limit - the SizeMin template parameter
static constexpr label min_size() noexcept { return SizeMin; }
@ -190,6 +190,9 @@ public:
// \note Only meaningful for contiguous data
inline std::streamsize capacity_bytes() const noexcept;
// Sizing
//- Alter the size of the underlying storage.
// The addressed size will be truncated if needed to fit, but will
// remain otherwise untouched.
@ -246,20 +249,23 @@ public:
//- Clear the list and delete storage.
inline void clearStorage();
//- Expand the addressable size to fit the allocated capacity.
// Returns the previous addressable size.
inline label expandStorage() noexcept;
//- Shrink the allocated space to the number of elements used.
inline void shrinkStorage();
inline void shrink_to_fit();
//- Shrink the allocated space to the number of elements used.
// Returns a reference to the DynamicField.
//- Shrink the internal bookkeeping of the allocated space to the
//- number of addressed elements without affecting allocation.
// \note when empty() it will delete any allocated memory.
inline void shrink_unsafe();
//- Calls shrink_to_fit() and returns a reference to the DynamicField.
inline DynamicField<T, SizeMin>& shrink();
// Edit
//- Swap with plain List content. Implies shrink_to_fit().
inline void swap(List<T>& list);
//- Swap content, independent of sizing parameter
template<int AnySizeMin>
inline void swap(DynamicField<T, AnySizeMin>& other);

View File

@ -438,19 +438,7 @@ inline void Foam::DynamicField<T, SizeMin>::clearStorage()
template<class T, int SizeMin>
inline Foam::label Foam::DynamicField<T, SizeMin>::expandStorage() noexcept
{
const label currLen = List<T>::size();
// Allow addressing into the entire list
List<T>::setAddressableSize(capacity_);
return currLen;
}
template<class T, int SizeMin>
inline void Foam::DynamicField<T, SizeMin>::shrinkStorage()
inline void Foam::DynamicField<T, SizeMin>::shrink_to_fit()
{
const label currLen = List<T>::size();
@ -458,22 +446,57 @@ inline void Foam::DynamicField<T, SizeMin>::shrinkStorage()
{
// Adjust addressable size to trigger proper resizing
List<T>::setAddressableSize(currLen+1);
List<T>::resize(currLen);
capacity_ = List<T>::size();
}
}
template<class T, int SizeMin>
inline void Foam::DynamicField<T, SizeMin>::shrink_unsafe()
{
if (List<T>::empty())
{
// Delete storage if empty
List<T>::clear();
}
capacity_ = List<T>::size();
}
template<class T, int SizeMin>
inline Foam::DynamicField<T, SizeMin>&
Foam::DynamicField<T, SizeMin>::shrink()
{
this->shrinkStorage();
this->shrink_to_fit();
return *this;
}
template<class T, int SizeMin>
inline void
Foam::DynamicField<T, SizeMin>::swap(List<T>& list)
{
if
(
static_cast<const List<T>*>(this)
== static_cast<const List<T>*>(&list)
)
{
return; // Self-swap is a no-op
}
// Remove unused storage
this->shrink_to_fit();
// Swap storage and addressable size
UList<T>::swap(list);
// Update capacity
capacity_ = List<T>::size();
}
template<class T, int SizeMin>
template<int AnySizeMin>
inline void Foam::DynamicField<T, SizeMin>::swap
@ -529,9 +552,8 @@ inline void Foam::DynamicField<T, SizeMin>::swap
template<class T, int SizeMin>
inline void Foam::DynamicField<T, SizeMin>::transfer(List<T>& list)
{
// Take over storage, clear addressing for list
capacity_ = list.size();
Field<T>::transfer(list);
capacity_ = Field<T>::size();
}
@ -551,12 +573,10 @@ inline void Foam::DynamicField<T, SizeMin>::transfer
return; // Self-assignment is a no-op
}
// Take over storage as-is (without shrink, without using SizeMin)
// clear addressing and storage for old list.
// Take over storage as-is (without shrink)
capacity_ = list.capacity();
Field<T>::transfer(static_cast<List<T>&>(list));
list.clearStorage(); // Ensure capacity=0
list.clearStorage(); // capacity=0 etc.
}
@ -576,12 +596,10 @@ inline void Foam::DynamicField<T, SizeMin>::transfer
return; // Self-assignment is a no-op
}
// Take over storage as-is (without shrink, without using SizeMin)
// clear addressing and storage for old list.
// Take over storage as-is (without shrink)
capacity_ = list.capacity();
Field<T>::transfer(static_cast<List<T>&>(list));
list.clearStorage(); // Ensure capacity=0
list.clearStorage(); // capacity=0 etc.
}
@ -818,10 +836,10 @@ inline Foam::Istream& Foam::DynamicField<T, SizeMin>::readList
// The logic should be the same and this avoids duplicate code
DynamicList<T, SizeMin> list;
(*this).swap(list);
this->swap(list);
list.readList(is);
(*this).swap(list);
this->swap(list);
return is;
}