Prechádzať zdrojové kódy

Add an implementation of buddy allocator

Panagiotis Christopoulos Charitos 4 rokov pred
rodič
commit
2be8055cae

+ 8 - 0
AnKi/Config.h.cmake

@@ -191,6 +191,14 @@
 #	define __builtin_popcount __popcnt
 #	define __builtin_popcountl(x) int(__popcnt64(x))
 #	define __builtin_clzll(x) int(__lzcnt64(x))
+
+#pragma intrinsic(_BitScanForward)
+inline int __builtin_ctzll(unsigned long long x)
+{
+	unsigned long o;
+    _BitScanForward64(&o, x);
+    return o;
+}
 #endif
 
 // Constants

+ 2 - 2
AnKi/Math/Functions.h

@@ -113,9 +113,9 @@ inline constexpr T absolute(const T f)
 }
 
 template<typename T>
-inline constexpr T pow(const T x, const T power)
+inline constexpr T pow(const T x, const T exp)
 {
-	return T(std::pow(x, power));
+	return T(std::pow(x, exp));
 }
 
 template<typename T>

+ 1 - 0
AnKi/Util.h

@@ -41,6 +41,7 @@
 #include <AnKi/Util/Xml.h>
 #include <AnKi/Util/F16.h>
 #include <AnKi/Util/Function.h>
+#include <AnKi/Util/BuddyAllocator.h>
 
 /// @defgroup util Utilities (like STL)
 

+ 85 - 0
AnKi/Util/BuddyAllocator.h

@@ -0,0 +1,85 @@
+// Copyright (C) 2009-2021, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <AnKi/Util/DynamicArray.h>
+
+namespace anki {
+
+/// @addtogroup util_memory
+/// @{
+
+/// This is a generic implementation of a buddy allocator.
+/// @tparam T_MAX_MEMORY_RANGE_LOG2 The max memory to allocate.
+template<U32 T_MAX_MEMORY_RANGE_LOG2>
+class BuddyAllocator
+{
+public:
+	/// The type of the address.
+	using Address = std::conditional_t<(T_MAX_MEMORY_RANGE_LOG2 > 32), PtrSize, U32>;
+
+	~BuddyAllocator()
+	{
+		ANKI_ASSERT(m_allocationCount == 0 && "Forgot to deallocate");
+	}
+
+	/// Allocate memory.
+	/// @param alloc The allocator used for internal structures of the BuddyAllocator.
+	/// @param size The size of the allocation.
+	/// @param[out] address The returned address if the allocation didn't fail.
+	/// @return True if the allocation succeeded.
+	template<typename TAllocator>
+	ANKI_USE_RESULT Bool allocate(TAllocator alloc, PtrSize size, Address& address);
+
+	/// Free memory.
+	/// @param alloc The allocator used for internal structures of the BuddyAllocator.
+	/// @param address The address to free.
+	/// @param size The size of the allocation.
+	template<typename TAllocator>
+	void free(TAllocator alloc, Address address, PtrSize size);
+
+	/// Print a debug representation of the internal structures.
+	void debugPrint();
+
+private:
+	/// Because we need a constexpr version of pow.
+	template<typename T>
+	static constexpr T pow2(T exp)
+	{
+		return T(1) << exp;
+	}
+
+	static constexpr U32 log2(PtrSize v)
+	{
+		ANKI_ASSERT(isPowerOfTwo(v));
+		return U32(__builtin_ctzll(v));
+	}
+
+	static constexpr U32 ORDER_COUNT = T_MAX_MEMORY_RANGE_LOG2 + 1;
+	static constexpr PtrSize MAX_MEMORY_RANGE = pow2<PtrSize>(T_MAX_MEMORY_RANGE_LOG2);
+
+	using FreeList = DynamicArray<Address, PtrSize>;
+	Array<FreeList, ORDER_COUNT> m_freeLists;
+	U32 m_allocationCount = 0;
+
+	template<typename TAllocator>
+	PtrSize popFree(TAllocator& alloc, U32 order)
+	{
+		ANKI_ASSERT(m_freeLists[order].getSize() > 0);
+		const PtrSize address = m_freeLists[order].getBack();
+		m_freeLists[order].popBack(alloc);
+		ANKI_ASSERT(address < MAX_MEMORY_RANGE);
+		return address;
+	}
+
+	template<typename TAllocator>
+	void freeInternal(TAllocator& alloc, PtrSize address, PtrSize size);
+};
+/// @}
+
+} // end namespace anki
+
+#include <AnKi/Util/BuddyAllocator.inl.h>

+ 159 - 0
AnKi/Util/BuddyAllocator.inl.h

@@ -0,0 +1,159 @@
+// Copyright (C) 2009-2021, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <AnKi/Util/BuddyAllocator.h>
+
+namespace anki {
+
+template<U32 T_MAX_MEMORY_RANGE_LOG2>
+template<typename TAllocator>
+Bool BuddyAllocator<T_MAX_MEMORY_RANGE_LOG2>::allocate(TAllocator alloc, PtrSize size, Address& outAddress)
+{
+	ANKI_ASSERT(size > 0 && size <= MAX_MEMORY_RANGE);
+
+	// Lazy initialize
+	if(m_allocationCount == 0)
+	{
+		const Address startingAddress = 0;
+		m_freeLists[ORDER_COUNT - 1].create(alloc, 1, startingAddress);
+	}
+
+	// Find the order to start the search
+	const PtrSize alignedSize = nextPowerOfTwo(size);
+	U32 order = log2(alignedSize);
+
+	while(m_freeLists[order].getSize() == 0)
+	{
+		++order;
+		if(order >= m_freeLists.getSize())
+		{
+			// Out of memory
+			return false;
+		}
+	}
+
+	// Iterate
+	PtrSize address = popFree(alloc, order);
+	while(true)
+	{
+		const PtrSize orderSize = pow2<PtrSize>(order);
+		if(orderSize == alignedSize)
+		{
+			// Found the address
+			break;
+		}
+
+		const PtrSize buddyAddress = address + orderSize / 2;
+		ANKI_ASSERT(buddyAddress < MAX_MEMORY_RANGE && buddyAddress <= getMaxNumericLimit<Address>());
+
+		ANKI_ASSERT(order > 0);
+		m_freeLists[order - 1].emplaceBack(alloc, Address(buddyAddress));
+
+		--order;
+	}
+
+	ANKI_ASSERT(address + alignedSize <= MAX_MEMORY_RANGE);
+	++m_allocationCount;
+	ANKI_ASSERT(address <= getMaxNumericLimit<Address>());
+	outAddress = Address(address);
+	return true;
+}
+
+template<U32 T_MAX_MEMORY_RANGE_LOG2>
+template<typename TAllocator>
+void BuddyAllocator<T_MAX_MEMORY_RANGE_LOG2>::free(TAllocator alloc, Address address, PtrSize size)
+{
+	freeInternal(alloc, address, nextPowerOfTwo(size));
+
+	--m_allocationCount;
+
+	// Some checks
+	if(m_allocationCount == 0)
+	{
+		for(const FreeList& freeList : m_freeLists)
+		{
+			ANKI_ASSERT(freeList.getSize() == 0);
+		}
+	}
+}
+
+template<U32 T_MAX_MEMORY_RANGE_LOG2>
+template<typename TAllocator>
+void BuddyAllocator<T_MAX_MEMORY_RANGE_LOG2>::freeInternal(TAllocator& alloc, PtrSize address, PtrSize size)
+{
+	ANKI_ASSERT(isPowerOfTwo(size));
+	ANKI_ASSERT(address + size <= MAX_MEMORY_RANGE);
+
+	if(size == MAX_MEMORY_RANGE)
+	{
+		return;
+	}
+
+	// Find if the buddy is in the left side of the memory address space or the right one
+	const Bool buddyIsLeft = ((address / size) % 2) != 0;
+	PtrSize buddyAddress;
+	if(buddyIsLeft)
+	{
+		ANKI_ASSERT(address >= size);
+		buddyAddress = address - size;
+	}
+	else
+	{
+		buddyAddress = address + size;
+	}
+
+	ANKI_ASSERT(buddyAddress + size <= MAX_MEMORY_RANGE);
+
+	// Adjust the free lists
+	const U32 order = log2(size);
+	Bool buddyFound = false;
+	for(PtrSize i = 0; i < m_freeLists[order].getSize(); ++i)
+	{
+		if(m_freeLists[order][i] == buddyAddress)
+		{
+			m_freeLists[order].erase(alloc, m_freeLists[order].getBegin() + i);
+
+			freeInternal(alloc, (buddyIsLeft) ? buddyAddress : address, size * 2);
+			buddyFound = true;
+			break;
+		}
+	}
+
+	if(!buddyFound)
+	{
+		ANKI_ASSERT(address <= getMaxNumericLimit<Address>());
+		m_freeLists[order].emplaceBack(alloc, Address(address));
+	}
+}
+
+template<U32 T_MAX_MEMORY_RANGE_LOG2>
+void BuddyAllocator<T_MAX_MEMORY_RANGE_LOG2>::debugPrint()
+{
+	BitSet<MAX_MEMORY_RANGE>* freeBytes = new BitSet<MAX_MEMORY_RANGE>(false);
+	for(I32 order = ORDER_COUNT - 1; order >= 0; --order)
+	{
+		const PtrSize orderSize = pow2<PtrSize>(order);
+		freeBytes->unsetAll();
+
+		printf("%d: ", order);
+		for(PtrSize address : m_freeLists[order])
+		{
+			for(PtrSize byte = address; byte < address + orderSize; ++byte)
+			{
+				freeBytes->set(byte);
+			}
+		}
+
+		for(PtrSize i = 0; i < MAX_MEMORY_RANGE; ++i)
+		{
+			putc(freeBytes->get(i) ? 'F' : '?', stdout);
+		}
+
+		printf("\n");
+	}
+	delete freeBytes;
+}
+
+} // end namespace anki

+ 51 - 17
AnKi/Util/DynamicArray.h

@@ -201,6 +201,24 @@ public:
 		}
 	}
 
+	/// Destroy the array.
+	template<typename TAllocator>
+	void destroy(TAllocator alloc)
+	{
+		if(m_data)
+		{
+			ANKI_ASSERT(m_size > 0);
+			ANKI_ASSERT(m_capacity > 0);
+			alloc.deleteArray(m_data, m_size);
+
+			m_data = nullptr;
+			m_size = 0;
+			m_capacity = 0;
+		}
+
+		ANKI_ASSERT(m_data == nullptr && m_size == 0 && m_capacity == 0);
+	}
+
 	/// Grow or create the array. @a T needs to be copyable and moveable.
 	template<typename TAllocator>
 	void resize(TAllocator alloc, Size size, const Value& v);
@@ -236,22 +254,20 @@ public:
 	template<typename TAllocator, typename... TArgs>
 	Iterator emplaceAt(TAllocator alloc, ConstIterator where, TArgs&&... args);
 
-	/// Destroy the array.
+	/// Removes the (first, last] elements.
+	/// @param alloc The allocator.
+	/// @param first Points to the position of the first element to remove.
+	/// @param last Points to the position of the last element to remove minus one.
 	template<typename TAllocator>
-	void destroy(TAllocator alloc)
-	{
-		if(m_data)
-		{
-			ANKI_ASSERT(m_size > 0);
-			ANKI_ASSERT(m_capacity > 0);
-			alloc.deleteArray(m_data, m_size);
+	void erase(TAllocator alloc, ConstIterator first, ConstIterator last);
 
-			m_data = nullptr;
-			m_size = 0;
-			m_capacity = 0;
-		}
-
-		ANKI_ASSERT(m_data == nullptr && m_size == 0 && m_capacity == 0);
+	/// Removes one element.
+	/// @param alloc The allocator.
+	/// @param at Points to the position of the element to remove.
+	template<typename TAllocator>
+	void erase(TAllocator alloc, ConstIterator at)
+	{
+		erase(alloc, at, at + 1);
 	}
 
 	/// Validate it. Will only work when assertions are enabled.
@@ -412,6 +428,12 @@ public:
 		Base::resize(m_alloc, size, v);
 	}
 
