會員登入 新使用者?立即註冊

這是一個充滿夢想的地方!致力於創造簡單易用、媲美商業遊戲等級的玩家版3D線上遊戲創作平台!!!(研發中)

關閉
勇者們!一起創作心目中的遊戲世界吧!!! 我要留言

3D線上遊戲製作軟體 - 勇者大人對於記憶體池(memory pool)的一些經驗看法

分類:程式技巧
2008/12/31 18:08




在我早期寫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)使用方法簡介.
上一篇 下一篇
回應(14)
引用(0)

統計資料

  • 今日人數:21
  • 累計人數:87105
  • 發表文章:77
  • 相片數量:111
  • 回應數量:424

勇者居民觀察室

    Blog Look Score and Rank

     

    目前在線人數:

    who's online

我的訂閱

雅虎資訊 版權所有 © 2009 Yahoo! Taiwan All Rights Reserved
「本服務設有管理員」 服務條款 隱私權政策