Browse Source

Implement the ClassAllocatorBuilder

Panagiotis Christopoulos Charitos 4 năm trước cách đây
mục cha
commit
ae6d9863ed

+ 1 - 0
AnKi/Util.h

@@ -43,6 +43,7 @@
 #include <AnKi/Util/Function.h>
 #include <AnKi/Util/BuddyAllocatorBuilder.h>
 #include <AnKi/Util/StackAllocatorBuilder.h>
+#include <AnKi/Util/ClassAllocatorBuilder.h>
 
 /// @defgroup util Utilities (like STL)
 

+ 119 - 0
AnKi/Util/ClassAllocatorBuilder.h

@@ -0,0 +1,119 @@
+// 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/List.h>
+#include <AnKi/Util/DynamicArray.h>
+
+namespace anki {
+
+/// @addtogroup util_memory
+/// @{
+
+/// This is a convenience class used to build class memory allocators.
+/// @tparam TChunk This is the type of the internally allocated chunks. This should be having the following members:
+///                @code
+///                BitSet<SOME_UPPER_LIMIT> m_inUseSuballocations
+///                U32 m_suballocationCount;
+///                void* m_class;
+///                @endcode
+///                And should inherit from IntrusiveListEnabled<TChunk>
+/// @tparam TInterface This is the type of the interface that contains various info. Should have the following members:
+///                    @code
+///                    U32 getClassCount();
+///                    void getClassInfo(U32 classIdx, PtrSize& chunkSize, PtrSize& suballocationSize) const;
+///                    Error allocateChunk(U32 classIdx, Chunk*& chunk);
+///                    void freeChunk(TChunk* out);
+///                    @endcode
+/// @tparam TLock This an optional lock. Can be a Mutex or SpinLock or some dummy class.
+template<typename TChunk, typename TInterface, typename TLock>
+class ClassAllocatorBuilder
+{
+public:
+	/// Create.
+	ClassAllocatorBuilder() = default;
+
+	ClassAllocatorBuilder(const ClassAllocatorBuilder&) = delete; // Non-copyable
+
+	/// Calls @a destroy().
+	~ClassAllocatorBuilder()
+	{
+		destroy();
+	}
+
+	ClassAllocatorBuilder& operator=(const ClassAllocatorBuilder&) = delete; // Non-copyable
+
+	/// Initialize it. Feel free to feedle with the TInterface before you do that.
+	void init(GenericMemoryPoolAllocator<U8> alloc);
+
+	/// Destroy the allocator builder.
+	void destroy();
+
+	/// Allocate memory.
+	/// @param size The size to allocate.
+	/// @param alignment The alignment of the returned address.
+	/// @param[out] chunk The chunk that the memory belongs to.
+	/// @param[out] offset The offset inside the chunk.
+	/// @note This is thread safe.
+	ANKI_USE_RESULT Error allocate(PtrSize size, PtrSize alignment, TChunk*& chunk, PtrSize& offset);
+
+	/// Free memory.
+	/// @param chunk The chunk the allocation belongs to.
+	/// @param offset The memory offset inside the chunk.
+	void free(TChunk* chunk, PtrSize offset);
+
+	/// Access the interface.
+	/// @note Not thread safe. Don't call it while calling allocate or free.
+	TInterface& getInterface()
+	{
+		return m_interface;
+	}
+
+	/// Access the interface.
+	/// @note Not thread safe. Don't call it while calling allocate or free.
+	const TInterface& getInterface() const
+	{
+		return m_interface;
+	}
+
+private:
+	/// A class of allocations. It's a list of memory chunks. Each chunk is dividied in suballocations.
+	class Class
+	{
+	public:
+		/// The active chunks.
+		IntrusiveList<TChunk> m_chunkList;
+
+		/// The size of each chunk.
+		PtrSize m_chunkSize = 0;
+
+		/// The max size a suballocation can have.
+		PtrSize m_suballocationSize = 0;
+
+		/// Lock.
+		TLock m_mtx;
+	};
+
+	GenericMemoryPoolAllocator<U8> m_alloc;
+
+	/// The interface as decribed in the class docs.
+	TInterface m_interface;
+
+	/// All the classes.
+	DynamicArray<Class> m_classes;
+
+	Class* findClass(PtrSize size, PtrSize alignment);
+
+	Bool isInitialized() const
+	{
+		return m_classes[0].m_chunkSize != 0;
+	}
+};
+/// @}
+
+} // end namespace anki
+
+#include <AnKi/Util/ClassAllocatorBuilder.inl.h>

+ 172 - 0
AnKi/Util/ClassAllocatorBuilder.inl.h

@@ -0,0 +1,172 @@
+// 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/ClassAllocatorBuilder.h>
+
+namespace anki {
+
+template<typename TChunk, typename TInterface, typename TLock>
+void ClassAllocatorBuilder<TChunk, TInterface, TLock>::init(GenericMemoryPoolAllocator<U8> alloc)
+{
+	m_alloc = alloc;
+
+	m_classes.create(m_alloc, m_interface.getClassCount());
+
+	for(U32 classIdx = 0; classIdx < m_classes.getSize(); ++classIdx)
+	{
+		Class& c = m_classes[classIdx];
+
+		m_interface.getClassInfo(classIdx, c.m_chunkSize, c.m_suballocationSize);
+		ANKI_ASSERT(c.m_suballocationSize > 0 && c.m_chunkSize > 0 && c.m_chunkSize >= c.m_suballocationSize);
+		ANKI_ASSERT((c.m_chunkSize % c.m_suballocationSize) == 0);
+	}
+}
+
+template<typename TChunk, typename TInterface, typename TLock>
+void ClassAllocatorBuilder<TChunk, TInterface, TLock>::destroy()
+{
+	for(Class& c : m_classes)
+	{
+		(void)c;
+		ANKI_ASSERT(c.m_chunkList.isEmpty() && "Forgot to deallocate");
+	}
+
+	m_classes.destroy(m_alloc);
+}
+
+template<typename TChunk, typename TInterface, typename TLock>
+typename ClassAllocatorBuilder<TChunk, TInterface, TLock>::Class*
+ClassAllocatorBuilder<TChunk, TInterface, TLock>::findClass(PtrSize size, PtrSize alignment)
+{
+	ANKI_ASSERT(size > 0 && alignment > 0);
+
+	PtrSize lowLimit = 0;
+	Class* it = m_classes.getBegin();
+	const Class* end = m_classes.getEnd();
+
+	while(it != end)
+	{
+		const PtrSize highLimit = it->m_suballocationSize;
+
+		if(size > lowLimit && size <= highLimit)
+		{
+			if(alignment <= highLimit)
+			{
+				// Found the class
+				return it;
+			}
+			else
+			{
+				// The class found doesn't have the proper alignment. Need to go higher
+
+				while(++it != end)
+				{
+					if(alignment <= it->m_suballocationSize)
+					{
+						// Now found something
+						return it;
+					}
+				}
+			}
+		}
+
+		lowLimit = highLimit;
+		++it;
+	}
+
+	ANKI_UTIL_LOGF("Memory class not found");
+	return nullptr;
+}
+
+template<typename TChunk, typename TInterface, typename TLock>
+Error ClassAllocatorBuilder<TChunk, TInterface, TLock>::allocate(PtrSize size, PtrSize alignment, TChunk*& chunk,
+																 PtrSize& offset)
+{
+	ANKI_ASSERT(isInitialized());
+	ANKI_ASSERT(size > 0 && alignment > 0);
+
+	chunk = nullptr;
+	offset = MAX_PTR_SIZE;
+
+	// Find the class for the given size
+	Class* cl = findClass(size, alignment);
+	const PtrSize maxSuballocationCount = cl->m_chunkSize / cl->m_suballocationSize;
+
+	LockGuard<TLock> lock(cl->m_mtx);
+
+	// Find chunk with free suballocation
+	auto it = cl->m_chunkList.getBegin();
+	const auto end = cl->m_chunkList.getEnd();
+	while(it != end)
+	{
+		if(it->m_suballocationCount < maxSuballocationCount)
+		{
+			chunk = &(*it);
+			break;
+		}
+
+		++it;
+	}
+
+	// Create a new chunk if needed
+	if(chunk == nullptr)
+	{
+		ANKI_CHECK(m_interface.allocateChunk(U32(cl - &m_classes[0]), chunk));
+
+		chunk->m_inUseSuballocations.unsetAll();
+		chunk->m_suballocationCount = 0;
+		chunk->m_class = cl;
+
+		cl->m_chunkList.pushBack(chunk);
+	}
+
+	// Allocate from chunk
+	const U32 bitCount = U32(maxSuballocationCount);
+	for(U32 i = 0; i < bitCount; ++i)
+	{
+		if(!chunk->m_inUseSuballocations.get(i))
+		{
+			// Found an empty slot, allocate from it
+			chunk->m_inUseSuballocations.set(i);
+			++chunk->m_suballocationCount;
+
+			offset = i * cl->m_suballocationSize;
+			break;
+		}
+	}
+
+	ANKI_ASSERT(chunk);
+	ANKI_ASSERT(isAligned(alignment, offset));
+	ANKI_ASSERT(offset + size <= cl->m_chunkSize);
+	return Error::NONE;
+}
+
+template<typename TChunk, typename TInterface, typename TLock>
+void ClassAllocatorBuilder<TChunk, TInterface, TLock>::free(TChunk* chunk, PtrSize offset)
+{
+	ANKI_ASSERT(isInitialized());
+	ANKI_ASSERT(chunk);
+
+	Class& cl = *static_cast<Class*>(chunk->m_class);
+
+	ANKI_ASSERT(offset < cl.m_chunkSize);
+
+	LockGuard<TLock> lock(cl.m_mtx);
+
+	const U32 suballocationIdx = U32(offset / cl.m_suballocationSize);
+
+	ANKI_ASSERT(chunk->m_inUseSuballocations.get(suballocationIdx));
+	ANKI_ASSERT(chunk->m_suballocationCount > 0);
+	chunk->m_inUseSuballocations.unset(suballocationIdx);
+	--chunk->m_suballocationCount;
+
+	if(chunk->m_suballocationCount == 0)
+	{
+		cl.m_chunkList.erase(chunk);
+		m_interface.freeChunk(chunk);
+	}
+}
+
+} // end namespace anki

+ 0 - 206
Tests/Gr/ClassGpuAllocator.cpp

@@ -1,206 +0,0 @@
-// 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/Gr/Utils/ClassGpuAllocator.h>
-#include <Tests/Framework/Framework.h>
-#include <random>
-#include <algorithm>
-
-namespace anki {
-
-class Mem : public ClassGpuAllocatorMemory
-{
-public:
-	void* m_mem = nullptr;
-	PtrSize m_size = 0;
-};
-
-class Interface final : public ClassGpuAllocatorInterface
-{
-public:
-	class Class
-	{
-	public:
-		Class(PtrSize slot, PtrSize cluster)
-			: m_slotSize(slot)
-			, m_clusterSize(cluster)
-		{
-		}
-
-		PtrSize m_slotSize;
-		PtrSize m_clusterSize;
-	};
-
-	std::vector<Class> m_classes;
-	PtrSize m_maxSize = 128 * 1024 * 1024;
-	PtrSize m_crntSize = 0;
-
-	Interface()
-	{
-		m_classes.push_back(Class(256, 16 * 1024));
-		m_classes.push_back(Class(4 * 1024, 256 * 1024));
-		m_classes.push_back(Class(128 * 1024, 8 * 1024 * 1024));
-		m_classes.push_back(Class(1 * 1024 * 1024, 32 * 1024 * 1024));
-		m_classes.push_back(Class(16 * 1024 * 1024, 128 * 1024 * 1024));
-		m_classes.push_back(Class(64 * 1024 * 1024, 256 * 1024 * 1024));
-		m_classes.push_back(Class(128 * 1024 * 1024, 256 * 1024 * 1024));
-	}
-
-	ANKI_USE_RESULT Error allocate(U32 classIdx, ClassGpuAllocatorMemory*& mem)
-	{
-		PtrSize size = m_classes[classIdx].m_clusterSize;
-
-		if(m_crntSize + size > m_maxSize)
-		{
-			return Error::OUT_OF_MEMORY;
-		}
-
-		PtrSize alignment = 256;
-
-		Mem* m = new Mem();
-
-		m_crntSize += size;
-
-		m->m_mem = mallocAligned(size, alignment);
-		m->m_size = size;
-		mem = m;
-
-		return Error::NONE;
-	}
-
-	void free(ClassGpuAllocatorMemory* mem)
-	{
-		Mem* m = static_cast<Mem*>(mem);
-		m_crntSize -= m->m_size;
-
-		freeAligned(m->m_mem);
-		delete m;
-	}
-
-	U32 getClassCount() const
-	{
-		return U32(m_classes.size());
-	}
-
-	void getClassInfo(U32 classIdx, PtrSize& slotSize, PtrSize& chunkSize) const
-	{
-		slotSize = m_classes[classIdx].m_slotSize;
-		chunkSize = m_classes[classIdx].m_clusterSize;
-	}
-};
-
-static inline U32 floorPow2(U32 v)
-{
-	v |= v >> 16;
-	v |= v >> 8;
-	v |= v >> 4;
-	v |= v >> 2;
-	v |= v >> 1;
-	v++;
-	return v >> 1;
-}
-
-ANKI_TEST(Gr, ClassGpuAllocator)
-{
-	HeapAllocator<U8> alloc(allocAligned, nullptr);
-	Interface iface;
-
-	ClassGpuAllocator calloc;
-	calloc.init(alloc, &iface);
-
-	std::mt19937 gen(0);
-
-	const U SHIFT = 15;
-	std::discrete_distribution<U> dis(16 * SHIFT, 0.0, F32(SHIFT), [](F32 c) {
-		return exp2(-0.5 * c);
-	});
-
-	auto nextAllocSize = [&]() -> U {
-		U size = U(256.0 * exp2(F64(dis(gen)) / 16.0));
-		return size;
-	};
-
-	std::vector<ClassGpuAllocatorHandle> handles;
-	const U TEST_COUNT = 100;
-	const U ITERATIONS = 20;
-
-	for(U tests = 0; tests < TEST_COUNT; ++tests)
-	{
-		for(U i = 0; i < ITERATIONS; ++i)
-		{
-			// Fill up the heap.
-			while(1)
-			{
-				ClassGpuAllocatorHandle handle;
-				PtrSize size = nextAllocSize();
-
-				if(calloc.allocate(size, 1, handle))
-				{
-					break;
-				}
-
-				handles.push_back(handle);
-			}
-
-			std::shuffle(handles.begin(), handles.end(), gen);
-
-			PtrSize halfSize = (handles.size() * 3) / 4;
-			for(PtrSize i = halfSize; i < handles.size(); ++i)
-			{
-				calloc.free(handles[i]);
-			}
-
-			handles.erase(handles.begin() + halfSize, handles.end());
-		}
-
-		// The heap should be roughly half-full now, so test fragmentation.
-		U32 freeSize = U32(iface.m_maxSize - iface.m_crntSize);
-		U32 baseFreeSize = floorPow2(freeSize);
-
-		const U32 BASE_SIZE = 256;
-		const F32 BIAS = 0.0;
-		const F32 POWER = 1.2f;
-		const F32 OFFSET = -1.0f;
-
-		F32 bestCase = 0.0;
-		{
-			// Best case is when we can allocate once for every bit that is set in the pow2 structure.
-			U32 freeBits = freeSize / BASE_SIZE;
-			for(U32 bit = 0; bit < 32; bit++)
-			{
-				if(freeBits & (1u << bit))
-				{
-					bestCase += (pow(POWER, F32(BIAS + F32(bit))) + OFFSET) * F32(BASE_SIZE << bit);
-				}
-			}
-		}
-
-		F32 score = 0.0;
-
-		while(baseFreeSize >= BASE_SIZE)
-		{
-			ClassGpuAllocatorHandle handle;
-			while(calloc.allocate(baseFreeSize, 1, handle) == Error::NONE)
-			{
-				score += (pow(POWER, (log2(F32(baseFreeSize / BASE_SIZE)) + BIAS)) + OFFSET) * F32(baseFreeSize);
-				handles.push_back(handle);
-				handle = {};
-			}
-
-			baseFreeSize >>= 1;
-		}
-
-		printf("Score: %.3f\n", score / bestCase);
-
-		// Cleanup
-		for(ClassGpuAllocatorHandle& h : handles)
-		{
-			calloc.free(h);
-		}
-		handles.clear();
-	}
-}
-
-} // end namespace anki

+ 208 - 0
Tests/Util/ClassAllocatorBuilder.cpp

@@ -0,0 +1,208 @@
+// 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/ClassAllocatorBuilder.h>
+#include <AnKi/Util/List.h>
+#include <AnKi/Util/Thread.h>
+#include <AnKi/Util/BitSet.h>
+#include <Tests/Framework/Framework.h>
+#include <random>
+#include <algorithm>
+
+namespace anki {
+
+namespace {
+
+class Chunk : public IntrusiveListEnabled<Chunk>
+{
+public:
+	Chunk() = default;
+
+	void* m_memory;
+
+	BitSet<128> m_inUseSuballocations = {false};
+
+	U32 m_suballocationCount;
+
+	void* m_class;
+
+	PtrSize m_size;
+};
+
+class Interface
+{
+public:
+	class Class
+	{
+	public:
+		Class(PtrSize suballocationSize, PtrSize cluster)
+			: m_suballocationSize(suballocationSize)
+			, m_chunkSize(cluster)
+		{
+		}
+
+		PtrSize m_suballocationSize;
+		PtrSize m_chunkSize;
+	};
+
+	Array<Class, 7> m_classes = {{Class(256, 16_KB), Class(4_KB, 256_KB), Class(128_KB, 8_MB), Class(1_MB, 32_MB),
+								  Class(16_MB, 128_MB), Class(64_MB, 256_MB), Class(128_MB, 256_MB)}};
+	static constexpr PtrSize MAX_SIZE = 128_MB;
+	PtrSize m_crntSize = 0;
+
+	ANKI_USE_RESULT Error allocateChunk(U32 classIdx, Chunk*& chunk)
+	{
+		PtrSize size = m_classes[classIdx].m_chunkSize;
+
+		if(m_crntSize + size > MAX_SIZE)
+		{
+			return Error::OUT_OF_MEMORY;
+		}
+
+		PtrSize alignment = 256;
+
+		chunk = new Chunk;
+		chunk->m_memory = mallocAligned(size, alignment);
+		chunk->m_size = size;
+
+		m_crntSize += size;
+
+		return Error::NONE;
+	}
+
+	void freeChunk(Chunk* chunk)
+	{
+		m_crntSize -= chunk->m_size;
+
+		freeAligned(chunk->m_memory);
+		delete chunk;
+	}
+
+	static constexpr U32 getClassCount()
+	{
+		return 7;
+	}
+
+	void getClassInfo(U32 classIdx, PtrSize& chunkSize, PtrSize& suballocationSize) const
+	{
+		suballocationSize = m_classes[classIdx].m_suballocationSize;
+		chunkSize = m_classes[classIdx].m_chunkSize;
+	}
+};
+
+} // namespace
+
+static inline U32 floorPow2(U32 v)
+{
+	v |= v >> 16;
+	v |= v >> 8;
+	v |= v >> 4;
+	v |= v >> 2;
+	v |= v >> 1;
+	v++;
+	return v >> 1;
+}
+
+ANKI_TEST(Util, ClassAllocatorBuilder)
+{
+	HeapAllocator<U8> alloc(allocAligned, nullptr);
+	ClassAllocatorBuilder<Chunk, Interface, Mutex> calloc;
+	calloc.init(alloc);
+
+	std::mt19937 gen(0);
+
+	const U SHIFT = 15;
+	std::discrete_distribution<U> dis(16 * SHIFT, 0.0, F32(SHIFT), [](F32 c) {
+		return exp2(-0.5 * c);
+	});
+
+	auto nextAllocSize = [&]() -> U {
+		U size = U(256.0 * exp2(F64(dis(gen)) / 16.0));
+		return size;
+	};
+
+	std::vector<std::pair<Chunk*, PtrSize>> allocations;
+	const U TEST_COUNT = 100;
+	const U ITERATIONS = 20;
+
+	for(U tests = 0; tests < TEST_COUNT; ++tests)
+	{
+		for(U i = 0; i < ITERATIONS; ++i)
+		{
+			// Fill up the heap.
+			while(1)
+			{
+				const PtrSize size = nextAllocSize();
+				Chunk* chunk;
+				PtrSize offset;
+
+				if(calloc.allocate(size, 1, chunk, offset))
+				{
+					break;
+				}
+
+				allocations.push_back({chunk, offset});
+			}
+
+			std::shuffle(allocations.begin(), allocations.end(), gen);
+
+			PtrSize halfSize = (allocations.size() * 3) / 4;
+			for(PtrSize i = halfSize; i < allocations.size(); ++i)
+			{
+				calloc.free(allocations[i].first, allocations[i].second);
+			}
+
+			allocations.erase(allocations.begin() + halfSize, allocations.end());
+		}
+
+		// The heap should be roughly half-full now, so test fragmentation.
+		const U32 freeSize = U32(Interface::MAX_SIZE - calloc.getInterface().m_crntSize);
+		U32 baseFreeSize = floorPow2(freeSize);
+
+		const U32 BASE_SIZE = 256;
+		const F32 BIAS = 0.0;
+		const F32 POWER = 1.2f;
+		const F32 OFFSET = -1.0f;
+
+		F32 bestCase = 0.0;
+		{
+			// Best case is when we can allocate once for every bit that is set in the pow2 structure.
+			U32 freeBits = freeSize / BASE_SIZE;
+			for(U32 bit = 0; bit < 32; bit++)
+			{
+				if(freeBits & (1u << bit))
+				{
+					bestCase += (pow(POWER, F32(BIAS + F32(bit))) + OFFSET) * F32(BASE_SIZE << bit);
+				}
+			}
+		}
+
+		F32 score = 0.0;
+
+		while(baseFreeSize >= BASE_SIZE)
+		{
+			Chunk* chunk;
+			PtrSize offset;
+			while(calloc.allocate(baseFreeSize, 1, chunk, offset) == Error::NONE)
+			{
+				score += (pow(POWER, (log2(F32(baseFreeSize / BASE_SIZE)) + BIAS)) + OFFSET) * F32(baseFreeSize);
+				allocations.push_back({chunk, offset});
+			}
+
+			baseFreeSize >>= 1;
+		}
+
+		printf("Score: %.3f\n", score / bestCase);
+
+		// Cleanup
+		for(auto& h : allocations)
+		{
+			calloc.free(h.first, h.second);
+		}
+		allocations.clear();
+	}
+}
+
+} // end namespace anki