![]()
在我早期寫windows程式時,我總是喜歡使用『動態配置記憶體』。
需要記憶體的時候,就跟作業系統new一下即可,不用時則delete歸還系統。
後來發現,常常這樣搞的話,
會讓作業系統容易發生『記憶體破碎』(memory fragmentation)等相關問題。
『記憶體破碎』問題如下:重複進行許多次不同容量記憶體區塊的配置(new)和釋放(delete)後,
會在堆積記憶體(heap memory)中留下許多零碎的、系統很難再去使用的小區塊,
這就是所謂的記憶體碎片問題。
此問題的發生取決於系統的分配機制。
碎片可能會增加配置時間,因而降低程式執行時的性能,
也可能會導致記憶體耗盡,造成程式可能發生當機。
『動態配置記憶體』缺點如下:[1]隨著使用次數的增加,而開始發生記憶體破碎問題。
[2]容易發生人為疏忽的記憶體洩漏(memory leak)問題。
[3]每次配置新記憶體區塊時,都會再附加額外的檔頭資訊,用多了則記憶體消耗更大。
[4]配置和釋放動態記憶體的速度可能會越來越慢。
[5]最後有可能會發生記憶體配置失敗的問題,而導致程式當機。
但是我當時還是不管此問題,繼續這麼做,我只想求方便!
因為我認為我是寫Windows平台的單機遊戲,能運用的記憶體很大。
直到這幾年我開始寫伺服器程式時,才發現若是繼續依照慣例來寫程式,
則很快的會讓伺服器程式發生記憶體配置失敗和效能低落等問題,
我才開始找更好的解決方案!
期間找很多資料研究如何設計伺服器程式,也問過工作上的程式朋友他的作法,
得到一個結論,要撰寫高效穩定的伺服器程式,務必得使用
『記憶體池』(memory pool)。
記憶體池,就是程式啟動時,預先配置一塊所需使用的超大記憶體,
然後,後續的所有記憶體配置請求都由此配給出去。
這樣就能完全解決上述的種種問題喔!
不過,使用新技巧,當然也會帶來新的問題。
傳統的記憶體池操作方式都是一樣擁有『配置和釋放』二種功能,
所以用久了,也是會有效能上的問題!
而根據我對效能的嚴格要求,我則有不同的作法。
我選擇以
空間換取時間,因為遊戲設計是很講究效能的!
我的作法程序如下:
[1]先配置一塊經過需求計算的超大記憶體。
[2]然後用一組『記憶體池操作類別』來做各種運用。
[3]直到程式結束時才釋放整塊記憶體池!
此重點在於第二點,一組
『記憶體池操作類別』。
例如:
要操作串列時,『記憶體池操作串列類別』會先從記憶體池被配置出來,
然後使用者就可進行運用,期間若是有需要釋放節點時,
則是由『記憶體池操作串列類別』自己
回收起來即可(不是做真正的釋放),
下次使用者要配置新節點時,則從已回收節點優先分配出去。
此種記憶體池和操作的設計優點如下:
[1]避免了記憶體破碎所衍生的問題。
[2]避免了配置和釋放記憶體所花費的效能問題,所以速度極佳。
[3]極適合伺服器應用程式的長期運作。
[4]記憶體間是緊密相鄰的,所以能提高系統cache命中率,進而提高軟體效能。
缺點是此大塊記憶體一直霸佔著,直到程式結束!
不過,對遊戲來說,這也不算缺點!
因為遊戲軟體本身就是一種很操作業系統的軟體!
在要求高效能的前提下,先將系統資源霸佔起來,
遊戲中才能有較好的效能。
所以,先計算好會使用到多少記憶體,對遊戲來說是必須的!
而且,其實遊戲中最佔記憶體的是屬於
圖形部份!
若要省記憶體,就請在此下功夫吧!
記憶體池操作串列類別 程式碼範例如下:
//==========================================================================================================================================================
template < class AVATAR >
struct MPNode2 : AVATAR
{
MPNode2< AVATAR > *pPrev;
MPNode2< AVATAR > *pNext;
};
template < class AVATAR >
struct AV_MemoryPoolList2 // 記憶池雙向串列類別
{
//------------------------------------------------------------
MPNode2< AVATAR > *pBufferList;
DWORD BufferListCount;
MPNode2< AVATAR > *pFreeList;
DWORD FreeListCount;
DWORD NodeUseCount;
//------------------------------------------------------------
inline void Create( DWORD object_max )
{
pFreeList = NULL;
FreeListCount = 0;
NodeUseCount = 0;
BufferListCount = object_max;
const DWORD object_size = sizeof( MPNode2< AVATAR > );
pBufferList = (MPNode2< AVATAR >*)AV_KernelMemoryPool::sMemoryAllocate( object_size * object_max );
}
//------------------------------------------------------------
// 由外部傳入pbuffer_list記憶體, 只將AV_MemoryPoolList2當作介面來操作
inline void CreateLink( DWORD object_max, MPNode2< AVATAR > *pbuffer_list )
{
pFreeList = NULL;
FreeListCount = 0;
NodeUseCount = 0;
BufferListCount = object_max;
pBufferList = pbuffer_list;
}
//------------------------------------------------------------
inline AVATAR* GetNode()
{
MPNode2< AVATAR > *node = NULL;
if( FreeListCount > 0 )
{
node = pFreeList;
pFreeList = pFreeList->pNext;
--FreeListCount;
}
else
{
if( BufferListCount > 0 )
{
node = pBufferList;
++pBufferList;
--BufferListCount;
}
else
{
Debug::SBox( "AV_MemoryPoolList2< %s >::GetNode allocate a new node.", typeid( AVATAR ).name() );
node = (MPNode2< AVATAR >*)AV_KernelMemoryPool::sMemoryAllocate( sizeof( MPNode2< AVATAR > ) );
}
}
node->pPrev = NULL;
node->pNext = NULL;
++NodeUseCount;
return node;
}
//------------------------------------------------------------
inline BOOL CheckSpaceFull()
{
if( FreeListCount == 0 && BufferListCount == 0 )
return TRUE;
return FALSE;
}
//------------------------------------------------------------
inline void ReleaseNode( AVATAR *&release_node )
{
ZeroMemory( release_node, sizeof( MPNode2< AVATAR > ) );
if( FreeListCount > 0 )
((MPNode2< AVATAR >*)release_node)->pNext = pFreeList;
pFreeList = (MPNode2< AVATAR >*)release_node;
release_node = NULL;
++FreeListCount;
--NodeUseCount;
}
//------------------------------------------------------------
inline void ReleaseNodeNoClean( AVATAR *&release_node )
{
((MPNode2< AVATAR >*)release_node)->pPrev = NULL;
((MPNode2< AVATAR >*)release_node)->pNext = NULL;
if( FreeListCount > 0 )
((MPNode2< AVATAR >*)release_node)->pNext = pFreeList;
pFreeList = (MPNode2< AVATAR >*)release_node;
release_node = NULL;
++FreeListCount;
--NodeUseCount;
}
//------------------------------------------------------------
};
//==========================================================================================================================================================
template < class AVATAR > // 記憶池雙向串列類別( 有包含pHeadNode連結 )
struct AV_MemoryPool2HeadList : AV_MemoryPoolList2< AVATAR >
{
//------------------------------------------------------------
MPNode2< AVATAR > *pHeadNode;
MPNode2< AVATAR > *pTailNode;
DWORD LinkNodeCount;
//------------------------------------------------------------
inline void Create( DWORD object_max )
{
AV_MemoryPoolList2< AVATAR >::Create( object_max );
pHeadNode = NULL;
pTailNode = NULL;
LinkNodeCount = 0;
}
//------------------------------------------------------------
inline void CreateLink( DWORD object_max, MPNode2< AVATAR > *pbuffer_list )
{
AV_MemoryPoolList2< AVATAR >::CreateLink( object_max, pbuffer_list );
pHeadNode = NULL;
pTailNode = NULL;
LinkNodeCount = 0;
}
//------------------------------------------------------------
inline void Link( AVATAR *link_node )
{
MPNode2< AVATAR > *node = (MPNode2< AVATAR >*)link_node;
node->pPrev = NULL;
node->pNext = NULL;
if( !pHeadNode )
{
pTailNode = pHeadNode = node;
}
else
{
pTailNode->pNext = node;
node->pPrev = pTailNode;
pTailNode = node;
}
++LinkNodeCount;
}
inline void Unlink( AVATAR *unlink_node )
{
MPNode2< AVATAR > *node = (MPNode2< AVATAR >*)unlink_node;
if( node->pPrev )
{
if( node->pNext ) // middle node
{
node->pPrev->pNext = node->pNext;
node->pNext->pPrev = node->pPrev;
}
else // tail node
{
if( pTailNode == node )
{
pTailNode = pTailNode->pPrev;
pTailNode->pNext = NULL;
}
else
Debug::SBox( "AV_MemoryPool2HeadList< %s >::Unlink if( pTailNode != node )", typeid( AVATAR ).name() );
}
}
else // head node
{
if( pHeadNode == node )
{
if( pHeadNode->pNext )
{
pHeadNode = pHeadNode->pNext;
pHeadNode->pPrev = NULL;
}
else
{
pHeadNode = NULL;
pTailNode = NULL;
}
}
else
Debug::SBox( "AV_MemoryPool2HeadList< %s >::Unlink if( pHeadNode != node ) it will lose the node.", typeid( AVATAR ).name() );
}
node->pPrev = NULL;
node->pNext = NULL;
--LinkNodeCount;
}
};
//==========================================================================================================================================================
template < class AVATAR, DWORD ARRAY_MAX > // 記憶池雙向串列類別(用陣列當pBufferList的緩衝區)
struct AV_MemoryPool2HeadArrayList : AV_MemoryPool2HeadList< AVATAR >
{
//------------------------------------------------------------
MPNode2< AVATAR > NodeBuffer[ ARRAY_MAX ];
//------------------------------------------------------------
inline void Create()
{
ZeroMemory( NodeBuffer, sizeof( NodeBuffer ) );
AV_MemoryPool2HeadList< AVATAR >::CreateLink( ARRAY_MAX, NodeBuffer );
}
};
若想看全部完整的程式碼,請於此下載。
程式碼下載:我的完整MemoryPool程式碼下載連結enjoy it !
延伸閱讀:
3D線上遊戲製作軟體 - 勇者大人的記憶體池(memory pool)使用方法簡介.