9/10/2013

對於記憶體池(memory pool)的一些經驗

在我早期寫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 );     } };


沒有留言: