#ifndef MEMPOOL_H #define MEMPOOL_H #ifdef _WIN32 #pragma once #endif #include "memalloc.h" #include "tslist.h" #include "platform.h" #include "utlvector.h" #include "utlrbtree.h" typedef void (*MemoryPoolReportFunc_t)(PRINTF_FORMAT_STRING char const* pMsg, ...); class CUtlMemoryPool { public: enum MemoryPoolGrowType_t { GROW_NONE = 0, GROW_FAST = 1, GROW_SLOW = 2 }; CUtlMemoryPool(int blockSize, int numElements, int growMode = GROW_FAST, const char* pszAllocOwner = NULL, int nAlignment = 0); ~CUtlMemoryPool(); void* Alloc(); void* Alloc(size_t amount); void* AllocZero(); void* AllocZero(size_t amount); void Free(void* pMem); void Clear(); static void SetErrorReportFunc(MemoryPoolReportFunc_t func); int Count() const { return m_BlocksAllocated; } int PeakCount() const { return m_PeakAlloc; } int BlockSize() const { return m_BlockSize; } int Size() const; bool IsAllocationWithinPool(void* pMem) const; protected: class CBlob { public: CBlob* m_pPrev, * m_pNext; int m_NumBytes; char m_Data[1]; char m_Padding[3]; }; void Init(); void AddNewBlob(); void ReportLeaks(); int m_BlockSize; int m_BlocksPerBlob; int m_GrowMode; int m_BlocksAllocated; int m_PeakAlloc; unsigned short m_nAlignment; unsigned short m_NumBlobs; void* m_pHeadOfFreeList; const char* m_pszAllocOwner; CBlob m_BlobHead; static MemoryPoolReportFunc_t g_ReportFunc; }; class CMemoryPoolMT : public CUtlMemoryPool { public: CMemoryPoolMT(int blockSize, int numElements, int growMode = GROW_FAST, const char* pszAllocOwner = NULL, int nAlignment = 0) : CUtlMemoryPool(blockSize, numElements, growMode, pszAllocOwner, nAlignment) {} void* Alloc() { AUTO_LOCK(m_mutex); return CUtlMemoryPool::Alloc(); } void* Alloc(size_t amount) { AUTO_LOCK(m_mutex); return CUtlMemoryPool::Alloc(amount); } void* AllocZero() { AUTO_LOCK(m_mutex); return CUtlMemoryPool::AllocZero(); } void* AllocZero(size_t amount) { AUTO_LOCK(m_mutex); return CUtlMemoryPool::AllocZero(amount); } void Free(void* pMem) { AUTO_LOCK(m_mutex); CUtlMemoryPool::Free(pMem); } void Clear() { AUTO_LOCK(m_mutex); return CUtlMemoryPool::Clear(); } private: CThreadFastMutex m_mutex; }; template< class T > class CClassMemoryPool : public CUtlMemoryPool { public: CClassMemoryPool(int numElements, int growMode = GROW_FAST, int nAlignment = 0) : CUtlMemoryPool(sizeof(T), numElements, growMode, MEM_ALLOC_CLASSNAME(T), nAlignment) {} T* Alloc(); T* AllocZero(); void Free(T* pMem); void Clear(); }; template class CAlignedMemPool { enum { BLOCK_SIZE = COMPILETIME_MAX(ALIGN_VALUE(ITEM_SIZE, ALIGNMENT), 8), }; public: CAlignedMemPool(); void* Alloc(); void Free(void* p); static int __cdecl CompareChunk(void* const* ppLeft, void* const* ppRight); void Compact(); int NumTotal() { AUTO_LOCK(m_mutex); return m_Chunks.Count() * (CHUNK_SIZE / BLOCK_SIZE); } int NumAllocated() { AUTO_LOCK(m_mutex); return NumTotal() - m_nFree; } int NumFree() { AUTO_LOCK(m_mutex); return m_nFree; } int BytesTotal() { AUTO_LOCK(m_mutex); return NumTotal() * BLOCK_SIZE; } int BytesAllocated() { AUTO_LOCK(m_mutex); return NumAllocated() * BLOCK_SIZE; } int BytesFree() { AUTO_LOCK(m_mutex); return NumFree() * BLOCK_SIZE; } int ItemSize() { return ITEM_SIZE; } int BlockSize() { return BLOCK_SIZE; } int ChunkSize() { return CHUNK_SIZE; } private: struct FreeBlock_t { FreeBlock_t* pNext; byte reserved[BLOCK_SIZE - sizeof(FreeBlock_t*)]; }; CUtlVector m_Chunks; FreeBlock_t* m_pFirstFree; int m_nFree; CAllocator m_Allocator; double m_TimeLastCompact; CThreadFastMutex m_mutex; }; template class CObjectPool { public: CObjectPool() { int i = nInitialCount; while (i-- > 0) { m_AvailableObjects.PushItem(new T); } } ~CObjectPool() { Purge(); } int NumAvailable() { return m_AvailableObjects.Count(); } void Purge() { T* p = NULL; while (m_AvailableObjects.PopItem(&p)) { delete p; } } T* GetObject(bool bCreateNewIfEmpty = bDefCreateNewIfEmpty) { T* p = NULL; if (!m_AvailableObjects.PopItem(&p)) { p = (bCreateNewIfEmpty) ? new T : NULL; } return p; } void PutObject(T* p) { m_AvailableObjects.PushItem(p); } private: CTSList m_AvailableObjects; }; template class CFixedBudgetMemoryPool { public: CFixedBudgetMemoryPool() { m_pBase = m_pLimit = 0; COMPILE_TIME_ASSERT(ITEM_SIZE % 4 == 0); } bool Owns(void* p) { return (p >= m_pBase && p < m_pLimit); } void* Alloc() { MEM_ALLOC_CREDIT_CLASS(); #ifndef USE_MEM_DEBUG if (!m_pBase) { LOCAL_THREAD_LOCK(); if (!m_pBase) { byte* pMemory = m_pBase = (byte*)malloc(ITEM_COUNT * ITEM_SIZE); m_pLimit = m_pBase + (ITEM_COUNT * ITEM_SIZE); for (int i = 0; i < ITEM_COUNT; i++) { m_freeList.Push((TSLNodeBase_t*)pMemory); pMemory += ITEM_SIZE; } } } void* p = m_freeList.Pop(); if (p) return p; #endif return malloc(ITEM_SIZE); } void Free(void* p) { #ifndef USE_MEM_DEBUG if (Owns(p)) m_freeList.Push((TSLNodeBase_t*)p); else #endif free(p); } void Clear() { #ifndef USE_MEM_DEBUG if (m_pBase) { free(m_pBase); } m_pBase = m_pLimit = 0; Construct(&m_freeList); #endif } bool IsEmpty() { #ifndef USE_MEM_DEBUG if (m_pBase && m_freeList.Count() != ITEM_COUNT) return false; #endif return true; } enum { ITEM_SIZE = ALIGN_VALUE(PROVIDED_ITEM_SIZE, TSLIST_NODE_ALIGNMENT) }; CTSListBase m_freeList; byte* m_pBase; byte* m_pLimit; }; #define BIND_TO_FIXED_BUDGET_POOL( poolName ) \ inline void* operator new( size_t size ) { return poolName.Alloc(); } \ inline void* operator new( size_t size, int nBlockUse, const char *pFileName, int nLine ) { return poolName.Alloc(); } \ inline void operator delete( void* p ) { poolName.Free(p); } \ inline void operator delete( void* p, int nBlockUse, const char *pFileName, int nLine ) { poolName.Free(p); } template< class T > inline T* CClassMemoryPool::Alloc() { T* pRet; { MEM_ALLOC_CREDIT_CLASS(); pRet = (T*)CUtlMemoryPool::Alloc(); } if (pRet) { Construct(pRet); } return pRet; } template< class T > inline T* CClassMemoryPool::AllocZero() { T* pRet; { MEM_ALLOC_CREDIT_CLASS(); pRet = (T*)CUtlMemoryPool::AllocZero(); } if (pRet) { Construct(pRet); } return pRet; } template< class T > inline void CClassMemoryPool::Free(T* pMem) { if (pMem) { Destruct(pMem); } CUtlMemoryPool::Free(pMem); } template< class T > inline void CClassMemoryPool::Clear() { CUtlRBTree freeBlocks; SetDefLessFunc(freeBlocks); void* pCurFree = m_pHeadOfFreeList; while (pCurFree != NULL) { freeBlocks.Insert(pCurFree); pCurFree = *((void**)pCurFree); } for (CBlob* pCur = m_BlobHead.m_pNext; pCur != &m_BlobHead; pCur = pCur->m_pNext) { int nElements = pCur->m_NumBytes / this->m_BlockSize; T* p = (T*)AlignValue(pCur->m_Data, this->m_nAlignment); T* pLimit = p + nElements; while (p < pLimit) { if (freeBlocks.Find(p) == freeBlocks.InvalidIndex()) { Destruct(p); } p++; } } CUtlMemoryPool::Clear(); } #define DECLARE_FIXEDSIZE_ALLOCATOR( _class ) \ public: \ inline void* operator new( size_t size ) { MEM_ALLOC_CREDIT_(#_class " pool"); return s_Allocator.Alloc(size); } \ inline void* operator new( size_t size, int nBlockUse, const char *pFileName, int nLine ) { MEM_ALLOC_CREDIT_(#_class " pool"); return s_Allocator.Alloc(size); } \ inline void operator delete( void* p ) { s_Allocator.Free(p); } \ inline void operator delete( void* p, int nBlockUse, const char *pFileName, int nLine ) { s_Allocator.Free(p); } \ private: \ static CUtlMemoryPool s_Allocator #define DEFINE_FIXEDSIZE_ALLOCATOR( _class, _initsize, _grow ) \ CUtlMemoryPool _class::s_Allocator(sizeof(_class), _initsize, _grow, #_class " pool") #define DEFINE_FIXEDSIZE_ALLOCATOR_ALIGNED( _class, _initsize, _grow, _alignment ) \ CUtlMemoryPool _class::s_Allocator(sizeof(_class), _initsize, _grow, #_class " pool", _alignment ) #define DECLARE_FIXEDSIZE_ALLOCATOR_MT( _class ) \ public: \ inline void* operator new( size_t size ) { MEM_ALLOC_CREDIT_(#_class " pool"); return s_Allocator.Alloc(size); } \ inline void* operator new( size_t size, int nBlockUse, const char *pFileName, int nLine ) { MEM_ALLOC_CREDIT_(#_class " pool"); return s_Allocator.Alloc(size); } \ inline void operator delete( void* p ) { s_Allocator.Free(p); } \ inline void operator delete( void* p, int nBlockUse, const char *pFileName, int nLine ) { s_Allocator.Free(p); } \ private: \ static CMemoryPoolMT s_Allocator #define DEFINE_FIXEDSIZE_ALLOCATOR_MT( _class, _initsize, _grow ) \ CMemoryPoolMT _class::s_Allocator(sizeof(_class), _initsize, _grow, #_class " pool") #define DECLARE_FIXEDSIZE_ALLOCATOR_EXTERNAL( _class ) \ public: \ inline void* operator new( size_t size ) { MEM_ALLOC_CREDIT_(#_class " pool"); return s_pAllocator->Alloc(size); } \ inline void* operator new( size_t size, int nBlockUse, const char *pFileName, int nLine ) { MEM_ALLOC_CREDIT_(#_class " pool"); return s_pAllocator->Alloc(size); } \ inline void operator delete( void* p ) { s_pAllocator->Free(p); } \ private: \ static CUtlMemoryPool* s_pAllocator #define DEFINE_FIXEDSIZE_ALLOCATOR_EXTERNAL( _class, _allocator ) \ CUtlMemoryPool* _class::s_pAllocator = _allocator template inline CAlignedMemPool::CAlignedMemPool() : m_pFirstFree(0), m_nFree(0), m_TimeLastCompact(0) { { COMPILE_TIME_ASSERT(sizeof(FreeBlock_t) >= BLOCK_SIZE); } { COMPILE_TIME_ASSERT(ALIGN_VALUE(sizeof(FreeBlock_t), ALIGNMENT) == sizeof(FreeBlock_t)); } } template inline void* CAlignedMemPool::Alloc() { AUTO_LOCK(m_mutex); if (!m_pFirstFree) { if (!GROWMODE && m_Chunks.Count()) { return NULL; } FreeBlock_t* pNew = (FreeBlock_t*)m_Allocator.Alloc(CHUNK_SIZE); Assert((unsigned)pNew % ALIGNMENT == 0); m_Chunks.AddToTail(pNew); m_nFree = CHUNK_SIZE / BLOCK_SIZE; m_pFirstFree = pNew; for (int i = 0; i < m_nFree - 1; i++) { pNew->pNext = pNew + 1; pNew++; } pNew->pNext = NULL; } void* p = m_pFirstFree; m_pFirstFree = m_pFirstFree->pNext; m_nFree--; return p; } template inline void CAlignedMemPool::Free(void* p) { AUTO_LOCK(m_mutex); FreeBlock_t* pFree = ((FreeBlock_t*)p); FreeBlock_t* pCur = m_pFirstFree; FreeBlock_t* pPrev = NULL; while (pCur && pFree > pCur) { pPrev = pCur; pCur = pCur->pNext; } pFree->pNext = pCur; if (pPrev) { pPrev->pNext = pFree; } else { m_pFirstFree = pFree; } m_nFree++; if (m_nFree >= (CHUNK_SIZE / BLOCK_SIZE) * COMPACT_THRESHOLD) { double time = Plat_FloatTime(); double compactTime = (m_nFree >= (CHUNK_SIZE / BLOCK_SIZE) * COMPACT_THRESHOLD * 4) ? 15.0 : 30.0; if (m_TimeLastCompact > time || m_TimeLastCompact + compactTime < time) { Compact(); m_TimeLastCompact = time; } } } template inline void CAlignedMemPool::Compact() { FreeBlock_t* pCur = m_pFirstFree; FreeBlock_t* pPrev = NULL; m_Chunks.Sort(CompareChunk); #ifdef VALIDATE_ALIGNED_MEM_POOL { FreeBlock_t* p = m_pFirstFree; while (p) { if (p->pNext && p > p->pNext) { __asm { int 3 } } p = p->pNext; } for (int i = 0; i < m_Chunks.Count(); i++) { if (i + 1 < m_Chunks.Count()) { if (m_Chunks[i] > m_Chunks[i + 1]) { __asm { int 3 } } } } } #endif int i; for (i = 0; i < m_Chunks.Count(); i++) { int nBlocksPerChunk = CHUNK_SIZE / BLOCK_SIZE; FreeBlock_t* pChunkLimit = ((FreeBlock_t*)m_Chunks[i]) + nBlocksPerChunk; int nFromChunk = 0; if (pCur == m_Chunks[i]) { FreeBlock_t* pFirst = pCur; while (pCur && pCur >= m_Chunks[i] && pCur < pChunkLimit) { pCur = pCur->pNext; nFromChunk++; } pCur = pFirst; } while (pCur && pCur >= m_Chunks[i] && pCur < pChunkLimit) { if (nFromChunk != nBlocksPerChunk) { if (pPrev) { pPrev->pNext = pCur; } else { m_pFirstFree = pCur; } pPrev = pCur; } else if (pPrev) { pPrev->pNext = NULL; } else { m_pFirstFree = NULL; } pCur = pCur->pNext; } if (nFromChunk == nBlocksPerChunk) { m_Allocator.Free(m_Chunks[i]); m_nFree -= nBlocksPerChunk; m_Chunks[i] = 0; } } for (i = m_Chunks.Count() - 1; i >= 0; i--) { if (!m_Chunks[i]) { m_Chunks.FastRemove(i); } } } #endif