Skip to content

Commit

Permalink
Make inline allocations behave as initially allocated
Browse files Browse the repository at this point in the history
- Fix unnecessary character destruction on shrinking Resize() and Clear()
- Optimize reallocations from primary to secondary allocations by providing used capacity parameter
- Proper move implementation for inline allocation
- HeapString can now handle having empty allocation
  • Loading branch information
doanamo committed Jan 5, 2025
1 parent 8a57af3 commit d79f5b2
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 101 deletions.
13 changes: 5 additions & 8 deletions Engine/Common/Containers/Array.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,15 @@ class Array final

void ShrinkToFit()
{
m_allocation.Resize(m_size);
m_allocation.Resize(m_size, m_size);
}

void Reserve(const u64 capacity, const bool exact = true)
{
if(capacity > m_allocation.GetCapacity())
{
const u64 newCapacity = exact ? capacity : CalculateCapacity(capacity);
m_allocation.Resize(newCapacity);
m_allocation.Resize(newCapacity, m_size);
}
}

Expand Down Expand Up @@ -196,12 +196,6 @@ class Array final
{
ASSERT(newCapacity != 0);

// Determine if we should use initial capacity recommended by allocator.
if(newCapacity <= Allocation::GetInitialCapacity())
{
return Allocation::GetInitialCapacity();
}

// Find the next power of two capacity (unless already power of two),
// but not smaller than some predefined minimum starting capacity.
return std::max(4ull, NextPow2(newCapacity - 1ull));
Expand All @@ -215,4 +209,7 @@ static_assert(sizeof(Array<u64>) == 24);
template<typename ElementType, u64 ElementCount>
using InlineArray = Array<ElementType, Memory::InlineAllocator<ElementCount>>;

template<typename ElementType>
using HeapArray = Array<ElementType, Memory::DefaultAllocator>;

// #todo: Add static array with inline allocator and no backing allocator (or always asserting one).
118 changes: 67 additions & 51 deletions Engine/Common/Containers/String.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class StringViewBase;
template<typename CharType, typename Allocator>
class StringBase
{
static_assert(std::is_trivial_v<CharType>);
using Allocation = typename Allocator::template TypedAllocation<CharType>;
Allocation m_allocation;
u64 m_length = 0;
Expand All @@ -20,11 +21,10 @@ class StringBase
static constexpr u64 CharSize = sizeof(CharType);
static constexpr u64 NullCount = 1;
static constexpr CharType NullChar = '\0';
static constexpr CharType EmptyString[NullCount] = { NullChar };
static constexpr CharType* EmptyString = "";

StringBase()
{
// #todo: Empty heap string will always allocate. Find a way to avoid this.
ConstructFromText(EmptyString, 0);
}

Expand Down Expand Up @@ -101,67 +101,74 @@ class StringBase

void ShrinkToFit()
{
m_allocation.Resize(m_length + NullCount);
const u64 usedCapacity = m_length + NullCount;
m_allocation.Resize(m_length ? usedCapacity : 0, usedCapacity);
}

void Reserve(const u64 length, const bool exact = true)
{
u64 newCapacity = length + NullCount;
u64 newCapacity = length ? length + NullCount : 0;
if(newCapacity > m_allocation.GetCapacity())
{
if(!exact)
{
newCapacity = CalculateCapacity(newCapacity);
}

m_allocation.Resize(newCapacity);
const u64 usedCapacity = m_length + NullCount;
m_allocation.Resize(newCapacity, usedCapacity);
}
}

void Resize(const u64 length, const CharType fillCharacter = ' ')
{
if(length > m_length) // Grow length
if(length > m_length)
{
Reserve(length);
Memory::ConstructRange(
m_allocation.GetPointer() + m_length,
m_allocation.GetPointer() + length,
fillCharacter);
}
else if(length < m_length) // Shrink length

if(CharType* data = m_allocation.GetPointer())
{
Memory::DestructRange(
m_allocation.GetPointer() + length + NullCount,
m_allocation.GetPointer() + m_length + NullCount);
data[length] = NullChar;
}

m_allocation.GetPointer()[length] = NullChar;
m_length = length;
}

void Clear()
{
if(m_length > 0)
if(CharType* data = m_allocation.GetPointer())
{
Memory::DestructRange(
m_allocation.GetPointer() + NullCount,
m_allocation.GetPointer() + m_length + NullCount);

m_allocation.GetPointer()[0] = NullChar;
m_length = 0;
data[0] = NullChar;
}

m_length = 0;
}

CharType* GetData()
{
ASSERT(m_allocation.GetPointer());
return m_allocation.GetPointer();
if(CharType* data = m_allocation.GetPointer())
{
return data;
}

ASSERT_SLOW(EmptyString[0] == NullChar);
return const_cast<CharType*>(EmptyString);
}

const CharType* GetData() const
{
ASSERT(m_allocation.GetPointer());
return m_allocation.GetPointer();
if(const CharType* data = m_allocation.GetPointer())
{
return data;
}

ASSERT_SLOW(EmptyString[0] == NullChar);
return EmptyString;
}

u64 GetLength() const
Expand All @@ -177,19 +184,17 @@ class StringBase

bool IsEmpty() const
{
return GetLength() == 0;
return m_length == 0;
}

CharType* operator*()
{
ASSERT(m_allocation.GetPointer());
return m_allocation.GetPointer();
return GetData();
}

const CharType* operator*() const
{
ASSERT(m_allocation.GetPointer());
return m_allocation.GetPointer();
return GetData();
}

CharType& operator[](u64 index)
Expand All @@ -213,11 +218,13 @@ class StringBase

StringBase result;
result.Reserve(length);
if(CharType* resultData = result.m_allocation.GetPointer())
{
std::memcpy(resultData, GetData(), m_length * sizeof(CharType));
std::memcpy(resultData + m_length, other.GetData(), other.GetLength() * sizeof(CharType));
resultData[length] = NullChar;
}

std::memcpy(result.GetData(), GetData(), m_length * sizeof(CharType));
std::memcpy(result.GetData() + m_length, other.GetData(), other.GetLength() * sizeof(CharType));

result.GetData()[length] = NullChar;
result.m_length = length;
return result;
}
Expand All @@ -230,10 +237,13 @@ class StringBase

StringBase result;
result.Reserve(length);
std::memcpy(result.GetData(), GetData(), m_length * sizeof(CharType));
std::memcpy(result.GetData() + m_length, other, otherLength * sizeof(CharType));
if(CharType* resultData = result.m_allocation.GetPointer())
{
std::memcpy(resultData, GetData(), m_length * sizeof(CharType));
std::memcpy(resultData + m_length, other, otherLength * sizeof(CharType));
resultData[length] = NullChar;
}

result.GetData()[length] = NullChar;
result.m_length = length;
return result;
}
Expand All @@ -245,9 +255,12 @@ class StringBase
const u64 newLength = m_length + other.GetLength();

