فهرست منبع

Writing a chain allocator

Panagiotis Christopoulos Charitos 12 سال پیش
والد
کامیت
d0a2ac515a

+ 4 - 2
build/android.toolchain.cmake

@@ -297,6 +297,8 @@
 #     [+] updated for NDK r9
 #   - November 2013
 #     [+] updated for NDK r9b
+#   - December 2013
+#     [+] updated for NDK r9c
 # ------------------------------------------------------------------------------
 
 cmake_minimum_required( VERSION 2.6.3 )
@@ -323,7 +325,7 @@ set( CMAKE_SYSTEM_VERSION 1 )
 # rpath makes low sence for Android
 set( CMAKE_SKIP_RPATH TRUE CACHE BOOL "If set, runtime paths are not added when using shared libraries." )
 
-set( ANDROID_SUPPORTED_NDK_VERSIONS ${ANDROID_EXTRA_NDK_VERSIONS} -r9b -r9 -r8e -r8d -r8c -r8b -r8 -r7c -r7b -r7 -r6b -r6 -r5c -r5b -r5 "" )
+set( ANDROID_SUPPORTED_NDK_VERSIONS ${ANDROID_EXTRA_NDK_VERSIONS} -r9c -r9b -r9 -r8e -r8d -r8c -r8b -r8 -r7c -r7b -r7 -r6b -r6 -r5c -r5b -r5 "" )
 if(NOT DEFINED ANDROID_NDK_SEARCH_PATHS)
  if( CMAKE_HOST_WIN32 )
   file( TO_CMAKE_PATH "$ENV{PROGRAMFILES}" ANDROID_NDK_SEARCH_PATHS )
@@ -1735,7 +1737,7 @@ endif()
 #   BUILD_WITH_STANDALONE_TOOLCHAIN : TRUE if standalone toolchain is used
 #   ANDROID_NDK_HOST_SYSTEM_NAME : "windows", "linux-x86" or "darwin-x86" depending on host platform
 #   ANDROID_NDK_ABI_NAME : "armeabi", "armeabi-v7a", "x86" or "mips" depending on ANDROID_ABI
-#   ANDROID_NDK_RELEASE : one of r5, r5b, r5c, r6, r6b, r7, r7b, r7c, r8, r8b, r8c, r8d, r8e, r9, r9b; set only for NDK
+#   ANDROID_NDK_RELEASE : one of r5, r5b, r5c, r6, r6b, r7, r7b, r7c, r8, r8b, r8c, r8d, r8e, r9, r9b, r9c; set only for NDK
 #   ANDROID_ARCH_NAME : "arm" or "x86" or "mips" depending on ANDROID_ABI
 #   ANDROID_SYSROOT : path to the compiler sysroot
 #   TOOL_OS_SUFFIX : "" or ".exe" depending on host platform

+ 0 - 1
include/anki/Util.h

@@ -15,7 +15,6 @@
 #include "anki/util/Memory.h"
 #include "anki/util/NonCopyable.h"
 #include "anki/util/Object.h"
-#include "anki/util/Observer.h"
 #include "anki/util/Singleton.h"
 #include "anki/util/StdTypes.h"
 #include "anki/util/StringList.h"

+ 4 - 0
include/anki/scene/Property.h

@@ -1,6 +1,8 @@
 #ifndef ANKI_SCENE_PROPERTY_H
 #define ANKI_SCENE_PROPERTY_H
 
+#if 0
+
 #include "anki/util/Observer.h"
 #include "anki/util/Exception.h"
 #include "anki/util/Assert.h"
@@ -390,3 +392,5 @@ private:
 } // end namespace anki
 
 #endif
+
+#endif

+ 1 - 1
include/anki/util/Allocator.h

@@ -98,7 +98,7 @@ public:
 		// custom mem allocation function
 		PtrSize size = n * sizeof(T);
 		allocatedSize += size;
-		return (T*)mallocAligned(size, alignof(T));
+		return (pointer)mallocAligned(size, alignof(T));
 	}
 
 	/// Deallocate memory

+ 81 - 12
include/anki/util/Memory.h

@@ -4,24 +4,28 @@
 #include "anki/util/StdTypes.h"
 #include "anki/util/Assert.h"
 #include "anki/util/NonCopyable.h"
+#include "anki/util/Thread.h"
 #include <atomic>
+#include <vector>
+#include <memory>
 #include <algorithm> // For the std::move
 
 namespace anki {
 
-// Forward
-template<typename T>
-class Allocator;
-
 /// @addtogroup util
 /// @{
 /// @addtogroup memory
 /// @{
 
-/// Thread safe memory pool
-///
-/// It's a preallocated memory pool that is used for memory allocations on top
-/// of that preallocated memory. It is mainly used by fast stack allocators
+/// Allocate aligned memory
+void* mallocAligned(PtrSize size, PtrSize alignmentBytes);
+
+/// Free aligned memory
+void freeAligned(void* ptr);
+
+/// Thread safe memory pool. It's a preallocated memory pool that is used for 
+/// memory allocations on top of that preallocated memory. It is mainly used by 
+/// fast stack allocators
 class StackMemoryPool: public NonCopyable
 {
 public:
@@ -77,11 +81,76 @@ private:
 	std::atomic<U8*> top = {nullptr};
 };
 
-/// Allocate aligned memory
-extern void* mallocAligned(PtrSize size, PtrSize alignmentBytes);
+/// Chain memory pool. Almost similar to StackMemoryPool but more flexible and 
+/// at the same time a bit slower.
+class ChainMemoryPool: public NonCopyable
+{
+public:
+	/// Chunk allocation method
+	enum NextChunkAllocationMethod
+	{
+		FIXED,
+		MULTIPLY,
+		ADD
+	};
 
-/// Free aligned memory
-extern void freeAligned(void* ptr);
+	/// Default constructor
+	ChainMemoryPool(
+		NextChunkAllocationMethod allocMethod, 
+		U32 allocMethodValue, 
+		U32 alignmentBytes = ANKI_SAFE_ALIGNMENT);
+
+	/// Destroy
+	~ChainMemoryPool();
+
+	/// Allocate memory. This operation is thread safe
+	/// @return The allocated memory or nullptr on failure
+	void* allocate(PtrSize size) throw();
+
+	/// Free memory. If the ptr is not the last allocation of the chunk
+	/// then nothing happens and the method returns false
+	///
+	/// @param[in, out] ptr Memory block to deallocate
+	/// @return True if the deallocation actually happened and false otherwise
+	Bool free(void* ptr) throw();
+
+private:
+	/// A chunk of memory
+	class Chunk
+	{
+	public:
+		StackMemoryPool pool;
+		std::atomic<U32> allocationsCount;
+
+		Chunk(PtrSize size, U32 alignmentBytes)
+			: pool(size, alignmentBytes), allocationsCount{0}
+		{}
+	};
+
+	/// Alignment of allocations
+	U32 alignmentBytes;
+
+	/// A list of chunks
+	std::vector<Chunk*> chunks;
+
+	/// Current chunk to allocate from
+	Chunk* crntChunk = nullptr;
+
+	/// Fast thread locking
+	SpinLock lock;
+
+	/// Chunk allocation method value
+	U32 chAllocMethodValue;
+
+	/// Chunk allocation method
+	U8 chAllocMethod;
+
+	/// Allocate memory from a chunk
+	void* allocateFromChunk(Chunk* ch, PtrSize size) throw();
+
+	/// Create a new chunk
+	Chunk* createNewChunk(PtrSize minSize) throw();
+};
 
 /// @}
 /// @}

+ 42 - 9
include/anki/util/Thread.h

@@ -1,22 +1,50 @@
 #ifndef ANKI_UTIL_THREAD_H
 #define ANKI_UTIL_THREAD_H
 
-#include "anki/Config.h"
 #include "anki/util/StdTypes.h"
 #include "anki/util/Array.h"
-#include "anki/util/Vector.h"
+#include "anki/util/NonCopyable.h"
+#include <thread>
 #include <condition_variable>
 #include <mutex>
-#include <thread>
-
-namespace anki {
+#include <atomic>
 
 #define ANKI_DISABLE_THREADPOOL_THREADING 0
 
+namespace anki {
+
+// Forward
 class Threadpool;
 
+/// @addtogroup util
+/// @{
+/// @addtogroup thread
+/// @{
+
 typedef U32 ThreadId;
 
+/// Spin lock. Good if the critical section will be executed in a short period
+/// of time
+class SpinLock
+{
+public:
+	/// Lock 
+	void lock()
+	{
+		while(l.test_and_set(std::memory_order_acquire))
+		{}
+	}
+
+	/// Unlock
+	void unlock()
+	{
+		l.clear(std::memory_order_release);
+	}
+
+private:
+	std::atomic_flag l = ATOMIC_FLAG_INIT;	
+};
+
 /// A barrier for thread synchronization. It works just like boost::barrier
 class Barrier
 {
@@ -165,7 +193,7 @@ private:
 
 /// Parallel task dispatcher.You feed it with tasks and sends them for
 /// execution in parallel and then waits for all to finish
-class Threadpool
+class Threadpool: public NonCopyable
 {
 public:
 	static constexpr U MAX_THREADS = 32; ///< An absolute limit
@@ -188,6 +216,7 @@ public:
 	/// Assign a task to a working thread
 	void assignNewTask(U taskId, ThreadpoolTask* task)
 	{
+		ANKI_ASSERT(taskId < getThreadsCount());
 		threads[taskId]->assignNewTask(task);
 	}
 
@@ -199,16 +228,20 @@ public:
 #endif
 	}
 
-	U32 getThreadsCount() const
+	U getThreadsCount() const
 	{
-		return threads.size();
+		return threadsCount;
 	}
 
 private:
-	Vector<ThreadpoolThread*> threads; ///< Worker threads
+	ThreadpoolThread** threads = nullptr; ///< Worker threads array
+	U8 threadsCount = 0;
 	std::unique_ptr<Barrier> barrier; ///< Synchronization barrier
 };
 
+/// @}
+/// @}
+
 } // end namespace anki
 
 #endif

+ 162 - 35
src/util/Memory.cpp

@@ -8,6 +8,61 @@
 
 namespace anki {
 
+//==============================================================================
+// Other                                                                       =
+//==============================================================================
+
+//==============================================================================
+void* mallocAligned(PtrSize size, PtrSize alignmentBytes)
+{
+#if ANKI_POSIX 
+#	if ANKI_OS != ANKI_OS_ANDROID
+	void* out;
+	int err = posix_memalign(
+		&out, getAlignedRoundUp(alignmentBytes, sizeof(void*)), size);
+
+	if(!err)
+	{
+		// Make sure it's aligned
+		ANKI_ASSERT(isAligned(alignmentBytes, out));
+		return out;
+	}
+	else
+	{
+		throw ANKI_EXCEPTION("mallocAligned() failed");
+		return nullptr;
+	}
+#	else
+	void* out = memalign(
+		getAlignedRoundUp(alignmentBytes, sizeof(void*)), size);
+
+	if(out)
+	{
+		// Make sure it's aligned
+		ANKI_ASSERT(isAligned(alignmentBytes, out));
+		return out;
+	}
+	else
+	{
+		throw ANKI_EXCEPTION("mallocAligned() failed");
+		return nullptr;
+	}
+#	endif
+#else
+#	error "Unimplemented"
+#endif
+}
+
+//==============================================================================
+void freeAligned(void* ptr)
+{
+#if ANKI_POSIX
+	::free(ptr);
+#else
+#	error "Unimplemented"
+#endif
+}
+
 //==============================================================================
 // StackMemoryPool                                                             =
 //==============================================================================
@@ -162,58 +217,130 @@ void StackMemoryPool::reset()
 }
 
 //==============================================================================
-// Other                                                                       =
+// ChainMemoryPool                                                             =
 //==============================================================================
 
 //==============================================================================
-void* mallocAligned(PtrSize size, PtrSize alignmentBytes)
+ChainMemoryPool::ChainMemoryPool(
+	NextChunkAllocationMethod allocMethod, 
+	U32 allocMethodValue, 
+	U32 alignmentBytes_)
+	:	alignmentBytes(alignmentBytes_), 
+		chAllocMethodValue(allocMethodValue),
+		chAllocMethod(allocMethod)
+{}
+
+//==============================================================================
+ChainMemoryPool::~ChainMemoryPool()
 {
-#if ANKI_POSIX 
-#	if ANKI_OS != ANKI_OS_ANDROID
-	void* out;
-	int err = posix_memalign(
-		&out, getAlignedRoundUp(alignmentBytes, sizeof(void*)), size);
+	// TODO
+}
 
-	if(!err)
-	{
-		// Make sure it's aligned
-		ANKI_ASSERT(isAligned(alignmentBytes, out));
-		return out;
-	}
-	else
+//==============================================================================
+void* ChainMemoryPool::allocateFromChunk(Chunk* ch, PtrSize size) throw()
+{
+	ANKI_ASSERT(ch);
+	void* mem = ch->pool.allocate(size);
+
+	if(mem)
 	{
-		throw ANKI_EXCEPTION("mallocAligned() failed");
-		return nullptr;
+		++ch->allocationsCount;
 	}
-#	else
-	void* out = memalign(
-		getAlignedRoundUp(alignmentBytes, sizeof(void*)), size);
 
-	if(out)
+	return mem;
+}
+
+//==============================================================================
+ChainMemoryPool::Chunk* ChainMemoryPool::createNewChunk(PtrSize size) throw()
+{
+	//
+	// Calculate preferred size
+	//
+	
+	// Get the size of the next chunk
+	PtrSize crntMaxSize;
+	if(chAllocMethod == FIXED)
 	{
-		// Make sure it's aligned
-		ANKI_ASSERT(isAligned(alignmentBytes, out));
-		return out;
+		crntMaxSize = chAllocMethodValue;
 	}
 	else
 	{
-		throw ANKI_EXCEPTION("mallocAligned() failed");
-		return nullptr;
+		// Get the size of the previous max chunk
+		crntMaxSize = 0;
+		for(Chunk* c : chunks)
+		{
+			if(c)
+			{
+				PtrSize poolSize = c->pool.getSize();
+				crntMaxSize = std::max(crntMaxSize, poolSize);
+			}
+		}
+
+		// Increase it
+		if(chAllocMethod == MULTIPLY)
+		{
+			crntMaxSize *= chAllocMethodValue;
+		}
+		else
+		{
+			ANKI_ASSERT(chAllocMethod == ADD);
+			crntMaxSize += chAllocMethodValue;
+		}
 	}
-#	endif
-#else
-#	error "Unimplemented"
-#endif
+
+	// Fix the size
+	size = std::max(crntMaxSize, size * 2);
+
+	//
+	// Create the chunk
+	//
+	Chunk* chunk = new Chunk(size, alignmentBytes);
+
+	if(chunk)
+	{
+		chunks.push_back(chunk);
+	}
+	
+	return chunk;
 }
 
 //==============================================================================
-void freeAligned(void* ptr)
+void* ChainMemoryPool::allocate(PtrSize size) throw()
 {
-#if ANKI_POSIX
-	::free(ptr);
-#else
-#	error "Unimplemented"
-#endif
+	Chunk* ch;
+	void* mem = nullptr;
+
+	// Get chunk
+	lock.lock();
+	ch = crntChunk;
+	lock.unlock();
+
+	// Create new chunk if needed
+	if(ch == nullptr || (mem = allocateFromChunk(ch, size)) == nullptr)
+	{
+		// Create new chunk
+		lock.lock();
+
+		crntChunk = createNewChunk(size);
+		ch = crntChunk;
+
+		lock.unlock();
+
+		// Chunk creation failed
+		if(ch == nullptr)
+		{
+			return mem;
+		}
+	}
+
+	if(mem == nullptr)
+	{
+		ANKI_ASSERT(ch != nullptr);
+		mem = allocateFromChunk(ch, size);
+		ANKI_ASSERT(mem != nullptr && "The chunk should have space");
+	}
+
+	return mem;
 }
 
 } // end namespace anki

+ 14 - 6
src/util/Thread.cpp

@@ -1,5 +1,6 @@
 #include "anki/util/Thread.h"
 #include "anki/util/Assert.h"
+#include "anki/util/Exception.h"
 
 namespace anki {
 
@@ -88,23 +89,30 @@ void ThreadpoolThread::workingFunc()
 //==============================================================================
 Threadpool::~Threadpool()
 {
-	for(ThreadpoolThread* thread : threads)
+	while(threadsCount-- != 0)
 	{
-		delete thread;
+		delete threads[threadsCount];
+	}
+
+	if(threads)
+	{
+		delete[] threads;
 	}
 }
 
 //==============================================================================
-void Threadpool::init(U threadsCount)
+void Threadpool::init(U count)
 {
+	threadsCount = count;
 	ANKI_ASSERT(threadsCount <= MAX_THREADS && threadsCount > 0);
 
 	barrier.reset(new Barrier(threadsCount + 1));
 
-	threads.resize(threadsCount);
-	for(U i = 0; i < threadsCount; i++)
+	threads = new ThreadpoolThread*[threadsCount];
+
+	while(count-- != 0)
 	{
-		threads[i] = new ThreadpoolThread(i, barrier.get(), this);
+		threads[count] = new ThreadpoolThread(count, barrier.get(), this);
 	}
 }