source-engine/gcsdk/steamextra/tier1/tsmempool.cpp

256 lines
7.9 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// The copyright to the contents herein is the property of Valve, L.L.C.
// The contents may be used and/or copied only with the written permission of
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//
// Purpose:
//=============================================================================
//#include "pch_vstdlib.h"
#include "stdafx.h"
#include "tier0/tslist.h"
#include "tier0/t0constants.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static const uint k_cubBytesAllocatedToConsiderFreeingMemory = 5 * k_nMegabyte;
static const int k_cBlocksAllocatedToConsiderFreeingMemory = 10;
typedef TSLNodeBase_t FreeListItem_t;
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CThreadSafeMemoryPool::CThreadSafeMemoryPool( int blockSize, int numElements, int growMode )
{
m_ptslistFreeBlocks = new CTSListBase;
// round up to the nearest 8-byte boundary
if ( blockSize % TSLIST_NODE_ALIGNMENT != 0 )
{
blockSize += TSLIST_NODE_ALIGNMENT - (blockSize % TSLIST_NODE_ALIGNMENT);
}
Assert( blockSize % TSLIST_NODE_ALIGNMENT == 0 );
Assert( blockSize > sizeof(FreeListItem_t) );
m_nGrowMode = growMode;
m_cubBlockSize = blockSize;
m_nGrowSize = numElements;
m_cubAllocated = 0;
}
//-----------------------------------------------------------------------------
// Purpose: Frees the memory contained in the mempool
//-----------------------------------------------------------------------------
CThreadSafeMemoryPool::~CThreadSafeMemoryPool()
{
AUTO_LOCK_SPIN_WRITE( m_threadRWLock );
FOR_EACH_VEC( m_vecBlockSets, i )
{
_aligned_free( m_vecBlockSets[i].m_pubBlockSet );
}
delete m_ptslistFreeBlocks;
}
//-----------------------------------------------------------------------------
// Purpose: Frees everything
//-----------------------------------------------------------------------------
void CThreadSafeMemoryPool::Clear()
{
AUTO_LOCK_SPIN_WRITE( m_threadRWLock );
ClearNoLock();
}
//-----------------------------------------------------------------------------
// Purpose: Frees everything
//-----------------------------------------------------------------------------
void CThreadSafeMemoryPool::ClearNoLock()
{
FOR_EACH_VEC( m_vecBlockSets, i )
{
_aligned_free( m_vecBlockSets[i].m_pubBlockSet );
}
m_ptslistFreeBlocks->Detach();
m_cubAllocated = 0;
m_cBlocksInUse = 0;
m_vecBlockSets.RemoveAll();
}
//-----------------------------------------------------------------------------
// Purpose: Allocates a single block of memory from the pool.
//-----------------------------------------------------------------------------
void *CThreadSafeMemoryPool::Alloc()
{
return Alloc( m_cubBlockSize );
}
//-----------------------------------------------------------------------------
// Purpose: Allocates a single block of memory from the pool.
//-----------------------------------------------------------------------------
void *CThreadSafeMemoryPool::Alloc( unsigned int amount )
{
// loop attempting to get memory
// there appears to be a case where memory corruption can get this into an infinite loop
// normally 1 or 2 attempts are necessary to get a block, so if we hit 1000 we know something is wrong
int cAttempts = 1000;
while ( --cAttempts )
{
// pull first from the free list
m_threadRWLock.LockForRead();
FreeListItem_t *pFreeListItem = m_ptslistFreeBlocks->Pop();
if ( pFreeListItem )
{
m_threadRWLock.UnlockRead();
m_cBlocksInUse++;
return (void *)pFreeListItem;
}
m_threadRWLock.UnlockRead();
// no free items, add a new block
// switch from a read lock to a write lock
AUTO_LOCK_SPIN_WRITE( m_threadRWLock );
// another thread may have allocated memory; try the free list again if so
if ( m_ptslistFreeBlocks->Count() > 0 )
continue;
size_t cubBlob = m_nGrowSize * m_cubBlockSize;
if ( m_nGrowMode == GROW_FAST )
{
cubBlob *= (m_vecBlockSets.Count() + 1);
}
// don't grow if we're told not to
if ( m_nGrowMode == GROW_NONE && m_vecBlockSets.Count() == 1 )
return NULL;
// allocate, but we can fail
byte *pBlobBase = (byte *)MemAlloc_AllocAligned( cubBlob, TSLIST_NODE_ALIGNMENT /*, (m_nGrowMode == GROW_TIL_YOU_CANT)*/ );
if ( !pBlobBase )
return NULL;
byte *pBlobEnd = pBlobBase + cubBlob;
// add all the items to the pool
for ( byte *pBlob = pBlobBase; pBlob < pBlobEnd; pBlob += m_cubBlockSize )
{
m_ptslistFreeBlocks->Push( (FreeListItem_t *)pBlob );
}
m_cubAllocated += cubBlob;
BlockSet_t blockSet = { pBlobBase, cubBlob };
m_vecBlockSets.AddToTail( blockSet );
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Frees a block of memory
//-----------------------------------------------------------------------------
void CThreadSafeMemoryPool::Free( void *pMem )
{
Free( pMem, m_cubBlockSize );
}
//-----------------------------------------------------------------------------
// Purpose: Frees a block of memory
//-----------------------------------------------------------------------------
void CThreadSafeMemoryPool::Free( void *pMem, int cubAlloc )
{
m_threadRWLock.LockForRead();
// push the item back onto the free list
m_ptslistFreeBlocks->Push( (FreeListItem_t *)pMem );
m_cBlocksInUse--;
m_threadRWLock.UnlockRead();
// if we're completely free, and have too much memory allocated, free some
if ( m_cBlocksInUse == 0
&& m_cubAllocated >= k_cubBytesAllocatedToConsiderFreeingMemory
&& m_vecBlockSets.Count() >= k_cBlocksAllocatedToConsiderFreeingMemory )
{
AUTO_LOCK_SPIN_WRITE( m_threadRWLock );
// check again nothing is in use
if ( m_cBlocksInUse == 0 )
{
// free all the blocks
ClearNoLock();
}
}
}
//-----------------------------------------------------------------------------
// Purpose: display
//-----------------------------------------------------------------------------
void CThreadSafeMemoryPool::PrintStats()
{
AUTO_LOCK_SPIN_WRITE( m_threadRWLock );
int cBlocksInUse = m_cBlocksInUse;
Msg( "Block size: %-11s Alloc'd: %8d Num blobs: %5d (%s)\n", Q_pretifymem( m_cubBlockSize, 2, true ),
cBlocksInUse, m_vecBlockSets.Count(), Q_pretifymem( m_cubAllocated, 2, true ) );
}
//-----------------------------------------------------------------------------
// Purpose: data accessor
//-----------------------------------------------------------------------------
size_t CThreadSafeMemoryPool::CubTotalSize()
{
return m_cubAllocated;
}
//-----------------------------------------------------------------------------
// Purpose: data accessor
//-----------------------------------------------------------------------------
size_t CThreadSafeMemoryPool::CubSizeInUse()
{
return m_cBlocksInUse * m_cubBlockSize;
}
//-----------------------------------------------------------------------------
// Purpose: data accessor
//-----------------------------------------------------------------------------
int CThreadSafeMemoryPool::Count()
{
return m_cBlocksInUse;
}
#ifdef DBGFLAG_VALIDATE
//-----------------------------------------------------------------------------
// Purpose: Run a global validation pass on all of our data structures and memory
// allocations.
// Input: validator - Our global validator object
// pchName - Our name (typically a member var in our container)
//-----------------------------------------------------------------------------
void CThreadSafeMemoryPool::Validate( CValidator &validator, const char *pchName )
{
AUTO_LOCK_SPIN_WRITE( m_threadRWLock );
VALIDATE_SCOPE();
FOR_EACH_VEC( m_vecBlockSets, i )
{
validator.ClaimMemory( MemAlloc_Unalign( m_vecBlockSets[i].m_pubBlockSet ) );
}
ValidateObj( m_vecBlockSets );
validator.ClaimMemory( MemAlloc_Unalign( m_ptslistFreeBlocks ) );
}
#endif // DBGFLAG_VALIDATE