dawn-cmake/src/tests/unittests/BuddyMemoryAllocatorTests.cpp
Corentin Wallez ca35435716 Preliminary changes to Vulkan memory suballocation
This mostly makes the MemoryAllocator not owned by the
BuddyResourceAllocator so that we don't need an extra class for the
dependency injection in the Vulkan backend. (the container for the
BuddyMemoryAllocator can be it's MemoryAllocator at the same time).

Also renames methods of MemoryAllocator to be more explicit.

Also renames the constructor parameter of BuddyMemoryAllocator to be
(subjectively) closer to what the represent.

BUG=dawn:27

Change-Id: I37355ad5b3cded143956f0adc4742fa1b717e9bc
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/12661
Reviewed-by: Bryan Bernhart <bryan.bernhart@intel.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
2019-10-24 21:28:16 +00:00

348 lines
15 KiB
C++

// 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"
#include "dawn_native/ResourceHeapAllocator.h"
using namespace dawn_native;
class DummyResourceHeapAllocator : public ResourceHeapAllocator {
public:
ResultOrError<std::unique_ptr<ResourceHeapBase>> AllocateResourceHeap(uint64_t size) override {
return std::make_unique<ResourceHeapBase>();
}
void DeallocateResourceHeap(std::unique_ptr<ResourceHeapBase> allocation) override {
}
};
class DummyBuddyResourceAllocator {
public:
DummyBuddyResourceAllocator(uint64_t maxBlockSize, uint64_t memorySize)
: mAllocator(maxBlockSize, memorySize, &mHeapAllocator) {
}
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:
DummyResourceHeapAllocator mHeapAllocator;
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);
}