+	/// @copydoc DynamicArray::resize
+	void resize(Size size)
+	{
+		Base::resize(m_alloc, size);
+	}
+
 	/// @copydoc DynamicArray::emplaceBack
 	template<typename... TArgs>
 	Iterator emplaceBack(TArgs&&... args)
@@ -419,6 +441,12 @@ public:
 		return Base::emplaceBack(m_alloc, std::forward<TArgs>(args)...);
 	}
 
+	/// @copydoc DynamicArray::popBack
+	void popBack()
+	{
+		Base::popBack(m_alloc);
+	}
+
 	/// @copydoc DynamicArray::emplaceAt
 	template<typename... TArgs>
 	Iterator emplaceAt(ConstIterator where, TArgs&&... args)
@@ -426,10 +454,16 @@ public:
 		return Base::emplaceAt(m_alloc, where, std::forward<TArgs>(args)...);
 	}
 
-	/// @copydoc DynamicArray::resize
-	void resize(Size size)
+	/// @copydoc DynamicArray::erase
+	void erase(ConstIterator first, ConstIterator last)
 	{
-		Base::resize(m_alloc, size);
+		return Base::erase(m_alloc, first, last);
+	}
+
+	/// @copydoc DynamicArray::erase
+	void erase(ConstIterator at)
+	{
+		return Base::erase(m_alloc, at);
 	}
 
 	/// @copydoc DynamicArray::moveAndReset

+ 25 - 2
AnKi/Util/DynamicArray.inl.h

