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:
parent
277d2e15d5
commit
21dfc91954
4
BUILD.gn
4
BUILD.gn
|
@ -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",
|
||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -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_
|
|
@ -21,7 +21,7 @@ namespace dawn_native {
|
|||
|
||||
// Wrapper for a resource backed by a heap.
|
||||
class ResourceHeapBase {
|
||||
protected:
|
||||
public:
|
||||
ResourceHeapBase() = default;
|
||||
virtual ~ResourceHeapBase() = default;
|
||||
};
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue