mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-06-05 06:03:34 +00:00
Resource Management 3: Buddy allocator
Introduces the buddy system for fast power-of-two sub-allocation. BUG=dawn:27 Change-Id: I56836ae317ecc5a91d8341c843fc37d4f91fb5af Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/9260 Reviewed-by: Kai Ninomiya <kainino@chromium.org> Reviewed-by: Corentin Wallez <cwallez@chromium.org> Commit-Queue: Corentin Wallez <cwallez@chromium.org>
This commit is contained in:
parent
7b57c5bb77
commit
35ad5221fb
3
BUILD.gn
3
BUILD.gn
@ -112,6 +112,8 @@ source_set("libdawn_native_sources") {
|
||||
"src/dawn_native/BindGroup.h",
|
||||
"src/dawn_native/BindGroupLayout.cpp",
|
||||
"src/dawn_native/BindGroupLayout.h",
|
||||
"src/dawn_native/BuddyAllocator.cpp",
|
||||
"src/dawn_native/BuddyAllocator.h",
|
||||
"src/dawn_native/Buffer.cpp",
|
||||
"src/dawn_native/Buffer.h",
|
||||
"src/dawn_native/CommandAllocator.cpp",
|
||||
@ -598,6 +600,7 @@ test("dawn_unittests") {
|
||||
]
|
||||
sources += [
|
||||
"src/tests/unittests/BitSetIteratorTests.cpp",
|
||||
"src/tests/unittests/BuddyAllocatorTests.cpp",
|
||||
"src/tests/unittests/CommandAllocatorTests.cpp",
|
||||
"src/tests/unittests/EnumClassBitmasksTests.cpp",
|
||||
"src/tests/unittests/ErrorTests.cpp",
|
||||
|
267
src/dawn_native/BuddyAllocator.cpp
Normal file
267
src/dawn_native/BuddyAllocator.cpp
Normal file
@ -0,0 +1,267 @@
|
||||
// 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/BuddyAllocator.h"
|
||||
|
||||
#include "common/Assert.h"
|
||||
#include "common/Math.h"
|
||||
|
||||
namespace dawn_native {
|
||||
|
||||
BuddyAllocator::BuddyAllocator(uint64_t maxSize) : mMaxBlockSize(maxSize) {
|
||||
ASSERT(IsPowerOfTwo(maxSize));
|
||||
|
||||
mFreeLists.resize(Log2(mMaxBlockSize) + 1);
|
||||
|
||||
// Insert the level0 free block.
|
||||
mRoot = new BuddyBlock(maxSize, /*offset*/ 0);
|
||||
mFreeLists[0] = {mRoot};
|
||||
}
|
||||
|
||||
BuddyAllocator::~BuddyAllocator() {
|
||||
if (mRoot) {
|
||||
DeleteBlock(mRoot);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t BuddyAllocator::ComputeTotalNumOfFreeBlocksForTesting() const {
|
||||
return ComputeNumOfFreeBlocks(mRoot);
|
||||
}
|
||||
|
||||
uint64_t BuddyAllocator::ComputeNumOfFreeBlocks(BuddyBlock* block) const {
|
||||
if (block->mState == BlockState::Free) {
|
||||
return 1;
|
||||
} else if (block->mState == BlockState::Split) {
|
||||
return ComputeNumOfFreeBlocks(block->split.pLeft) +
|
||||
ComputeNumOfFreeBlocks(block->split.pLeft->pBuddy);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t BuddyAllocator::ComputeLevelFromBlockSize(uint64_t blockSize) const {
|
||||
// Every level in the buddy system can be indexed by order-n where n = log2(blockSize).
|
||||
// However, mFreeList zero-indexed by level.
|
||||
// For example, blockSize=4 is Level1 if MAX_BLOCK is 8.
|
||||
return Log2(mMaxBlockSize) - Log2(blockSize);
|
||||
}
|
||||
|
||||
uint64_t BuddyAllocator::GetNextFreeAlignedBlock(size_t allocationBlockLevel,
|
||||
uint64_t alignment) const {
|
||||
ASSERT(IsPowerOfTwo(alignment));
|
||||
// The current level is the level that corresponds to the allocation size. The free list may
|
||||
// not contain a block at that level until a larger one gets allocated (and splits).
|
||||
// Continue to go up the tree until such a larger block exists.
|
||||
//
|
||||
// Even if the block exists at the level, it cannot be used if it's offset is unaligned.
|
||||
// When the alignment is also a power-of-two, we simply use the next free block whose size
|
||||
// is greater than or equal to the alignment value.
|
||||
//
|
||||
// After one 8-byte allocation:
|
||||
//
|
||||
// Level --------------------------------
|
||||
// 0 32 | S |
|
||||
// --------------------------------
|
||||
// 1 16 | S | F2 | S - split
|
||||
// -------------------------------- F - free
|
||||
// 2 8 | Aa | F1 | | A - allocated
|
||||
// --------------------------------
|
||||
//
|
||||
// Allocate(size=8, alignment=8) will be satisfied by using F1.
|
||||
// Allocate(size=8, alignment=4) will be satified by using F1.
|
||||
// Allocate(size=8, alignment=16) will be satisified by using F2.
|
||||
//
|
||||
for (size_t currLevel = allocationBlockLevel; currLevel >= 0; currLevel--) {
|
||||
BuddyBlock* freeBlock = mFreeLists[currLevel].head;
|
||||
if (freeBlock && (freeBlock->mOffset % alignment == 0)) {
|
||||
return currLevel;
|
||||
}
|
||||
|
||||
if (currLevel == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return INVALID_OFFSET; // No free block exists at any level.
|
||||
}
|
||||
|
||||
// Inserts existing free block into the free-list.
|
||||
// Called by allocate upon splitting to insert a child block into a free-list.
|
||||
// Note: Always insert into the head of the free-list. As when a larger free block at a lower
|
||||
// level was split, there were no smaller free blocks at a higher level to allocate.
|
||||
void BuddyAllocator::InsertFreeBlock(BuddyBlock* block, size_t level) {
|
||||
ASSERT(block->mState == BlockState::Free);
|
||||
|
||||
// Inserted block is now the front (no prev).
|
||||
block->free.pPrev = nullptr;
|
||||
|
||||
// Old head is now the inserted block's next.
|
||||
block->free.pNext = mFreeLists[level].head;
|
||||
|
||||
// Block already in HEAD position (ex. right child was inserted first).
|
||||
if (mFreeLists[level].head != nullptr) {
|
||||
// Old head's previous is the inserted block.
|
||||
mFreeLists[level].head->free.pPrev = block;
|
||||
}
|
||||
|
||||
mFreeLists[level].head = block;
|
||||
}
|
||||
|
||||
void BuddyAllocator::RemoveFreeBlock(BuddyBlock* block, size_t level) {
|
||||
ASSERT(block->mState == BlockState::Free);
|
||||
|
||||
if (mFreeLists[level].head == block) {
|
||||
// Block is in HEAD position.
|
||||
mFreeLists[level].head = mFreeLists[level].head->free.pNext;
|
||||
} else {
|
||||
// Block is after HEAD position.
|
||||
BuddyBlock* pPrev = block->free.pPrev;
|
||||
BuddyBlock* pNext = block->free.pNext;
|
||||
|
||||
ASSERT(pPrev != nullptr);
|
||||
ASSERT(pPrev->mState == BlockState::Free);
|
||||
|
||||
pPrev->free.pNext = pNext;
|
||||
|
||||
if (pNext != nullptr) {
|
||||
ASSERT(pNext->mState == BlockState::Free);
|
||||
pNext->free.pPrev = pPrev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t BuddyAllocator::Allocate(uint64_t allocationSize, uint64_t alignment) {
|
||||
if (allocationSize == 0 || allocationSize > mMaxBlockSize) {
|
||||
return INVALID_OFFSET;
|
||||
}
|
||||
|
||||
// Compute the level
|
||||
const uint32_t allocationSizeToLevel = ComputeLevelFromBlockSize(allocationSize);
|
||||
|
||||
ASSERT(allocationSizeToLevel < mFreeLists.size());
|
||||
|
||||
uint64_t currBlockLevel = GetNextFreeAlignedBlock(allocationSizeToLevel, alignment);
|
||||
|
||||
// Error when no free blocks exist (allocator is full)
|
||||
if (currBlockLevel == INVALID_OFFSET) {
|
||||
return INVALID_OFFSET;
|
||||
}
|
||||
|
||||
// Split free blocks level-by-level.
|
||||
// Terminate when the current block level is equal to the computed level of the requested
|
||||
// allocation.
|
||||
BuddyBlock* currBlock = mFreeLists[currBlockLevel].head;
|
||||
|
||||
for (; currBlockLevel < allocationSizeToLevel; currBlockLevel++) {
|
||||
ASSERT(currBlock->mState == BlockState::Free);
|
||||
|
||||
// Remove curr block (about to be split).
|
||||
RemoveFreeBlock(currBlock, currBlockLevel);
|
||||
|
||||
// Create two free child blocks (the buddies).
|
||||
const uint64_t nextLevelSize = currBlock->mSize / 2;
|
||||
BuddyBlock* leftChildBlock = new BuddyBlock(nextLevelSize, currBlock->mOffset);
|
||||
BuddyBlock* rightChildBlock =
|
||||
new BuddyBlock(nextLevelSize, currBlock->mOffset + nextLevelSize);
|
||||
|
||||
// Remember the parent to merge these back upon de-allocation.
|
||||
rightChildBlock->pParent = currBlock;
|
||||
leftChildBlock->pParent = currBlock;
|
||||
|
||||
// Make them buddies.
|
||||
leftChildBlock->pBuddy = rightChildBlock;
|
||||
rightChildBlock->pBuddy = leftChildBlock;
|
||||
|
||||
// Insert the children back into the free list into the next level.
|
||||
// The free list does not require a specific order. However, an order is specified as
|
||||
// it's ideal to allocate lower addresses first by having the leftmost child in HEAD.
|
||||
InsertFreeBlock(rightChildBlock, currBlockLevel + 1);
|
||||
InsertFreeBlock(leftChildBlock, currBlockLevel + 1);
|
||||
|
||||
// Curr block is now split.
|
||||
currBlock->mState = BlockState::Split;
|
||||
currBlock->split.pLeft = leftChildBlock;
|
||||
|
||||
// Decend down into the next level.
|
||||
currBlock = leftChildBlock;
|
||||
}
|
||||
|
||||
// Remove curr block from free-list (now allocated).
|
||||
RemoveFreeBlock(currBlock, currBlockLevel);
|
||||
currBlock->mState = BlockState::Allocated;
|
||||
|
||||
return currBlock->mOffset;
|
||||
}
|
||||
|
||||
void BuddyAllocator::Deallocate(uint64_t offset) {
|
||||
BuddyBlock* curr = mRoot;
|
||||
|
||||
// TODO(bryan.bernhart@intel.com): Optimize de-allocation.
|
||||
// Passing allocationSize directly will avoid the following level-by-level search;
|
||||
// however, it requires the size information to be stored outside the allocator.
|
||||
|
||||
// Search for the free block node that corresponds to the block offset.
|
||||
size_t currBlockLevel = 0;
|
||||
while (curr->mState == BlockState::Split) {
|
||||
if (offset < curr->split.pLeft->pBuddy->mOffset) {
|
||||
curr = curr->split.pLeft;
|
||||
} else {
|
||||
curr = curr->split.pLeft->pBuddy;
|
||||
}
|
||||
|
||||
currBlockLevel++;
|
||||
}
|
||||
|
||||
ASSERT(curr->mState == BlockState::Allocated);
|
||||
|
||||
// Ensure the block is at the correct level
|
||||
ASSERT(currBlockLevel == ComputeLevelFromBlockSize(curr->mSize));
|
||||
|
||||
// Mark curr free so we can merge.
|
||||
curr->mState = BlockState::Free;
|
||||
|
||||
// Merge the buddies (LevelN-to-Level0).
|
||||
while (currBlockLevel > 0 && curr->pBuddy->mState == BlockState::Free) {
|
||||
// Remove the buddy.
|
||||
RemoveFreeBlock(curr->pBuddy, currBlockLevel);
|
||||
|
||||
BuddyBlock* parent = curr->pParent;
|
||||
|
||||
// The buddies were inserted in a specific order but
|
||||
// could be deleted in any order.
|
||||
DeleteBlock(curr->pBuddy);
|
||||
DeleteBlock(curr);
|
||||
|
||||
// Parent is now free.
|
||||
parent->mState = BlockState::Free;
|
||||
|
||||
// Ascend up to the next level (parent block).
|
||||
curr = parent;
|
||||
currBlockLevel--;
|
||||
}
|
||||
|
||||
InsertFreeBlock(curr, currBlockLevel);
|
||||
}
|
||||
|
||||
// Helper which deletes a block in the tree recursively (post-order).
|
||||
void BuddyAllocator::DeleteBlock(BuddyBlock* block) {
|
||||
ASSERT(block != nullptr);
|
||||
|
||||
if (block->mState == BlockState::Split) {
|
||||
// Delete the pair in same order we inserted.
|
||||
DeleteBlock(block->split.pLeft->pBuddy);
|
||||
DeleteBlock(block->split.pLeft);
|
||||
}
|
||||
delete block;
|
||||
}
|
||||
|
||||
} // namespace dawn_native
|
111
src/dawn_native/BuddyAllocator.h
Normal file
111
src/dawn_native/BuddyAllocator.h
Normal file
@ -0,0 +1,111 @@
|
||||
// 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_BUDDYALLOCATOR_H_
|
||||
#define DAWNNATIVE_BUDDYALLOCATOR_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace dawn_native {
|
||||
|
||||
static constexpr uint64_t INVALID_OFFSET = std::numeric_limits<uint64_t>::max();
|
||||
|
||||
// Buddy allocator uses the buddy memory allocation technique to satisify an allocation request.
|
||||
// Memory is split into halves until just large enough to fit to the request. This
|
||||
// requires the allocation size to be a power-of-two value. The allocator "allocates" a block by
|
||||
// returning the starting offset whose size is guaranteed to be greater than or equal to the
|
||||
// allocation size. To deallocate, the same offset is used to find the corresponding block.
|
||||
//
|
||||
// Internally, it manages a free list to track free blocks in a full binary tree.
|
||||
// Every index in the free list corresponds to a level in the tree. That level also determines
|
||||
// the size of the block to be used to satisfy the request. The first level (index=0) represents
|
||||
// the root whose size is also called the max block size.
|
||||
//
|
||||
class BuddyAllocator {
|
||||
public:
|
||||
BuddyAllocator(uint64_t maxSize);
|
||||
~BuddyAllocator();
|
||||
|
||||
// Required methods.
|
||||
uint64_t Allocate(uint64_t allocationSize, uint64_t alignment = 1);
|
||||
void Deallocate(uint64_t offset);
|
||||
|
||||
// For testing purposes only.
|
||||
uint64_t ComputeTotalNumOfFreeBlocksForTesting() const;
|
||||
|
||||
private:
|
||||
uint32_t ComputeLevelFromBlockSize(uint64_t blockSize) const;
|
||||
uint64_t GetNextFreeAlignedBlock(size_t allocationBlockLevel, uint64_t alignment) const;
|
||||
|
||||
enum class BlockState { Free, Split, Allocated };
|
||||
|
||||
struct BuddyBlock {
|
||||
BuddyBlock(uint64_t size, uint64_t offset)
|
||||
: mOffset(offset), mSize(size), mState(BlockState::Free) {
|
||||
free.pPrev = nullptr;
|
||||
free.pNext = nullptr;
|
||||
}
|
||||
|
||||
uint64_t mOffset;
|
||||
uint64_t mSize;
|
||||
|
||||
// Pointer to this block's buddy, iff parent is split.
|
||||
// Used to quickly merge buddy blocks upon de-allocate.
|
||||
BuddyBlock* pBuddy = nullptr;
|
||||
BuddyBlock* pParent = nullptr;
|
||||
|
||||
// Track whether this block has been split or not.
|
||||
BlockState mState;
|
||||
|
||||
union {
|
||||
// Used upon allocation.
|
||||
// Avoids searching for the next free block.
|
||||
struct {
|
||||
BuddyBlock* pPrev;
|
||||
BuddyBlock* pNext;
|
||||
} free;
|
||||
|
||||
// Used upon de-allocation.
|
||||
// Had this block split upon allocation, it and it's buddy is to be deleted.
|
||||
struct {
|
||||
BuddyBlock* pLeft;
|
||||
} split;
|
||||
};
|
||||
};
|
||||
|
||||
void InsertFreeBlock(BuddyBlock* block, size_t level);
|
||||
void RemoveFreeBlock(BuddyBlock* block, size_t level);
|
||||
void DeleteBlock(BuddyBlock* block);
|
||||
|
||||
uint64_t ComputeNumOfFreeBlocks(BuddyBlock* block) const;
|
||||
|
||||
// Keep track the head and tail (for faster insertion/removal).
|
||||
struct BlockList {
|
||||
BuddyBlock* head = nullptr; // First free block in level.
|
||||
// TODO(bryan.bernhart@intel.com): Track the tail.
|
||||
};
|
||||
|
||||
BuddyBlock* mRoot = nullptr; // Used to deallocate non-free blocks.
|
||||
|
||||
uint64_t mMaxBlockSize = 0;
|
||||
|
||||
// List of linked-lists of free blocks where the index is a level that
|
||||
// corresponds to a power-of-two sized block.
|
||||
std::vector<BlockList> mFreeLists;
|
||||
};
|
||||
|
||||
} // namespace dawn_native
|
||||
|
||||
#endif // DAWNNATIVE_BUDDYALLOCATOR_H_
|
325
src/tests/unittests/BuddyAllocatorTests.cpp
Normal file
325
src/tests/unittests/BuddyAllocatorTests.cpp
Normal file
@ -0,0 +1,325 @@
|
||||
// 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/BuddyAllocator.h"
|
||||
|
||||
using namespace dawn_native;
|
||||
|
||||
// Verify the buddy allocator with a basic test.
|
||||
TEST(BuddyAllocatorTests, SingleBlock) {
|
||||
// After one 32 byte allocation:
|
||||
//
|
||||
// Level --------------------------------
|
||||
// 0 32 | A |
|
||||
// --------------------------------
|
||||
//
|
||||
constexpr uint64_t maxBlockSize = 32;
|
||||
BuddyAllocator allocator(maxBlockSize);
|
||||
|
||||
// Check that we cannot allocate a oversized block.
|
||||
ASSERT_EQ(allocator.Allocate(maxBlockSize * 2), INVALID_OFFSET);
|
||||
|
||||
// Check that we cannot allocate a zero sized block.
|
||||
ASSERT_EQ(allocator.Allocate(0u), INVALID_OFFSET);
|
||||
|
||||
// Allocate the block.
|
||||
uint64_t blockOffset = allocator.Allocate(maxBlockSize);
|
||||
ASSERT_EQ(blockOffset, 0u);
|
||||
|
||||
// Check that we are full.
|
||||
ASSERT_EQ(allocator.Allocate(maxBlockSize), INVALID_OFFSET);
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 0u);
|
||||
|
||||
// Deallocate the block.
|
||||
allocator.Deallocate(blockOffset);
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u);
|
||||
}
|
||||
|
||||
// Verify multiple allocations succeeds using a buddy allocator.
|
||||
TEST(BuddyAllocatorTests, MultipleBlocks) {
|
||||
// Fill every level in the allocator (order-n = 2^n)
|
||||
const uint64_t maxBlockSize = (1ull << 16);
|
||||
for (uint64_t order = 1; (1ull << order) <= maxBlockSize; order++) {
|
||||
BuddyAllocator allocator(maxBlockSize);
|
||||
|
||||
uint64_t blockSize = (1ull << order);
|
||||
for (uint32_t blocki = 0; blocki < (maxBlockSize / blockSize); blocki++) {
|
||||
ASSERT_EQ(allocator.Allocate(blockSize), blockSize * blocki);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that a single allocation succeeds using a buddy allocator.
|
||||
TEST(BuddyAllocatorTests, SingleSplitBlock) {
|
||||
// After one 8 byte allocation:
|
||||
//
|
||||
// Level --------------------------------
|
||||
// 0 32 | S |
|
||||
// --------------------------------
|
||||
// 1 16 | S | F | S - split
|
||||
// -------------------------------- F - free
|
||||
// 2 8 | A | F | | | A - allocated
|
||||
// --------------------------------
|
||||
//
|
||||
constexpr uint64_t maxBlockSize = 32;
|
||||
BuddyAllocator allocator(maxBlockSize);
|
||||
|
||||
// Allocate block (splits two blocks).
|
||||
uint64_t blockOffset = allocator.Allocate(8);
|
||||
ASSERT_EQ(blockOffset, 0u);
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 2u);
|
||||
|
||||
// Deallocate block (merges two blocks).
|
||||
allocator.Deallocate(blockOffset);
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u);
|
||||
|
||||
// Check that we cannot allocate a block that is oversized.
|
||||
ASSERT_EQ(allocator.Allocate(maxBlockSize * 2), INVALID_OFFSET);
|
||||
|
||||
// Re-allocate the largest block allowed after merging.
|
||||
blockOffset = allocator.Allocate(maxBlockSize);
|
||||
ASSERT_EQ(blockOffset, 0u);
|
||||
|
||||
allocator.Deallocate(blockOffset);
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u);
|
||||
}
|
||||
|
||||
// Verify that a multiple allocated blocks can be removed in the free-list.
|
||||
TEST(BuddyAllocatorTests, MultipleSplitBlocks) {
|
||||
// After four 16 byte allocations:
|
||||
//
|
||||
// Level --------------------------------
|
||||
// 0 32 | S |
|
||||
// --------------------------------
|
||||
// 1 16 | S | S | S - split
|
||||
// -------------------------------- F - free
|
||||
// 2 8 | Aa | Ab | Ac | Ad | A - allocated
|
||||
// --------------------------------
|
||||
//
|
||||
constexpr uint64_t maxBlockSize = 32;
|
||||
BuddyAllocator allocator(maxBlockSize);
|
||||
|
||||
// Populates the free-list with four blocks at Level2.
|
||||
|
||||
// Allocate "a" block (two splits).
|
||||
constexpr uint64_t blockSizeInBytes = 8;
|
||||
uint64_t blockOffsetA = allocator.Allocate(blockSizeInBytes);
|
||||
ASSERT_EQ(blockOffsetA, 0u);
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 2u);
|
||||
|
||||
// Allocate "b" block.
|
||||
uint64_t blockOffsetB = allocator.Allocate(blockSizeInBytes);
|
||||
ASSERT_EQ(blockOffsetB, blockSizeInBytes);
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u);
|
||||
|
||||
// Allocate "c" block (three splits).
|
||||
uint64_t blockOffsetC = allocator.Allocate(blockSizeInBytes);
|
||||
ASSERT_EQ(blockOffsetC, blockOffsetB + blockSizeInBytes);
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u);
|
||||
|
||||
// Allocate "d" block.
|
||||
uint64_t blockOffsetD = allocator.Allocate(blockSizeInBytes);
|
||||
ASSERT_EQ(blockOffsetD, blockOffsetC + blockSizeInBytes);
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 0u);
|
||||
|
||||
// Deallocate "d" block.
|
||||
// FreeList[Level2] = [BlockD] -> x
|
||||
allocator.Deallocate(blockOffsetD);
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u);
|
||||
|
||||
// Deallocate "b" block.
|
||||
// FreeList[Level2] = [BlockB] -> [BlockD] -> x
|
||||
allocator.Deallocate(blockOffsetB);
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 2u);
|
||||
|
||||
// Deallocate "c" block (one merges).
|
||||
// FreeList[Level1] = [BlockCD] -> x
|
||||
// FreeList[Level2] = [BlockB] -> x
|
||||
allocator.Deallocate(blockOffsetC);
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 2u);
|
||||
|
||||
// Deallocate "a" block (two merges).
|
||||
// FreeList[Level0] = [BlockABCD] -> x
|
||||
allocator.Deallocate(blockOffsetA);
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u);
|
||||
}
|
||||
|
||||
// Verify the buddy allocator can handle allocations of various sizes.
|
||||
TEST(BuddyAllocatorTests, MultipleSplitBlockIncreasingSize) {
|
||||
// After four Level4-to-Level1 byte then one L4 block allocations:
|
||||
//
|
||||
// Level -----------------------------------------------------------------
|
||||
// 0 512 | S |
|
||||
// -----------------------------------------------------------------
|
||||
// 1 256 | S | A |
|
||||
// -----------------------------------------------------------------
|
||||
// 2 128 | S | A | | |
|
||||
// -----------------------------------------------------------------
|
||||
// 3 64 | S | A | | | | | | |
|
||||
// -----------------------------------------------------------------
|
||||
// 4 32 | A | F | | | | | | | | | | | | | | |
|
||||
// -----------------------------------------------------------------
|
||||
//
|
||||
constexpr uint64_t maxBlockSize = 512;
|
||||
BuddyAllocator allocator(maxBlockSize);
|
||||
|
||||
ASSERT_EQ(allocator.Allocate(32), 0ull);
|
||||
ASSERT_EQ(allocator.Allocate(64), 64ull);
|
||||
ASSERT_EQ(allocator.Allocate(128), 128ull);
|
||||
ASSERT_EQ(allocator.Allocate(256), 256ull);
|
||||
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u);
|
||||
|
||||
// Fill in the last free block.
|
||||
ASSERT_EQ(allocator.Allocate(32), 32ull);
|
||||
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 0u);
|
||||
|
||||
// Check if we're full.
|
||||
ASSERT_EQ(allocator.Allocate(32), INVALID_OFFSET);
|
||||
}
|
||||
|
||||
// Verify very small allocations using a larger allocator works correctly.
|
||||
TEST(BuddyAllocatorTests, MultipleSplitBlocksVariableSizes) {
|
||||
// After allocating four pairs of one 64 byte block and one 32 byte block.
|
||||
//
|
||||
// Level -----------------------------------------------------------------
|
||||
// 0 512 | S |
|
||||
// -----------------------------------------------------------------
|
||||
// 1 256 | S | S |
|
||||
// -----------------------------------------------------------------
|
||||
// 2 128 | S | S | S | F |
|
||||
// -----------------------------------------------------------------
|
||||
// 3 64 | A | S | A | A | S | A | | |
|
||||
// -----------------------------------------------------------------
|
||||
// 4 32 | | | A | A | | | | | A | A | | | | | | |
|
||||
// -----------------------------------------------------------------
|
||||
//
|
||||
constexpr uint64_t maxBlockSize = 512;
|
||||
BuddyAllocator allocator(maxBlockSize);
|
||||
|
||||
ASSERT_EQ(allocator.Allocate(64), 0ull);
|
||||
ASSERT_EQ(allocator.Allocate(32), 64ull);
|
||||
|
||||
ASSERT_EQ(allocator.Allocate(64), 128ull);
|
||||
ASSERT_EQ(allocator.Allocate(32), 96ull);
|
||||
|
||||
ASSERT_EQ(allocator.Allocate(64), 192ull);
|
||||
ASSERT_EQ(allocator.Allocate(32), 256ull);
|
||||
|
||||
ASSERT_EQ(allocator.Allocate(64), 320ull);
|
||||
ASSERT_EQ(allocator.Allocate(32), 288ull);
|
||||
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u);
|
||||
}
|
||||
|
||||
// Verify the buddy allocator can deal with bad fragmentation.
|
||||
TEST(BuddyAllocatorTests, MultipleSplitBlocksInterleaved) {
|
||||
// Allocate every leaf then de-allocate every other of those allocations.
|
||||
//
|
||||
// Level -----------------------------------------------------------------
|
||||
// 0 512 | S |
|
||||
// -----------------------------------------------------------------
|
||||
// 1 256 | S | S |
|
||||
// -----------------------------------------------------------------
|
||||
// 2 128 | S | S | S | S |
|
||||
// -----------------------------------------------------------------
|
||||
// 3 64 | S | S | S | S | S | S | S | S |
|
||||
// -----------------------------------------------------------------
|
||||
// 4 32 | A | F | A | F | A | F | A | F | A | F | A | F | A | F | A | F |
|
||||
// -----------------------------------------------------------------
|
||||
//
|
||||
constexpr uint64_t maxBlockSize = 512;
|
||||
BuddyAllocator allocator(maxBlockSize);
|
||||
|
||||
// Allocate leaf blocks
|
||||
constexpr uint64_t minBlockSizeInBytes = 32;
|
||||
std::vector<uint64_t> blockOffsets;
|
||||
for (uint64_t i = 0; i < maxBlockSize / minBlockSizeInBytes; i++) {
|
||||
blockOffsets.push_back(allocator.Allocate(minBlockSizeInBytes));
|
||||
}
|
||||
|
||||
// Free every other leaf block.
|
||||
for (size_t count = 1; count < blockOffsets.size(); count += 2) {
|
||||
allocator.Deallocate(blockOffsets[count]);
|
||||
}
|
||||
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 8u);
|
||||
}
|
||||
|
||||
// Verify the buddy allocator can deal with multiple allocations with mixed alignments.
|
||||
TEST(BuddyAllocatorTests, SameSizeVariousAlignment) {
|
||||
// After two 8 byte allocations with 16 byte alignment then one 8 byte allocation with 8 byte
|
||||
// alignment.
|
||||
//
|
||||
// Level --------------------------------
|
||||
// 0 32 | S |
|
||||
// --------------------------------
|
||||
// 1 16 | S | S | S - split
|
||||
// -------------------------------- F - free
|
||||
// 2 8 | Aa | F | Ab | Ac | A - allocated
|
||||
// --------------------------------
|
||||
//
|
||||
BuddyAllocator allocator(32);
|
||||
|
||||
// Allocate Aa (two splits).
|
||||
ASSERT_EQ(allocator.Allocate(8, 16), 0u);
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 2u);
|
||||
|
||||
// Allocate Ab (skip Aa buddy due to alignment and perform another split).
|
||||
ASSERT_EQ(allocator.Allocate(8, 16), 16u);
|
||||
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 2u);
|
||||
|
||||
// Check that we cannot fit another.
|
||||
ASSERT_EQ(allocator.Allocate(8, 16), INVALID_OFFSET);
|
||||
|
||||
// Allocate Ac (zero splits and Ab's buddy is now the first free block).
|
||||
ASSERT_EQ(allocator.Allocate(8, 8), 24u);
|
||||
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u);
|
||||
}
|
||||
|
||||
// Verify the buddy allocator can deal with multiple allocations with equal alignments.
|
||||
TEST(BuddyAllocatorTests, VariousSizeSameAlignment) {
|
||||
// After two 8 byte allocations with 4 byte alignment then one 16 byte allocation with 4 byte
|
||||
// alignment.
|
||||
//
|
||||
// Level --------------------------------
|
||||
// 0 32 | S |
|
||||
// --------------------------------
|
||||
// 1 16 | S | Ac | S - split
|
||||
// -------------------------------- F - free
|
||||
// 2 8 | Aa | Ab | | A - allocated
|
||||
// --------------------------------
|
||||
//
|
||||
constexpr uint64_t maxBlockSize = 32;
|
||||
constexpr uint64_t alignment = 4;
|
||||
BuddyAllocator allocator(maxBlockSize);
|
||||
|
||||
// Allocate block Aa (two splits)
|
||||
ASSERT_EQ(allocator.Allocate(8, alignment), 0u);
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 2u);
|
||||
|
||||
// Allocate block Ab (Aa's buddy)
|
||||
ASSERT_EQ(allocator.Allocate(8, alignment), 8u);
|
||||
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u);
|
||||
|
||||
// Check that we can still allocate Ac.
|
||||
ASSERT_EQ(allocator.Allocate(16, alignment), 16ull);
|
||||
|
||||
ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 0u);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user