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.
o3de/Gems/Atom/RHI/Code/Tests/AllocatorTests.cpp

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);
}
}