//===== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: // // $Workfile: $ // $Date: $ // //------------------------------------------------------------------------------ // $Log: $ // // $NoKeywords: $ //=============================================================================// #ifndef MEMPOOL_H #define MEMPOOL_H #ifdef _WIN32 #pragma once #endif #include "tier0/memalloc.h" #include "tier0/tslist.h" #include "tier0/platform.h" #include "tier1/utlvector.h" #include "tier1/utlrbtree.h" //----------------------------------------------------------------------------- // Purpose: Optimized pool memory allocator //----------------------------------------------------------------------------- typedef void (*MemoryPoolReportFunc_t)(PRINTF_FORMAT_STRING char const* pMsg, ...); class CUtlMemoryPool { public: // Ways the memory pool can grow when it needs to make a new blob. enum MemoryPoolGrowType_t { GROW_NONE = 0, // Don't allow new blobs. GROW_FAST = 1, // New blob size is numElements * (i+1) (ie: the blocks it allocates // get larger and larger each time it allocates one). GROW_SLOW = 2 // New blob size is numElements. }; CUtlMemoryPool(int blockSize, int numElements, int growMode = GROW_FAST, const char* pszAllocOwner = NULL, unsigned short nAlignment = 0); ~CUtlMemoryPool(); void* Alloc(); // Allocate the element size you specified in the constructor. void* Alloc(size_t amount); void* AllocZero(); // Allocate the element size you specified in the constructor, zero the memory before construction void* AllocZero(size_t amount); void Free(void* pMem); // Frees everything void Clear(); // Error reporting... static void SetErrorReportFunc(MemoryPoolReportFunc_t func); // returns number of allocated blocks 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; int m_NumBytes; // Number of bytes in this blob. char m_Data[1]; char m_Padding[3]; // to int align the struct }; // Resets the pool void Init(); void AddNewBlob(); void ReportLeaks(); int m_BlockSize; int m_BlocksPerBlob; int m_GrowMode; // GROW_ enum. int m_BlocksAllocated; int m_PeakAlloc; unsigned short m_nAlignment; unsigned short m_NumBlobs; const char* m_pszAllocOwner; CUtlMemoryPool::CBlob* m_pPrev; CUtlMemoryPool::CBlob* m_pNext; static MemoryPoolReportFunc_t g_ReportFunc; }; //----------------------------------------------------------------------------- // Multi-thread/Thread Safe Memory Class //----------------------------------------------------------------------------- class CMemoryPoolMT : public CUtlMemoryPool { public: CMemoryPoolMT(int blockSize, int numElements, int growMode = GROW_FAST, const char* pszAllocOwner = NULL, unsigned short 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); } // Frees everything void Clear() { AUTO_LOCK(m_mutex); return CUtlMemoryPool::Clear(); } private: CThreadFastMutex m_mutex; // @TODO: Rework to use tslist (toml 7/6/2007) }; //----------------------------------------------------------------------------- // Wrapper macro to make an allocator that returns particular typed allocations // and construction and destruction of objects. //----------------------------------------------------------------------------- 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(); }; //----------------------------------------------------------------------------- // Specialized pool for aligned data management (e.g., Xbox textures) //----------------------------------------------------------------------------- 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; // Chunks are tracked outside blocks (unlike CUtlMemoryPool) to simplify alignment issues FreeBlock_t* m_pFirstFree; int m_nFree; CAllocator m_Allocator; double m_TimeLastCompact; CThreadFastMutex m_mutex; }; //----------------------------------------------------------------------------- // Pool variant using standard allocation // TODO[ AMOS ]: commented from here as CTSList isn't implemented yet; // structure and the size thereof are unknown. //----------------------------------------------------------------------------- //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; //}; ////----------------------------------------------------------------------------- //// Fixed budget pool with overflow to malloc ////----------------------------------------------------------------------------- //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(); //} // // // // // ////----------------------------------------------------------------------------- //// Macros that make it simple to make a class use a fixed-size allocator //// Put DECLARE_FIXEDSIZE_ALLOCATOR in the private section of a class, //// Put DEFINE_FIXEDSIZE_ALLOCATOR in the CPP file ////----------------------------------------------------------------------------- //#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") // ////----------------------------------------------------------------------------- //// Macros that make it simple to make a class use a fixed-size allocator //// This version allows us to use a memory pool which is externally defined... //// Put DECLARE_FIXEDSIZE_ALLOCATOR_EXTERNAL in the private section of a class, //// Put DEFINE_FIXEDSIZE_ALLOCATOR_EXTERNAL in the CPP file ////----------------------------------------------------------------------------- // //#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) //{ // // These COMPILE_TIME_ASSERT checks need to be in individual scopes to avoid build breaks // // on MacOS and Linux due to a gcc bug. // { 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); // // // Insertion sort to encourage allocation clusters in chunks // 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 int __cdecl CAlignedMemPool::CompareChunk(void* const* ppLeft, void* const* ppRight) //{ // return size_cast((intp)*ppLeft - (intp)*ppRight); //} // //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 // MEMPOOL_H