Resource Management 7: Device memory sub-allocation using buddy allocator.

Uses a large buddy system to allocate binary sized heaps on-demand.

BUG=dawn:27

Change-Id: I72e425c23e601da6ee53827423bef7ff13be049c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/10880
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Bryan Bernhart <bryan.bernhart@intel.com>
This commit is contained in:
Bryan Bernhart 2019-10-03 18:17:31 +00:00 committed by Commit Bot service account
parent 277d2e15d5
commit 21dfc91954
12 changed files with 597 additions and 32 deletions

View File

@ -143,6 +143,8 @@ source_set("libdawn_native_sources") {
"src/dawn_native/BindGroupTracker.h",
"src/dawn_native/BuddyAllocator.cpp",
"src/dawn_native/BuddyAllocator.h",
"src/dawn_native/BuddyMemoryAllocator.cpp",
"src/dawn_native/BuddyMemoryAllocator.h",
"src/dawn_native/Buffer.cpp",
"src/dawn_native/Buffer.h",
"src/dawn_native/CommandAllocator.cpp",
@ -186,6 +188,7 @@ source_set("libdawn_native_sources") {
"src/dawn_native/Forward.h",
"src/dawn_native/Instance.cpp",
"src/dawn_native/Instance.h",
"src/dawn_native/MemoryAllocator.h",
"src/dawn_native/ObjectBase.cpp",
"src/dawn_native/ObjectBase.h",
"src/dawn_native/PassResourceUsage.h",
@ -762,6 +765,7 @@ test("dawn_unittests") {
sources += [
"src/tests/unittests/BitSetIteratorTests.cpp",
"src/tests/unittests/BuddyAllocatorTests.cpp",
"src/tests/unittests/BuddyMemoryAllocatorTests.cpp",
"src/tests/unittests/CommandAllocatorTests.cpp",
"src/tests/unittests/EnumClassBitmasksTests.cpp",
"src/tests/unittests/ErrorTests.cpp",

View File

@ -0,0 +1,104 @@
// Copyright 2019 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "dawn_native/BuddyMemoryAllocator.h"
#include "common/Math.h"
namespace dawn_native {
BuddyMemoryAllocator::BuddyMemoryAllocator(uint64_t maxBlockSize,
uint64_t memorySize,
std::unique_ptr<MemoryAllocator> client)
: mMemorySize(memorySize), mBuddyBlockAllocator(maxBlockSize), mClient(std::move(client)) {
ASSERT(memorySize <= maxBlockSize);
ASSERT(IsPowerOfTwo(mMemorySize));
ASSERT(maxBlockSize % mMemorySize == 0);
mTrackedSubAllocations.resize(maxBlockSize / mMemorySize);
}
uint64_t BuddyMemoryAllocator::GetMemoryIndex(uint64_t offset) const {
ASSERT(offset != BuddyAllocator::kInvalidOffset);
return offset / mMemorySize;
}
ResultOrError<ResourceMemoryAllocation> BuddyMemoryAllocator::Allocate(uint64_t allocationSize,
uint64_t alignment,
int memoryFlags) {
ResourceMemoryAllocation invalidAllocation = ResourceMemoryAllocation{};
// Allocation cannot exceed the memory size.
if (allocationSize == 0 || allocationSize > mMemorySize) {
return invalidAllocation;
}
// Attempt to sub-allocate a block of the requested size.
const uint64_t blockOffset = mBuddyBlockAllocator.Allocate(allocationSize, alignment);
if (blockOffset == BuddyAllocator::kInvalidOffset) {
return invalidAllocation;
}
const uint64_t memoryIndex = GetMemoryIndex(blockOffset);
if (mTrackedSubAllocations[memoryIndex].refcount == 0) {
// Transfer ownership to this allocator
std::unique_ptr<ResourceHeapBase> memory;
DAWN_TRY_ASSIGN(memory, mClient->Allocate(mMemorySize, memoryFlags));
mTrackedSubAllocations[memoryIndex] = {/*refcount*/ 0, std::move(memory)};
}
mTrackedSubAllocations[memoryIndex].refcount++;
AllocationInfo info;
info.mBlockOffset = blockOffset;
info.mMethod = AllocationMethod::kSubAllocated;
// Allocation offset is always local to the memory.
const uint64_t memoryOffset = blockOffset % mMemorySize;
return ResourceMemoryAllocation{
info, memoryOffset, mTrackedSubAllocations[memoryIndex].mMemoryAllocation.get()};
} // namespace dawn_native
void BuddyMemoryAllocator::Deallocate(const ResourceMemoryAllocation& allocation) {
const AllocationInfo info = allocation.GetInfo();
ASSERT(info.mMethod == AllocationMethod::kSubAllocated);
const uint64_t memoryIndex = GetMemoryIndex(info.mBlockOffset);
ASSERT(mTrackedSubAllocations[memoryIndex].refcount > 0);
mTrackedSubAllocations[memoryIndex].refcount--;
if (mTrackedSubAllocations[memoryIndex].refcount == 0) {
mClient->Deallocate(std::move(mTrackedSubAllocations[memoryIndex].mMemoryAllocation));
}
mBuddyBlockAllocator.Deallocate(info.mBlockOffset);
}
uint64_t BuddyMemoryAllocator::GetMemorySize() const {
return mMemorySize;
}
uint64_t BuddyMemoryAllocator::ComputeTotalNumOfHeapsForTesting() const {
uint64_t count = 0;
for (const TrackedSubAllocations& allocation : mTrackedSubAllocations) {
if (allocation.refcount > 0) {
count++;
}
}
return count;
}
} // namespace dawn_native

View File

@ -0,0 +1,73 @@
// Copyright 2019 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef DAWNNATIVE_BUDDYMEMORYALLOCATOR_H_
#define DAWNNATIVE_BUDDYMEMORYALLOCATOR_H_
#include <vector>
#include "dawn_native/BuddyAllocator.h"
#include "dawn_native/MemoryAllocator.h"
#include "dawn_native/ResourceMemoryAllocation.h"
namespace dawn_native {
// BuddyMemoryAllocator uses the buddy allocator to sub-allocate blocks of device
// memory created by MemoryAllocator clients. It creates a very large buddy system
// where backing device memory blocks equal a specified level in the system.
//
// Upon sub-allocating, the offset gets mapped to device memory by computing the corresponding
// memory index and should the memory not exist, it is created. If two sub-allocations share the
// same memory index, the memory refcount is incremented to ensure de-allocating one doesn't
// release the other prematurely.
//
// The device will only create up to Log2(kMaxResourceSize) allocators and can prefer speed
// over memory footprint by selecting an allocator with a higher memory threshold which results
// in pre-allocating more memory.
//
// The resource allocation is guaranteed by the device to have compatible memory flags.
class BuddyMemoryAllocator {
public:
BuddyMemoryAllocator(uint64_t maxBlockSize,
uint64_t memorySize,
std::unique_ptr<MemoryAllocator> client);
~BuddyMemoryAllocator() = default;
ResultOrError<ResourceMemoryAllocation> Allocate(uint64_t allocationSize,
uint64_t alignment,
int memoryFlags = 0);
void Deallocate(const ResourceMemoryAllocation& allocation);
uint64_t GetMemorySize() const;
// For testing purposes.
uint64_t ComputeTotalNumOfHeapsForTesting() const;
private:
uint64_t GetMemoryIndex(uint64_t offset) const;
uint64_t mMemorySize = 0;
BuddyAllocator mBuddyBlockAllocator;
std::unique_ptr<MemoryAllocator> mClient;
struct TrackedSubAllocations {
size_t refcount = 0;
std::unique_ptr<ResourceHeapBase> mMemoryAllocation;
};
std::vector<TrackedSubAllocations> mTrackedSubAllocations;
};
} // namespace dawn_native
#endif // DAWNNATIVE_BUDDYMEMORYALLOCATOR_H_

View File

@ -0,0 +1,33 @@
// Copyright 2019 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef DAWNNATIVE_MEMORYALLOCATOR_H_
#define DAWNNATIVE_MEMORYALLOCATOR_H_
#include "dawn_native/Error.h"
#include "dawn_native/ResourceHeap.h"
namespace dawn_native {
// Interface for backend allocators that create physical device memory.
class MemoryAllocator {
public:
virtual ~MemoryAllocator() = default;
virtual ResultOrError<std::unique_ptr<ResourceHeapBase>> Allocate(uint64_t size,
int memoryFlags) = 0;
virtual void Deallocate(std::unique_ptr<ResourceHeapBase> allocation) = 0;
};
} // namespace dawn_native
#endif // DAWNNATIVE_MEMORYALLOCATOR_H_

View File

@ -21,7 +21,7 @@ namespace dawn_native {
// Wrapper for a resource backed by a heap.
class ResourceHeapBase {
protected:
public:
ResourceHeapBase() = default;
virtual ~ResourceHeapBase() = default;
};

View File

@ -15,41 +15,31 @@
#include "dawn_native/ResourceMemoryAllocation.h"
#include "common/Assert.h"
#include <limits>
namespace dawn_native {
static constexpr uint64_t kInvalidOffset = std::numeric_limits<uint64_t>::max();
ResourceMemoryAllocation::ResourceMemoryAllocation()
: mMethod(AllocationMethod::kInvalid),
mOffset(0),
mResourceHeap(nullptr),
mMappedPointer(nullptr) {
: mOffset(0), mResourceHeap(nullptr), mMappedPointer(nullptr) {
}
ResourceMemoryAllocation::ResourceMemoryAllocation(uint64_t offset,
ResourceMemoryAllocation::ResourceMemoryAllocation(const AllocationInfo& info,
uint64_t offset,
ResourceHeapBase* resourceHeap,
AllocationMethod method,
uint8_t* mappedPointer)
: mMethod(method),
mOffset(offset),
mResourceHeap(resourceHeap),
mMappedPointer(mappedPointer) {
: mInfo(info), mOffset(offset), mResourceHeap(resourceHeap), mMappedPointer(mappedPointer) {
}
ResourceHeapBase* ResourceMemoryAllocation::GetResourceHeap() const {
ASSERT(mMethod != AllocationMethod::kInvalid);
ASSERT(mInfo.mMethod != AllocationMethod::kInvalid);
return mResourceHeap;
}
uint64_t ResourceMemoryAllocation::GetOffset() const {
ASSERT(mMethod != AllocationMethod::kInvalid);
ASSERT(mInfo.mMethod != AllocationMethod::kInvalid);
return mOffset;
}
AllocationMethod ResourceMemoryAllocation::GetAllocationMethod() const {
return mMethod;
AllocationInfo ResourceMemoryAllocation::GetInfo() const {
return mInfo;
}
uint8_t* ResourceMemoryAllocation::GetMappedPointer() const {
@ -58,7 +48,6 @@ namespace dawn_native {
void ResourceMemoryAllocation::Invalidate() {
mResourceHeap = nullptr;
mMethod = AllocationMethod::kInvalid;
mOffset = kInvalidOffset;
mInfo = {};
}
} // namespace dawn_native

View File

@ -35,25 +35,36 @@ namespace dawn_native {
kInvalid
};
// Metadata that describes how the allocation was allocated.
struct AllocationInfo {
// AllocationInfo contains a separate offset to not confuse block vs memory offsets.
// The block offset is within the entire allocator memory range and only required by the
// buddy sub-allocator to get the corresponding memory. Unlike the block offset, the
// allocation offset is always local to the memory.
uint64_t mBlockOffset = 0;
AllocationMethod mMethod = AllocationMethod::kInvalid;
};
// Handle into a resource heap pool.
class ResourceMemoryAllocation {
public:
ResourceMemoryAllocation();
ResourceMemoryAllocation(uint64_t offset,
ResourceMemoryAllocation(const AllocationInfo& info,
uint64_t offset,
ResourceHeapBase* resourceHeap,
AllocationMethod method,
uint8_t* mappedPointer = nullptr);
~ResourceMemoryAllocation() = default;
ResourceHeapBase* GetResourceHeap() const;
uint64_t GetOffset() const;
AllocationMethod GetAllocationMethod() const;
uint8_t* GetMappedPointer() const;
AllocationInfo GetInfo() const;
void Invalidate();
private:
AllocationMethod mMethod;
AllocationInfo mInfo;
uint64_t mOffset;
ResourceHeapBase* mResourceHeap;
uint8_t* mMappedPointer;

View File

@ -40,9 +40,12 @@ namespace dawn_native { namespace d3d12 {
return DAWN_OUT_OF_MEMORY_ERROR("Unable to allocate resource");
}
return ResourceMemoryAllocation(
/*offset*/ 0, new ResourceHeap(std::move(committedResource)),
AllocationMethod::kDirect);
AllocationInfo info;
info.mMethod = AllocationMethod::kDirect;
return ResourceMemoryAllocation{info,
/*offset*/ 0,
new ResourceHeap(std::move(committedResource))};
}
void CommittedResourceAllocator::Deallocate(ResourceMemoryAllocation& allocation) {

View File

@ -51,7 +51,7 @@ namespace dawn_native { namespace d3d12 {
}
void ResourceAllocatorManager::DeallocateMemory(ResourceMemoryAllocation& allocation) {
if (allocation.GetAllocationMethod() == AllocationMethod::kInvalid) {
if (allocation.GetInfo().mMethod == AllocationMethod::kInvalid) {
return;
}
CommittedResourceAllocator* allocator = nullptr;

View File

@ -702,7 +702,7 @@ namespace dawn_native { namespace vulkan {
}
void Device::DeallocateMemory(ResourceMemoryAllocation& allocation) {
if (allocation.GetAllocationMethod() == AllocationMethod::kInvalid) {
if (allocation.GetInfo().mMethod == AllocationMethod::kInvalid) {
return;
}
mResourceAllocator->Deallocate(allocation);

View File

@ -105,8 +105,10 @@ namespace dawn_native { namespace vulkan {
"vkMapMemory"));
}
return ResourceMemoryAllocation(/*offset*/ 0, new ResourceMemory(allocatedMemory),
AllocationMethod::kDirect,
AllocationInfo info;
info.mMethod = AllocationMethod::kDirect;
return ResourceMemoryAllocation(info, /*offset*/ 0, new ResourceMemory(allocatedMemory),
static_cast<uint8_t*>(mappedPointer));
}

View File

@ -0,0 +1,346 @@
// Copyright 2019 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include "dawn_native/BuddyMemoryAllocator.h"
using namespace dawn_native;
class DummyMemoryAllocator : public MemoryAllocator {
public:
ResultOrError<std::unique_ptr<ResourceHeapBase>> Allocate(uint64_t size,
int memoryFlags = 0) override {
return std::make_unique<ResourceHeapBase>();
}
void Deallocate(std::unique_ptr<ResourceHeapBase> allocation) override {
}
};
class DummyBuddyResourceAllocator {
public:
DummyBuddyResourceAllocator(uint64_t maxBlockSize, uint64_t memorySize)
: mAllocator(maxBlockSize, memorySize, std::make_unique<DummyMemoryAllocator>()) {
}
ResourceMemoryAllocation Allocate(uint64_t allocationSize, uint64_t alignment = 1) {
ResultOrError<ResourceMemoryAllocation> result =
mAllocator.Allocate(allocationSize, alignment);
return (result.IsSuccess()) ? result.AcquireSuccess() : ResourceMemoryAllocation{};
}
void Deallocate(ResourceMemoryAllocation& allocation) {
mAllocator.Deallocate(allocation);
}
uint64_t ComputeTotalNumOfHeapsForTesting() const {
return mAllocator.ComputeTotalNumOfHeapsForTesting();
}
private:
BuddyMemoryAllocator mAllocator;
};
// Verify a single resource allocation in a single heap.
TEST(BuddyMemoryAllocatorTests, SingleHeap) {
// After one 128 byte resource allocation:
//
// max block size -> ---------------------------
// | A1/H0 | Hi - Heap at index i
// max heap size -> --------------------------- An - Resource allocation n
//
constexpr uint64_t heapSize = 128;
constexpr uint64_t maxBlockSize = heapSize;
DummyBuddyResourceAllocator allocator(maxBlockSize, heapSize);
// Cannot allocate greater than heap size.
ResourceMemoryAllocation invalidAllocation = allocator.Allocate(heapSize * 2);
ASSERT_EQ(invalidAllocation.GetInfo().mMethod, AllocationMethod::kInvalid);
// Allocate one 128 byte allocation (same size as heap).
ResourceMemoryAllocation allocation1 = allocator.Allocate(128);
ASSERT_EQ(allocation1.GetInfo().mBlockOffset, 0u);
ASSERT_EQ(allocation1.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 1u);
// Cannot allocate when allocator is full.
invalidAllocation = allocator.Allocate(128);
ASSERT_EQ(invalidAllocation.GetInfo().mMethod, AllocationMethod::kInvalid);
allocator.Deallocate(allocation1);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 0u);
}
// Verify that multiple allocation are created in separate heaps.
TEST(BuddyMemoryAllocatorTests, MultipleHeaps) {
// After two 128 byte resource allocations:
//
// max block size -> ---------------------------
// | | Hi - Heap at index i
// max heap size -> --------------------------- An - Resource allocation n
// | A1/H0 | A2/H1 |
// ---------------------------
//
constexpr uint64_t maxBlockSize = 256;
constexpr uint64_t heapSize = 128;
DummyBuddyResourceAllocator allocator(maxBlockSize, heapSize);
// Cannot allocate greater than heap size.
ResourceMemoryAllocation invalidAllocation = allocator.Allocate(heapSize * 2);
ASSERT_EQ(invalidAllocation.GetInfo().mMethod, AllocationMethod::kInvalid);
// Cannot allocate greater than max block size.
invalidAllocation = allocator.Allocate(maxBlockSize * 2);
ASSERT_EQ(invalidAllocation.GetInfo().mMethod, AllocationMethod::kInvalid);
// Allocate two 128 byte allocations.
ResourceMemoryAllocation allocation1 = allocator.Allocate(heapSize);
ASSERT_EQ(allocation1.GetInfo().mBlockOffset, 0u);
ASSERT_EQ(allocation1.GetInfo().mMethod, AllocationMethod::kSubAllocated);
// First allocation creates first heap.
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 1u);
ResourceMemoryAllocation allocation2 = allocator.Allocate(heapSize);
ASSERT_EQ(allocation2.GetInfo().mBlockOffset, heapSize);
ASSERT_EQ(allocation2.GetInfo().mMethod, AllocationMethod::kSubAllocated);
// Second allocation creates second heap.
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 2u);
// Deallocate both allocations
allocator.Deallocate(allocation1);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 1u); // Released H0
allocator.Deallocate(allocation2);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 0u); // Released H1
}
// Verify multiple sub-allocations can re-use heaps.
TEST(BuddyMemoryAllocatorTests, MultipleSplitHeaps) {
// After two 64 byte allocations with 128 byte heaps.
//
// max block size -> ---------------------------
// | | Hi - Heap at index i
// max heap size -> --------------------------- An - Resource allocation n
// | H0 | H1 |
// ---------------------------
// | A1 | A2 | A3 | |
// ---------------------------
//
constexpr uint64_t maxBlockSize = 256;
constexpr uint64_t heapSize = 128;
DummyBuddyResourceAllocator allocator(maxBlockSize, heapSize);
// Allocate two 64 byte sub-allocations.
ResourceMemoryAllocation allocation1 = allocator.Allocate(heapSize / 2);
ASSERT_EQ(allocation1.GetInfo().mBlockOffset, 0u);
ASSERT_EQ(allocation1.GetInfo().mMethod, AllocationMethod::kSubAllocated);
// First sub-allocation creates first heap.
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 1u);
ResourceMemoryAllocation allocation2 = allocator.Allocate(heapSize / 2);
ASSERT_EQ(allocation2.GetInfo().mBlockOffset, heapSize / 2);
ASSERT_EQ(allocation2.GetInfo().mMethod, AllocationMethod::kSubAllocated);
// Second allocation re-uses first heap.
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 1u);
ResourceMemoryAllocation allocation3 = allocator.Allocate(heapSize / 2);
ASSERT_EQ(allocation3.GetInfo().mBlockOffset, heapSize);
ASSERT_EQ(allocation3.GetInfo().mMethod, AllocationMethod::kSubAllocated);
// Third allocation creates second heap.
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 2u);
// Deallocate all allocations in reverse order.
allocator.Deallocate(allocation1);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(),
2u); // A2 pins H0.
allocator.Deallocate(allocation2);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 1u); // Released H0
allocator.Deallocate(allocation3);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 0u); // Released H1
}
// Verify resource sub-allocation of various sizes over multiple heaps.
TEST(BuddyMemoryAllocatorTests, MultiplSplitHeapsVariableSizes) {
// After three 64 byte allocations and two 128 byte allocations.
//
// max block size -> -------------------------------------------------------
// | |
// -------------------------------------------------------
// | | |
// max heap size -> -------------------------------------------------------
// | H0 | A3/H1 | H2 | A5/H3 |
// -------------------------------------------------------
// | A1 | A2 | | A4 | | |
// -------------------------------------------------------
//
constexpr uint64_t heapSize = 128;
constexpr uint64_t maxBlockSize = 512;
DummyBuddyResourceAllocator allocator(maxBlockSize, heapSize);
// Allocate two 64-byte allocations.
ResourceMemoryAllocation allocation1 = allocator.Allocate(64);
ASSERT_EQ(allocation1.GetInfo().mBlockOffset, 0u);
ASSERT_EQ(allocation1.GetOffset(), 0u);
ASSERT_EQ(allocation1.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ResourceMemoryAllocation allocation2 = allocator.Allocate(64);
ASSERT_EQ(allocation2.GetInfo().mBlockOffset, 64u);
ASSERT_EQ(allocation2.GetOffset(), 64u);
ASSERT_EQ(allocation2.GetInfo().mMethod, AllocationMethod::kSubAllocated);
// A1 and A2 share H0
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 1u);
ResourceMemoryAllocation allocation3 = allocator.Allocate(128);
ASSERT_EQ(allocation3.GetInfo().mBlockOffset, 128u);
ASSERT_EQ(allocation3.GetOffset(), 0u);
ASSERT_EQ(allocation3.GetInfo().mMethod, AllocationMethod::kSubAllocated);
// A3 creates and fully occupies a new heap.
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 2u);
ResourceMemoryAllocation allocation4 = allocator.Allocate(64);
ASSERT_EQ(allocation4.GetInfo().mBlockOffset, 256u);
ASSERT_EQ(allocation4.GetOffset(), 0u);
ASSERT_EQ(allocation4.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 3u);
// R5 size forms 64 byte hole after R4.
ResourceMemoryAllocation allocation5 = allocator.Allocate(128);
ASSERT_EQ(allocation5.GetInfo().mBlockOffset, 384u);
ASSERT_EQ(allocation5.GetOffset(), 0u);
ASSERT_EQ(allocation5.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 4u);
// Deallocate allocations in staggered order.
allocator.Deallocate(allocation1);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 4u); // A2 pins H0
allocator.Deallocate(allocation5);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 3u); // Released H3
allocator.Deallocate(allocation2);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 2u); // Released H0
allocator.Deallocate(allocation4);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 1u); // Released H2
allocator.Deallocate(allocation3);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 0u); // Released H1
}
// Verify resource sub-allocation of same sizes with various alignments.
TEST(BuddyMemoryAllocatorTests, SameSizeVariousAlignment) {
// After three 64 byte and one 128 byte resource allocations.
//
// max block size -> -------------------------------------------------------
// | |
// -------------------------------------------------------
// | | |
// max heap size -> -------------------------------------------------------
// | H0 | H1 | H2 | |
// -------------------------------------------------------
// | A1 | | A2 | | A3 | A4 | |
// -------------------------------------------------------
//
constexpr uint64_t heapSize = 128;
constexpr uint64_t maxBlockSize = 512;
DummyBuddyResourceAllocator allocator(maxBlockSize, heapSize);
ResourceMemoryAllocation allocation1 = allocator.Allocate(64, 128);
ASSERT_EQ(allocation1.GetInfo().mBlockOffset, 0u);
ASSERT_EQ(allocation1.GetOffset(), 0u);
ASSERT_EQ(allocation1.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 1u);
ResourceMemoryAllocation allocation2 = allocator.Allocate(64, 128);
ASSERT_EQ(allocation2.GetInfo().mBlockOffset, 128u);
ASSERT_EQ(allocation2.GetOffset(), 0u);
ASSERT_EQ(allocation2.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 2u);
ResourceMemoryAllocation allocation3 = allocator.Allocate(64, 128);
ASSERT_EQ(allocation3.GetInfo().mBlockOffset, 256u);
ASSERT_EQ(allocation3.GetOffset(), 0u);
ASSERT_EQ(allocation3.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 3u);
ResourceMemoryAllocation allocation4 = allocator.Allocate(64, 64);
ASSERT_EQ(allocation4.GetInfo().mBlockOffset, 320u);
ASSERT_EQ(allocation4.GetOffset(), 64u);
ASSERT_EQ(allocation4.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 3u);
}
// Verify resource sub-allocation of various sizes with same alignments.
TEST(BuddyMemoryAllocatorTests, VariousSizeSameAlignment) {
// After two 64 byte and two 128 byte resource allocations:
//
// max block size -> -------------------------------------------------------
// | |
// -------------------------------------------------------
// | | |
// max heap size -> -------------------------------------------------------
// | H0 | A3/H1 | A4/H2 | |
// -------------------------------------------------------
// | A1 | A2 | | | |
// -------------------------------------------------------
//
constexpr uint64_t heapSize = 128;
constexpr uint64_t maxBlockSize = 512;
DummyBuddyResourceAllocator allocator(maxBlockSize, heapSize);
constexpr uint64_t alignment = 64;
ResourceMemoryAllocation allocation1 = allocator.Allocate(64, alignment);
ASSERT_EQ(allocation1.GetInfo().mBlockOffset, 0u);
ASSERT_EQ(allocation1.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 1u);
ResourceMemoryAllocation allocation2 = allocator.Allocate(64, alignment);
ASSERT_EQ(allocation2.GetInfo().mBlockOffset, 64u);
ASSERT_EQ(allocation2.GetOffset(), 64u);
ASSERT_EQ(allocation2.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 1u); // Reuses H0
ResourceMemoryAllocation allocation3 = allocator.Allocate(128, alignment);
ASSERT_EQ(allocation3.GetInfo().mBlockOffset, 128u);
ASSERT_EQ(allocation3.GetOffset(), 0u);
ASSERT_EQ(allocation3.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 2u);
ResourceMemoryAllocation allocation4 = allocator.Allocate(128, alignment);
ASSERT_EQ(allocation4.GetInfo().mBlockOffset, 256u);
ASSERT_EQ(allocation4.GetOffset(), 0u);
ASSERT_EQ(allocation4.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 3u);
}