You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
278 lines
11 KiB
C++
278 lines
11 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include "RHITestFixture.h"
|
|
#include <Atom/RHI/PoolAllocator.h>
|
|
#include <Atom/RHI/FreeListAllocator.h>
|
|
#include <AzCore/Math/Random.h>
|
|
#include <AzCore/std/time.h>
|
|
#include <AzCore/UnitTest/UnitTest.h>
|
|
|
|
#define PRINTF(...) do { UnitTest::ColoredPrintf(UnitTest::COLOR_GREEN, "[ ] "); UnitTest::ColoredPrintf(UnitTest::COLOR_YELLOW, __VA_ARGS__); } while(0)
|
|
|
|
namespace UnitTest
|
|
{
|
|
using namespace AZ;
|
|
|
|
class AllocatorTest
|
|
: public RHITestFixture
|
|
{
|
|
public:
|
|
AllocatorTest()
|
|
: RHITestFixture()
|
|
{}
|
|
|
|
static size_t GetBytesToBlocks(size_t bytes, size_t alignment)
|
|
{
|
|
return size_t((bytes + alignment - 1) / alignment);
|
|
}
|
|
|
|
struct TestDescriptor
|
|
{
|
|
bool m_useVisualizer = false;
|
|
RHI::Allocator* m_allocator = nullptr;
|
|
size_t m_byteCountMax = 0;
|
|
size_t m_byteAlignmentBase = 256;
|
|
size_t m_gcLatency = 3;
|
|
size_t m_iterations = 10000;
|
|
size_t m_allocationSizeMin = 1;
|
|
size_t m_allocationSizeMax = 8 * 1024;
|
|
AZ::u64 m_addressBase = 0;
|
|
};
|
|
|
|
void run(const TestDescriptor& descriptor)
|
|
{
|
|
struct Block
|
|
{
|
|
Block() : m_used{}, m_gcIteration{} {}
|
|
|
|
AZ::u16 m_used;
|
|
AZ::u16 m_gcIteration;
|
|
};
|
|
|
|
struct Allocation
|
|
{
|
|
RHI::VirtualAddress address;
|
|
size_t size;
|
|
};
|
|
|
|
const size_t BlockCount = descriptor.m_byteCountMax / descriptor.m_byteAlignmentBase;
|
|
AZStd::vector<Block> m_usedBlocks(BlockCount);
|
|
AZStd::vector<Allocation> currentAllocations;
|
|
AZStd::vector<Allocation> retiredAllocationsCurrent;
|
|
AZStd::vector<Allocation> retiredAllocationsPrevious;
|
|
|
|
const size_t AllocationSizeRange = descriptor.m_allocationSizeMax - descriptor.m_allocationSizeMin;
|
|
|
|
AZStd::string outputString;
|
|
outputString.resize(BlockCount);
|
|
|
|
AZ::SimpleLcgRandom random(AZStd::GetTimeNowMicroSecond());
|
|
|
|
/**
|
|
* Does a bunch of random add / remove iterations, tracking garbage collection
|
|
* and block usage. It will assert if the allocator attempts to stomp on another
|
|
* allocation that is marked used.
|
|
*/
|
|
|
|
for (size_t iteration = 0; iteration < descriptor.m_iterations; ++iteration)
|
|
{
|
|
bool doPrint = false;
|
|
|
|
// 51% chance of doing an add. Biased towards adds so we fill up the allocator.
|
|
if ((random.GetRandom() % 100) <= 51)
|
|
{
|
|
const size_t AllocationSize = (AllocationSizeRange ? (random.GetRandom() % AllocationSizeRange) : 0) + descriptor.m_allocationSizeMin;
|
|
const size_t AllocationBlockCount = GetBytesToBlocks(AllocationSize, descriptor.m_byteAlignmentBase);
|
|
|
|
RHI::VirtualAddress address = descriptor.m_allocator->Allocate(AllocationSize, descriptor.m_byteAlignmentBase);
|
|
|
|
// Allocator has space. Record the allocation.
|
|
if (address.IsValid())
|
|
{
|
|
ASSERT_TRUE(address.m_ptr % descriptor.m_byteAlignmentBase == 0);
|
|
|
|
size_t addressOffset = address.m_ptr - descriptor.m_addressBase;
|
|
|
|
for (size_t currentBlock = 0; currentBlock < AllocationBlockCount; ++currentBlock)
|
|
{
|
|
size_t blockIndex = GetBytesToBlocks(addressOffset, descriptor.m_byteAlignmentBase) + currentBlock;
|
|
|
|
ASSERT_TRUE(m_usedBlocks[blockIndex].m_used == 0);
|
|
m_usedBlocks[blockIndex].m_used = 1;
|
|
}
|
|
|
|
currentAllocations.push_back(Allocation{ address, AllocationSize });
|
|
doPrint = true;
|
|
}
|
|
else
|
|
{
|
|
ASSERT_TRUE(currentAllocations.size() || retiredAllocationsCurrent.size());
|
|
}
|
|
}
|
|
else if (currentAllocations.size())
|
|
{
|
|
// pick a random allocation to remove
|
|
size_t allocationIndex = random.GetRandom() % currentAllocations.size();
|
|
|
|
Allocation allocation = currentAllocations[allocationIndex];
|
|
currentAllocations.erase(currentAllocations.begin() + allocationIndex);
|
|
|
|
descriptor.m_allocator->DeAllocate(allocation.address);
|
|
|
|
retiredAllocationsCurrent.push_back(allocation);
|
|
doPrint = true;
|
|
}
|
|
|
|
if ((random.GetRandom() % 4) == 0)
|
|
{
|
|
descriptor.m_allocator->GarbageCollect();
|
|
doPrint = true;
|
|
|
|
retiredAllocationsPrevious.clear();
|
|
for (Allocation retiredAllocation : retiredAllocationsCurrent)
|
|
{
|
|
size_t addressOffset = retiredAllocation.address.m_ptr - descriptor.m_addressBase;
|
|
size_t blockIndex = addressOffset / descriptor.m_byteAlignmentBase;
|
|
|
|
const size_t AllocationBlockCount = GetBytesToBlocks(retiredAllocation.size, descriptor.m_byteAlignmentBase);
|
|
|
|
// Just use the root block as the bookmarked item.
|
|
ASSERT_TRUE(m_usedBlocks[blockIndex].m_used == 1);
|
|
m_usedBlocks[blockIndex].m_gcIteration++;
|
|
|
|
if (m_usedBlocks[blockIndex].m_gcIteration > descriptor.m_gcLatency)
|
|
{
|
|
for (size_t currentBlock = 0; currentBlock < AllocationBlockCount; ++currentBlock)
|
|
{
|
|
size_t subBlockIndex = GetBytesToBlocks(addressOffset, descriptor.m_byteAlignmentBase) + currentBlock;
|
|
|
|
m_usedBlocks[subBlockIndex].m_used = 0;
|
|
m_usedBlocks[subBlockIndex].m_gcIteration = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
retiredAllocationsPrevious.push_back(retiredAllocation);
|
|
}
|
|
}
|
|
|
|
retiredAllocationsCurrent.swap(retiredAllocationsPrevious);
|
|
|
|
if (descriptor.m_useVisualizer)
|
|
{
|
|
PRINTF("GC...\n");
|
|
}
|
|
}
|
|
|
|
ASSERT_TRUE((retiredAllocationsCurrent.size() + currentAllocations.size()) == descriptor.m_allocator->GetAllocationCount());
|
|
|
|
if (doPrint)
|
|
{
|
|
for (size_t i = 0; i < BlockCount; ++i)
|
|
{
|
|
outputString[i] = m_usedBlocks[i].m_used ? 'x' : '-';
|
|
}
|
|
|
|
if (descriptor.m_useVisualizer)
|
|
{
|
|
PRINTF("%s\n", outputString.c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
TEST_F(AllocatorTest, PoolAllocator)
|
|
{
|
|
RHI::PoolAllocator::Descriptor descriptor;
|
|
descriptor.m_elementSize = 128;
|
|
descriptor.m_capacityInBytes = 128 * descriptor.m_elementSize;
|
|
descriptor.m_garbageCollectLatency = 2;
|
|
descriptor.m_addressBase = RHI::VirtualAddress::CreateFromPointer((void*)0xffffffffeeee0100);
|
|
|
|
RHI::PoolAllocator allocator;
|
|
allocator.Init(descriptor);
|
|
|
|
AllocatorTest::TestDescriptor testDescriptor;
|
|
testDescriptor.m_byteCountMax = descriptor.m_capacityInBytes;
|
|
testDescriptor.m_byteAlignmentBase = 128;
|
|
testDescriptor.m_gcLatency = descriptor.m_garbageCollectLatency;
|
|
testDescriptor.m_allocator = &allocator;
|
|
testDescriptor.m_useVisualizer = false;
|
|
testDescriptor.m_iterations = 10000;
|
|
testDescriptor.m_allocationSizeMin = descriptor.m_elementSize;
|
|
testDescriptor.m_allocationSizeMax = descriptor.m_elementSize;
|
|
testDescriptor.m_addressBase = descriptor.m_addressBase.m_ptr;
|
|
|
|
run(testDescriptor);
|
|
}
|
|
|
|
TEST_F(AllocatorTest, FirstFitAllocator)
|
|
{
|
|
RHI::FreeListAllocator::Descriptor descriptor;
|
|
descriptor.m_capacityInBytes = 64 * 1024;
|
|
descriptor.m_alignmentInBytes = 256;
|
|
descriptor.m_garbageCollectLatency = 2;
|
|
descriptor.m_addressBase = RHI::VirtualAddress::CreateFromPointer((void*)0xffffffffeeee0100);
|
|
descriptor.m_policy = RHI::FreeListAllocatorPolicy::FirstFit;
|
|
|
|
RHI::FreeListAllocator allocator;
|
|
allocator.Init(descriptor);
|
|
|
|
AllocatorTest::TestDescriptor testDescriptor;
|
|
testDescriptor.m_byteCountMax = descriptor.m_capacityInBytes;
|
|
testDescriptor.m_byteAlignmentBase = descriptor.m_alignmentInBytes;
|
|
testDescriptor.m_gcLatency = descriptor.m_garbageCollectLatency;
|
|
testDescriptor.m_allocator = &allocator;
|
|
testDescriptor.m_useVisualizer = false;
|
|
testDescriptor.m_iterations = 10000;
|
|
testDescriptor.m_addressBase = descriptor.m_addressBase.m_ptr;
|
|
run(testDescriptor);
|
|
|
|
descriptor.m_garbageCollectLatency = 0;
|
|
descriptor.m_addressBase = RHI::VirtualAddress::CreateFromOffset(1024);
|
|
allocator.Init(descriptor);
|
|
|
|
testDescriptor.m_gcLatency = descriptor.m_garbageCollectLatency;
|
|
testDescriptor.m_addressBase = descriptor.m_addressBase.m_ptr;
|
|
run(testDescriptor);
|
|
}
|
|
|
|
TEST_F(AllocatorTest, BestFitAllocator)
|
|
{
|
|
RHI::FreeListAllocator::Descriptor descriptor;
|
|
descriptor.m_capacityInBytes = 64 * 1024;
|
|
descriptor.m_alignmentInBytes = 256;
|
|
descriptor.m_garbageCollectLatency = 2;
|
|
descriptor.m_addressBase = RHI::VirtualAddress::CreateFromPointer((void*)0xffffffffeeee0100);
|
|
descriptor.m_policy = RHI::FreeListAllocatorPolicy::BestFit;
|
|
|
|
RHI::FreeListAllocator allocator;
|
|
allocator.Init(descriptor);
|
|
|
|
AllocatorTest::TestDescriptor testDescriptor;
|
|
testDescriptor.m_byteCountMax = descriptor.m_capacityInBytes;
|
|
testDescriptor.m_byteAlignmentBase = descriptor.m_alignmentInBytes;
|
|
testDescriptor.m_gcLatency = descriptor.m_garbageCollectLatency;
|
|
testDescriptor.m_allocator = &allocator;
|
|
testDescriptor.m_useVisualizer = false;
|
|
testDescriptor.m_iterations = 10000;
|
|
testDescriptor.m_addressBase = descriptor.m_addressBase.m_ptr;
|
|
run(testDescriptor);
|
|
|
|
descriptor.m_garbageCollectLatency = 0;
|
|
descriptor.m_addressBase = RHI::VirtualAddress::CreateFromOffset(1024);
|
|
allocator.Init(descriptor);
|
|
|
|
testDescriptor.m_gcLatency = descriptor.m_garbageCollectLatency;
|
|
testDescriptor.m_addressBase = descriptor.m_addressBase.m_ptr;
|
|
run(testDescriptor);
|
|
}
|
|
}
|