//========= Copyright 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/platform.h" #include "../tier0/tslist.h" #include "utlrbtree.h" #include "utlvector.h" //----------------------------------------------------------------------------- // Purpose: Optimized pool memory allocator //----------------------------------------------------------------------------- typedef void (*MemoryPoolReportFunc_t)(PRINTF_FORMAT_STRING char const *pMsg, ...); // Ways a memory pool can grow when it needs to make a new blob: enum MemoryPoolGrowType_t { UTLMEMORYPOOL_GROW_NONE = 0, // Don't allow new blobs. UTLMEMORYPOOL_GROW_FAST = 1, // New blob size is numElements * (i+1) (ie: the blocks it // allocates get larger and larger each time it allocates one). UTLMEMORYPOOL_GROW_SLOW = 2 // New blob size is numElements. }; class CUtlMemoryPool { public: // !KLUDGE! For legacy code support, import the global enum into this scope enum MemoryPoolGrowType_t { GROW_NONE = UTLMEMORYPOOL_GROW_NONE, GROW_FAST = UTLMEMORYPOOL_GROW_FAST, GROW_SLOW = UTLMEMORYPOOL_GROW_SLOW }; CUtlMemoryPool(int blockSize, int numElements, int growMode = UTLMEMORYPOOL_GROW_FAST, const char *pszAllocOwner = NULL, int 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() { return m_BlocksAllocated; } int PeakCount() { return m_PeakAlloc; } protected: class CBlob { public: CBlob *m_pPrev, *m_pNext; 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. // Put m_BlocksAllocated in front of m_pHeadOfFreeList for better // packing on 64-bit where pointers are 8-byte aligned. int m_BlocksAllocated; // FIXME: Change m_ppMemBlob into a growable array? void *m_pHeadOfFreeList; int m_PeakAlloc; unsigned short m_nAlignment; unsigned short m_NumBlobs; const char *m_pszAllocOwner; // CBlob could be not a multiple of 4 bytes so stuff it at the end here to // keep us otherwise aligned CBlob m_BlobHead; static MemoryPoolReportFunc_t g_ReportFunc; }; //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- class CMemoryPoolMT : public CUtlMemoryPool { public: CMemoryPoolMT(int blockSize, int numElements, int growMode = UTLMEMORYPOOL_GROW_FAST, const char *pszAllocOwner = NULL) : CUtlMemoryPool(blockSize, numElements, growMode, pszAllocOwner) {} 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 CClassMemoryPool : public CUtlMemoryPool { public: CClassMemoryPool(int numElements, int growMode = GROW_FAST, int nAlignment = 0) : CUtlMemoryPool(sizeof(T), numElements, growMode, MEM_ALLOC_CLASSNAME(T), nAlignment) { #ifdef PLATFORM_64BITS COMPILE_TIME_ASSERT(sizeof(CUtlMemoryPool) == 64); #else COMPILE_TIME_ASSERT(sizeof(CUtlMemoryPool) == 48); #endif } T *Alloc(); T *AllocZero(); void Free(T *pMem); void Clear(); }; //----------------------------------------------------------------------------- // Specialized pool for aligned data management (e.g., Xbox cubemaps) //----------------------------------------------------------------------------- template class CAlignedMemPool { enum { BLOCK_SIZE = ALIGN_VALUE(ITEM_SIZE, ALIGNMENT) > 8 ? 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() { return m_Chunks.Count() * (CHUNK_SIZE / BLOCK_SIZE); } int NumAllocated() { return NumTotal() - m_nFree; } int NumFree() { return m_nFree; } int BytesTotal() { return NumTotal() * BLOCK_SIZE; } int BytesAllocated() { return NumAllocated() * BLOCK_SIZE; } int BytesFree() { 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; float m_TimeLastCompact; }; //----------------------------------------------------------------------------- // Pool variant using standard allocation //----------------------------------------------------------------------------- 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; while (m_AvailableObjects.PopItem(&p)) { delete p; } } T *GetObject(bool bCreateNewIfEmpty = bDefCreateNewIfEmpty) { T *p; 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 inline T *CClassMemoryPool::Alloc() { T *pRet; { MEM_ALLOC_CREDIT_(MEM_ALLOC_CLASSNAME(T)); pRet = (T *)CUtlMemoryPool::Alloc(); } if (pRet) { Construct(pRet); } return pRet; } template inline T *CClassMemoryPool::AllocZero() { T *pRet; { MEM_ALLOC_CREDIT_(MEM_ALLOC_CLASSNAME(T)); pRet = (T *)CUtlMemoryPool::AllocZero(); } if (pRet) { Construct(pRet); } return pRet; } template inline void CClassMemoryPool::Free(T *pMem) { if (pMem) { Destruct(pMem); } CUtlMemoryPool::Free(pMem); } template 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) { T *p = (T *)pCur->m_Data; T *pLimit = (T *)(pCur->m_Data + pCur->m_NumBytes); 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) { 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() { if (!m_pFirstFree) { 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) { // 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) { float time = Plat_FloatTime(); float compactTime = (m_nFree >= (CHUNK_SIZE / BLOCK_SIZE) * COMPACT_THRESHOLD * 4) ? 15.0 : 30.0; if (m_TimeLastCompact > time || m_TimeLastCompact + compactTime < Plat_FloatTime()) { Compact(); m_TimeLastCompact = time; } } } template inline int __cdecl CAlignedMemPool< ITEM_SIZE, ALIGNMENT, CHUNK_SIZE, CAllocator, COMPACT_THRESHOLD>::CompareChunk(void *const *ppLeft, void *const *ppRight) { return ((unsigned)*ppLeft) - ((unsigned)*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