@@ -28,7 +28,7 @@ void DynamicArray<T, TSize>::resizeStorage(TAllocator alloc, Size newSize)
 	{
 		// Need to grow
 
-		m_capacity = (newSize > Size(F32(m_capacity) * GROW_SCALE)) ? newSize : Size(F32(m_capacity) * GROW_SCALE);
+		m_capacity = (newSize > Size(F64(m_capacity) * GROW_SCALE)) ? newSize : Size(F64(m_capacity) * GROW_SCALE);
 		Value* newStorage =
 			static_cast<Value*>(alloc.getMemoryPool().allocate(m_capacity * sizeof(Value), alignof(Value)));
 
@@ -60,7 +60,7 @@ void DynamicArray<T, TSize>::resizeStorage(TAllocator alloc, Size newSize)
 
 		m_size = newSize;
 
-		if(newSize < Size(F32(m_capacity) / SHRINK_SCALE) || newSize == 0)
+		if(newSize < Size(F64(m_capacity) / SHRINK_SCALE) || newSize == 0)
 		{
 			// Need to shrink
 
@@ -204,4 +204,27 @@ typename DynamicArray<T, TSize>::Iterator DynamicArray<T, TSize>::emplaceAt(TAll
 	return &m_data[outIdx];
 }
 
+template<typename T, typename TSize>
+template<typename TAllocator>
+void DynamicArray<T, TSize>::erase(TAllocator alloc, ConstIterator first, ConstIterator last)
+{
+	ANKI_ASSERT(first != last);
+	ANKI_ASSERT(m_data);
+	ANKI_ASSERT(first >= m_data && first < m_data + m_size);
+	ANKI_ASSERT(last > m_data && last <= m_data + m_size);
+
+	// Move from the back to close the gap
+	const Size firsti = Size(first - m_data);
+	const Size lasti = Size(last - m_data);
+	const Size toMove = m_size - lasti;
+	for(Size i = 0; i < toMove; ++i)
+	{
+		m_data[firsti + i] = std::move(m_data[lasti + i]);
+	}
+
+	// Resize storage
+	const Size newSize = m_size - Size(last - first);
+	resizeStorage(alloc, newSize);
+}
+
 } // end namespace anki

+ 70 - 0
Tests/Util/BuddyAllocator.cpp

@@ -0,0 +1,70 @@
+// Copyright (C) 2009-2021, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <Tests/Framework/Framework.h>
+#include <AnKi/Util/BuddyAllocator.h>
+
+namespace anki {
+
+ANKI_TEST(Util, BuddyAllocator)
+{
+	HeapAllocator<U8> alloc(allocAligned, nullptr);
+
+	// Simple
+	{
+		BuddyAllocator<4> buddy;
+
+		Array<U32, 2> addr;
+		Bool success = buddy.allocate(alloc, 1, addr[0]);
+		success = buddy.allocate(alloc, 3, addr[1]);
+
+		// buddy.debugPrint();
+
+		buddy.free(alloc, addr[0], 1);
+		buddy.free(alloc, addr[1], 3);
+
+		// printf("\n");
+		// buddy.debugPrint();
+	}
+
+	// Fuzzy
+	{
+		BuddyAllocator<32> buddy;
+		std::vector<std::pair<U32, U32>> allocations;
+		for(U32 it = 0; it < 1000; ++it)
+		{
+			if((getRandom() % 2) == 0)
+			{
+				// Do an allocation
+				U32 addr;
+				const U32 size = max<U32>(getRandom() % 512, 1);
+				const Bool success = buddy.allocate(alloc, size, addr);
+				if(success)
+				{
+					allocations.push_back({addr, size});
+				}
+			}
+			else
+			{
+				// Do some deallocation
+				if(allocations.size())
+				{
+					const PtrSize randPos = getRandom() % allocations.size();
+					buddy.free(alloc, allocations[randPos].first, allocations[randPos].second);
+
+					allocations.erase(allocations.begin() + randPos);
+				}
+			}
+		}
+
+		// Remove the remaining
+		for(const auto& pair : allocations)
+		{
+			buddy.free(alloc, pair.first, pair.second);
+		}
+	}
+}
+
+} // end namespace anki

+ 85 - 19
Tests/Util/DynamicArray.cpp

@@ -10,61 +10,75 @@
 
 namespace anki {
 
-static I64 constructor0Count = 0;
-static I64 constructor1Count = 0;
-static I64 constructor2Count = 0;
-static I64 constructor3Count = 0;
-static I64 destructorCount = 0;
-static I64 copyCount = 0;
-static I64 moveCount = 0;
+static I64 g_constructor0Count = 0;
+static I64 g_constructor1Count = 0;
+static I64 g_constructor2Count = 0;
+static I64 g_constructor3Count = 0;
+static I64 g_destructorCount = 0;
+static I64 g_copyCount = 0;
+static I64 g_moveCount = 0;
 
 class DynamicArrayFoo
 {
 public:
+	static constexpr I32 WRONG_NUMBER = -1234;
+
 	int m_x;
 
 	DynamicArrayFoo()
 		: m_x(0)
 	{
-		++constructor0Count;
+		++g_constructor0Count;
 	}
 
 	DynamicArrayFoo(int x)
 		: m_x(x)
 	{
-		++constructor1Count;
+		++g_constructor1Count;
+		if(m_x == WRONG_NUMBER)
+		{
+			++m_x;
+		}
 	}
 
 	DynamicArrayFoo(const DynamicArrayFoo& b)
 		: m_x(b.m_x)
 	{
-		++constructor2Count;
+		ANKI_TEST_EXPECT_NEQ(b.m_x, WRONG_NUMBER);
+		++g_constructor2Count;
 	}
 
 	DynamicArrayFoo(DynamicArrayFoo&& b)
 		: m_x(b.m_x)
 	{
+		ANKI_TEST_EXPECT_NEQ(b.m_x, WRONG_NUMBER);
 		b.m_x = 0;
-		++constructor3Count;
+		++g_constructor3Count;
 	}
 
 	~DynamicArrayFoo()
 	{
-		++destructorCount;
+		ANKI_TEST_EXPECT_NEQ(m_x, WRONG_NUMBER);
+		m_x = WRONG_NUMBER;
+		++g_destructorCount;
 	}
 
 	DynamicArrayFoo& operator=(const DynamicArrayFoo& b)
 	{
+		ANKI_TEST_EXPECT_NEQ(m_x, WRONG_NUMBER);
+		ANKI_TEST_EXPECT_NEQ(b.m_x, WRONG_NUMBER);
 		m_x = b.m_x;
-		++copyCount;
+		++g_copyCount;
 		return *this;
 	}
 
 	DynamicArrayFoo& operator=(DynamicArrayFoo&& b)
 	{
+		ANKI_TEST_EXPECT_NEQ(m_x, WRONG_NUMBER);
+		ANKI_TEST_EXPECT_NEQ(b.m_x, WRONG_NUMBER);
 		m_x = b.m_x;
 		b.m_x = 0;
-		++moveCount;
+		++g_moveCount;
 		return *this;
 	}
 };
@@ -135,9 +149,9 @@ ANKI_TEST(Util, DynamicArray)
 
 		arr = DynamicArrayAuto<DynamicArrayFoo>(alloc);
 		vec = std::vector<DynamicArrayFoo>();
-		ANKI_TEST_EXPECT_GT(destructorCount, 0);
-		ANKI_TEST_EXPECT_EQ(constructor0Count + constructor1Count + constructor2Count + constructor3Count,
-							destructorCount);
+		ANKI_TEST_EXPECT_GT(g_destructorCount, 0);
+		ANKI_TEST_EXPECT_EQ(g_constructor0Count + g_constructor1Count + g_constructor2Count + g_constructor3Count,
+							g_destructorCount);
 	}
 }
 