Reserve(newLength, false);
std::memcpy(GetData() + oldLength, other.GetData(), other.GetLength() * sizeof(CharType));
if(CharType* data = m_allocation.GetPointer())
{
std::memcpy(data + oldLength, other.GetData(), other.GetLength() * sizeof(CharType));
data[newLength] = NullChar;
}

GetData()[newLength] = NullChar;
m_length = newLength;
}

Expand All @@ -259,9 +272,12 @@ class StringBase
const u64 newLength = m_length + otherLength;

Reserve(newLength, false);
std::memcpy(GetData() + oldLength, other, otherLength * sizeof(CharType));
if(CharType* data = m_allocation.GetPointer())
{
std::memcpy(data + oldLength, other, otherLength * sizeof(CharType));
data[newLength] = NullChar;
}

GetData()[newLength] = NullChar;
m_length = newLength;
}

Expand All @@ -273,34 +289,34 @@ class StringBase

StringBase result;
result.Reserve(length);
std::snprintf(result.GetData(), result.GetCapacity() + NullCount,
format, std::forward<Arguments>(arguments)...);
if(CharType* resultData = result.m_allocation.GetPointer())
{
std::snprintf(resultData, result.GetCapacity() + NullCount,
format, std::forward<Arguments>(arguments)...);
resultData[length] = NullChar;
}

result.GetData()[length] = NullChar;
result.m_length = length;
return result;
}

private:
void ConstructFromText(const CharType* text, const u64 length)
{
ASSERT(text);
Reserve(length);
std::memcpy(m_allocation.GetPointer(), text, length * sizeof(CharType));
m_allocation.GetPointer()[length] = NullChar;
if(CharType* data = m_allocation.GetPointer())
{
std::memcpy(data, text, length * sizeof(CharType));
data[length] = NullChar;
}

m_length = length;
}

static u64 CalculateCapacity(const u64 newCapacity)
{
ASSERT(newCapacity != 0);

// Determine if we should use initial capacity recommended by allocator.
if(newCapacity <= Allocation::GetInitialCapacity())
{
return Allocation::GetInitialCapacity();
}

// Find the next power of two capacity (unless already power of two),
// but not smaller than some predefined minimum starting capacity.
return std::max(16ull, NextPow2(newCapacity - 1ull));
Expand All @@ -311,7 +327,7 @@ using DefaultStringAllocator = Memory::InlineAllocator<16>;
using String = StringBase<char, DefaultStringAllocator>;
static_assert(sizeof(String) == 32);

template<u64 InlineCapacity>
template<u64 InlineCapacity = 16>
using InlineString = StringBase<char, Memory::InlineAllocator<InlineCapacity>>;
static_assert(sizeof(InlineString<16>) == 32);

Expand Down
19 changes: 7 additions & 12 deletions Engine/Memory/Allocators/DefaultAllocator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ namespace Memory
m_capacity = capacity;
}

void Reallocate(const u64 capacity)
void Reallocate(const u64 newCapacity, const u64 usedCapacity)
{
ASSERT(m_pointer != nullptr);
ASSERT_SLOW(m_capacity != 0);
m_pointer = Memory::Reallocate<ElementType, DefaultAllocator>(m_pointer, capacity, m_capacity);
m_pointer = Memory::Reallocate<ElementType, DefaultAllocator>(m_pointer, newCapacity, m_capacity);
ASSERT_SLOW(m_pointer != nullptr);
m_capacity = capacity;
m_capacity = newCapacity;
}

void Deallocate()
Expand All @@ -82,22 +82,22 @@ namespace Memory
m_capacity = 0;
}

void Resize(const u64 capacity)
void Resize(const u64 newCapacity, const u64 usedCapacity)
{
if(m_capacity != 0)
{
if(capacity == 0)
if(newCapacity == 0)
{
Deallocate();
}
else
{
Reallocate(capacity);
Reallocate(newCapacity, usedCapacity);
}
}
else
{
Allocate(capacity);
Allocate(newCapacity);
}
}

Expand All @@ -110,11 +110,6 @@ namespace Memory
{
return m_capacity;
}

static u64 GetInitialCapacity()
{
return 0;
}
};
};
}
Loading

0 comments on commit d79f5b2

Please sign in to comment.