Skip to content

Commit

Permalink
PagedEntityContainer: free memory for expired pages when `ReuseSlots=…
Browse files Browse the repository at this point in the history
…false`

This helps to keep memory consumption under control.

Existing expired pages are left in the deactivated
state indefinitely instead of reallocating their storage
upon expiration, so that we don't break the indexing
and memory safety guarantees for the container.

Signed-off-by: Pavel Solodovnikov <[email protected]>
  • Loading branch information
ManManson authored and past-due committed Mar 21, 2024
1 parent e8081cb commit cc0e573
Showing 1 changed file with 41 additions and 0 deletions.
41 changes: 41 additions & 0 deletions lib/framework/paged_entity_container.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ class PagedEntityContainer
/// as well as the queue for recycled indices.
///
/// Always allocates storage for exactly `MaxElementsPerPage` elements.
///
/// If the parent container is instantiated with the `ReuseSlots=false` mode,
/// expired pages will deallocate their storage to keep memory consumption
/// under control.
/// </summary>
class Page
{
Expand Down Expand Up @@ -279,11 +283,23 @@ class PagedEntityContainer
++_expiredSlotsCount;
}

// When `ReuseSlots=false`, expired pages will also deallocate
// their storage upon reaching "expired" state.
bool is_expired() const
{
return _expiredSlotsCount == MaxElementsPerPage;
}

// This method renders the page unusable!
//
// The page will then need to call `allocate_storage()` + `reset_metadata()`
// to be usable once again.
void deallocate_storage()
{
_storage.reset();
_slotMetadata.reset();
}

// Reset generations to least possible valid value for all slots,
// plus mark all slots as dead, so that the page appears clean and empty.
void reset_metadata()
Expand Down Expand Up @@ -428,6 +444,19 @@ class PagedEntityContainer
// Increase counters for expired slots tracking.
_pages[pageIdx.first].increase_expired_slots_count();
++_expiredSlotsCount;
// Looks like at least GCC is smart enough to optimize the following
// instructions away, even when this is not a `constexpr if`, but a regular `if`,
// See https://godbolt.org/z/bY8oEYsdz.
if (!ReuseSlots)
{
if (_pages[pageIdx.first].is_expired())
{
// Free storage pointers for expired pages when not reusing slots,
// this should alleviate the problem of excessive memory consumption
// in such cases.
_pages[pageIdx.first].deallocate_storage();
}
}
}
else
{
Expand Down Expand Up @@ -695,6 +724,18 @@ class PagedEntityContainer
_capacity = MaxElementsPerPage;
// No valid items in the container now.
_maxIndex = INVALID_SLOT_IDX;
if (!ReuseSlots)
{
// If the first page is in the expired state, then
// its storage is deallocated, too. So, reallocate it.
//
// NOTE: expired pages free their storage only when
// the slot recycling mechanism is disabled!
if (_pages.front().is_expired())
{
_pages.front().allocate_storage();
}
}
_pages.front().reset_metadata();
_expiredSlotsCount = 0;
}
Expand Down

0 comments on commit cc0e573

Please sign in to comment.