@@ -250,7 +264,59 @@ ANKI_TEST(Util, DynamicArrayEmplaceAt)
 		arr.destroy();
 		vec.resize(0);
 
-		ANKI_TEST_EXPECT_EQ(constructor0Count + constructor1Count + constructor2Count + constructor3Count,
-							destructorCount);
+		ANKI_TEST_EXPECT_EQ(g_constructor0Count + g_constructor1Count + g_constructor2Count + g_constructor3Count,
+							g_destructorCount);
+	}
+}
+
+ANKI_TEST(Util, DynamicArrayErase)
+{
+	HeapAllocator<U8> alloc(allocAligned, nullptr);
+
+	// Fuzzy
+	{
+		srand(U32(time(nullptr)));
+
+		DynamicArrayAuto<DynamicArrayFoo> arr(alloc);
+		std::vector<DynamicArrayFoo> vec;
+
+		const I ITERATIONS = 10000;
+		for(I i = 0; i < ITERATIONS; ++i)
+		{
+			if(getRandom() % 2)
+			{
+				const I32 r = rand();
+				arr.emplaceBack(r);
+				vec.push_back(r);
+			}
+			else if(arr.getSize() > 0)
+			{
+				PtrSize eraseFrom = getRandom() % arr.getSize();
+				PtrSize eraseTo = getRandom() % (arr.getSize() + 1);
+				if(eraseTo < eraseFrom)
+				{
+					swapValues(eraseTo, eraseFrom);
+				}
+
+				if(eraseTo != eraseFrom)
+				{
+					vec.erase(vec.begin() + eraseFrom, vec.begin() + eraseTo);
+					arr.erase(arr.getBegin() + eraseFrom, arr.getBegin() + eraseTo);
+				}
+			}
+		}
+
+		// Check
+		ANKI_TEST_EXPECT_EQ(arr.getSize(), vec.size());
+		for(U32 i = 0; i < arr.getSize(); ++i)
+		{
+			ANKI_TEST_EXPECT_EQ(arr[i].m_x, vec[i].m_x);
+		}
+
+		arr.destroy();
+		vec.resize(0);
+
+		ANKI_TEST_EXPECT_EQ(g_constructor0Count + g_constructor1Count + g_constructor2Count + g_constructor3Count,
+							g_destructorCount);
 	}
 }