Memory management is over
Here is another useless article about memory management.During my spare time, I try to write reusable components for game development and the first one is a simple "memory management framework".
The main objectives are:
- Simplicity,
- Thread safety,
- Performances,
- Tweakability.
The first point is the most difficult one to manage since, like most programmers, I start with a simple design and at the end I create a monster: "Yes, he is alive ! alive !".
The first thing is to identify what will be allocations/deallocations entry points. I decided not to use C++ ::new and ::delete operators. I prefered to define my own templated allocation/deallocation functions:
- New<>,
- Delete<>,
- NewArray<>,
- DeleteArray<>,
All these functions are templated and I use Boost preprocessor library to automatically generate different version of New method with different number of parameters. Here is a sample code which uses this interface:
CMyObject * pObject = Memory::New<CMyObject>(1.5f);
Memory::Delete(pObject);
Note: Alignment is managed inside allocation functions thanks to C++ __alignof function.
Memory allocation/deallocation inside New<> methods is performed through other methods:
- VirtualAlloc,
- VirtualAlignedAlloc,
- VirtualFree,
- VirtualAlignedFree.
I lied a bit in the previous code sample. Indeed, New/Delete and VirtualAlloc/Free functions are embeded in classes:
class CDefaultAllocPolicy
{
static void * VirtualAlloc (size_t Size);
static void VirtualFree (void * pPtr);
static void * VirtualAlignedAlloc (size_t Size, size_t Alignment);
static void VirtualAlignedFree (void * pPtr);
};
template <typename TAllocPolicy = CDefaultAllocPolicy>
class CAlloc
{
template <typename TType>
static TType * New (void);
template <typename TType>
static void Delete (TType * pPtr);
template <typename TType>
static TType * NewArray (size_t ElemCount);
template <typename TType>
static void DeleteArray (TType * pPtr);
};
template CAlloc<> TDefaultAlloc;
Source code is much more like this:
CMyObject * pObject = Memory::TDefaultAlloc::New<CMyObject>(1.5f);
Memory::TDefaultAlloc::Delete(pObject);
The ones who already developed multithreaded applications know that memory allocations could be an issue. Indeed, memory is a shared resource like others and concurrent allocations can generate huge slow down in your application. The straight forward solution is to use a synchronization mechanisms like semaphores, mutexes or critical sections but these ones are not the perfect solutions whether we want to be superlinear (http://herbsutter.wordpress.com/2008/01/30/effective-concurrency-going-superlinear/).
A common solution is to have multiple allocators and use Thread Local Storage to store the one which is currently used by a given thead. In my situation, allocators are memory Heaps (c.f. article about heap layers) which manage their own region of memory i.e. when I perform an allocation from one heap I don't need to block others.
Thread Local Storage is a commonly used trick in C++ to have some variables specific to a thread. On PC, you just have to use the directive __declspec(thread) e.g. :
__declspec(thread) unsigned int g_LocalStorage;
I have defined the following Thread specific structure:
struct SLocalStorage
{
Memory::IHeap * m_pCurrentHeap;
/* other data*/
};
and the following accessors:
SLocalStorage & GetLocalStorage (void);
void InitThread (void);
void ResetThread (void);
IHeap * SetCurrentHeap (IHeap * pHeap);
IHeap * GetCurrentHeap (void);
The code for Virtualloc/Free is quite simple:
void * System::Memory::VirtualAlloc (size_t const AllocSize)
{
Memory::IHeap * const pCurrHeap = Memory::GetCurrentHeap();
void * pMem = NULL;
if (pCurrHeap)
{
pMem = pCurrHeap->Alloc(AllocSize);
}
else
{
pMem = __VirtualAlloc(AllocSize);
}
return (pMem);
}
void System::Memory::VirtualFree (void * pBuffer)
{
Memory::IHeap * const pCurrHeap = Memory::GetCurrentHeap();
if (pCurrHeap)
{
pCurrHeap->Free(pBuffer);
}
else
{
__VirtualFree(pBuffer);
}
}
Note: __VirtualAlloc/Free are low level functions in charge of calling OS allocators i.e. malloc/free.
I have also implemented two helpers to perform allocations in a specific Heap. The first one is an object which follows Auto pattern to modify the current Heap for a given scope:
{
AUTO_HEAP("HeapTest1");
/* All New<>/Delete<> calls will go through HeapTest1 allocator */
}
The second one is a macro which declares Virtual alloc functions and New specializations for a given heap:
DECLARE_DOMAIN_NEW(12, Engine, "HeapTest1") ;
void MyFoo
{
CMyObject * pObject = Memory::TEngineAlloc::New<CMyObject>(1.5f);
Memory::TEngineAlloc::Delete(pObject);
}
I also implemented a Heap manager class in charge of gathering all heaps which are created.
That's all. Perhaps this memory management framework is a little bit too complex but it satisfies most of my needs ^^.
Now, since my memory management is over, I can start implementing more funny things .... let's implement a simple Virtual FileSystem
---
Manu

Commentaires