色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

詳解內存池技術的原理與實現

Linux內核補給站 ? 來源:Linux內核補給站 ? 作者:Linux內核補給站 ? 2022-05-20 08:58 ? 次閱讀

序言

最近在網上看到了幾篇篇講述內存池技術的文章,有一篇是有IBM中國研發中心的人寫的,寫的不錯~~文章地址在本篇blog最后。原文的講述比我的要清晰很多,我在這只是把我的一些理解和遇到的一些問題和大家分享一下~~

一、為什么要使用內存池技術呢

主要有兩個原因:1、減少new、delete次數,減少運行時間;2、避免內存碎片。

1、效率

c語言中使用malloc/free來分配內存,c++中使用new/delete來分配內存,他們的內存申請與釋放都是與操作系統進行交互的。具體的內容在嚴蔚敏數據結構的第八章有相關講述,主要就是系統要維護一個內存鏈表,當有一個內存申請過來時,根據相應的分配算法在鏈表中找個一個合適的內存分配給它。這些算法有的是分配最先找到的不小于申請內存的內存塊,有的是分配最大的內存塊,有的是分配最接近申請內存大小的內存塊。分配的內存塊可能會大于所申請的內存大小,這樣還有進行切割,將剩余的內存插入到空閑鏈表中。當釋放的時候,系統可能要對內存進行整理,判斷free的內存塊的前后是否有空閑,若有的話還要進行合并。此外,new/delete還要考慮多線程的情況??傊痪湓挘{用庫中的內存分配函數,十分的耗時~~

2、內存碎片

什么是內存碎片內,從字面意思就很好理解了,就是內存不再是一整塊的了,而是碎了。因為連續的這種new/delete操作,一大塊內存肯能就被分割成小的內存分配出去了,這些小的內存都是不連續的。當你再去分配大的連續內存的時候,盡管剩余內存的總和可能大于所要分配的內存大小,但系統就找不到連續的內存了,所以導致分配錯誤。malloc的時候會導致返回NULL,而new的時候再vc6.0中返回NULL,vs2003以上則是拋出異常。

二、原理

要解決上述兩個問題,最好的方法就是內存池技術。具體方法就是大小固定、提前申請、重復利用。

因為內存的申請和釋放是很低效的,所以我們只在開始時申請一塊大的內存(在該塊內存不夠用時在二次分配),然后每次需要時都從這塊內存中取出,并標記下這塊內存被用了,釋放時標記此內存被釋放了。釋放時,并不真的把內存釋放給操作系統,只要在一大塊內存都空閑的時候,才釋放給操作系統。這樣,就減少了new/delete的操作次數,從而提高了效率。

在調用內存分配函數的時候,大部分時間所分配的內存大小都是一定的,所以可以采用每次都分配固定大小的內存塊,這樣就避免了內存碎片產生的可能。

三、具體實現

我所采用的內存池的構造方法完全是按照文章1所介紹的方法,內存池的結構圖如下:

poYBAGKGOTmAYqZTAAA5idptWh8137.gif?source=d16d100b

?

如圖所示MemoryPool是一個內存池類,其中pBlock是一個指向了一個內存塊的指針,nUintSzie是分配單元的大小,nInitSize是第一次分配時向系統申請的內存的大小,nGrouSize是后面每次向系統申請的內存的大小。

MemoryBloc代表一個內存塊單元,它有兩部分構成,一部分時MemoryBlock類的大小,另一部分則是實際的內存部分。一個MemoryBlock的內存是在重載的new操作符中分配的,如下所示:

void* MemoryBlock::operator new(size_t, int nUnitSize,int nUnitAmount )
{
    return ::operator new( sizeof(MemoryBlock) + nUnitSize * nUnitAmount );
}

MemoryBlock內中,nSize代碼該內存塊的大?。ㄏ到y分配內存大小-MemoryBlock類的大小),nFree是空閑內存單元的個數,nFirst代表的是下一個要分配的內存單元的序號。aData是用來記錄待分配內存的位置的。因為要分配的內存是在new中一起向系統申請的,并沒有一個指針指向這塊內存的位置,但它的位置就在MemoryBlock這個類的地址開始的,所以可以用MemoryBlock的最后一個成員的位置來表示待分配內存的位置。

帶分配內存中,是以nUnitSize為單位的,一個內存單元的頭兩個字節都記錄了下一個要分配的內存單元的序號,序號從0開始。這樣實際也就構成了一個數組鏈表。由MemoryBlock的構造函數來完成這個鏈表的初始化工作:

MemoryBlock::MemoryBlock( int nUnitSize,int nUnitAmount )
    :   nSize   (nUnitAmount * nUnitSize),
        nFree   (nUnitAmount - 1),  //構造的時候,就已將第一個單元分配出去了,所以減一
        nFirst  (1),                //同上
        pNext   (NULL)
{
    //初始化數組鏈表,將每個分配單元的下一個分配單元的序號寫在當前單元的前兩個字節中
    char* pData = aData;
    //最后一個位置不用寫入
    for( int i = 1; i < nSize - 1; i++)
    {
        (*(USHORT*)pData) = i;
        pData += nUnitSize;
    }
}

在MemoryPool的Alloc()中,遍歷block鏈表,找到nFree大于0的block,從其上分配內存單元。然后將nFree減一,修改nFirst的值。

在MemoryPool的Free(pFree)函數中,根據pFree的值,找到它所在的內存塊,然后將它的序號作為nFirst的值(因為它絕對是空閑的),在pFree的頭兩個字節中寫入原來nFirst的值。然后要判斷,該block是否全部為free,方法是檢測nFree * nUnitSize == nSize。若是,則向系統釋放內存,若不是,則將該block放到鏈表的頭部,因為該block上一定含有空隙的內存單元,這樣可以減少分配時遍歷鏈表所消耗的時間。

四、使用

內存池一般都是作為一個類的靜態成員,或者全局變量。使用時,重載new操作符,使其到MemoryPool中去分配內存,而不是向系統申請。這樣,一個類的所以對象都在一個內存池中開辟空間。


void CTest::operator delete( void* pTest )
{   
    Pool.Free(pTest);   
}
 
 
void* CTest::operator new(size_t)
{
    return (CTest*)Pool.Alloc();
}

五、代碼

MemoryPool.h

#include 
#include 
 
#define  MEMPOOL_ALIGNMENT 8            //對齊長度
//內存塊,每個內存塊管理一大塊內存,包括許多分配單元
class MemoryBlock
{
public:
                    MemoryBlock (int nUnitSize,int nUnitAmount);
                    ~MemoryBlock(){};
    static void*    operator new    (size_t,int nUnitSize,int nUnitAmount);
    static void     operator delete (void* ,int nUnitSize,int nUnitAmount){};
    static void     operator delete (void* pBlock);
 
    int             nSize;              //該內存塊的大小,以字節為單位
    int             nFree;              //該內存塊還有多少可分配的單元
    int             nFirst;             //當前可用單元的序號,從0開始
    MemoryBlock*    pNext;              //指向下一個內存塊
    char            aData[1];           //用于標記分配單元開始的位置,分配單元從aData的位置開始
     
};
 
class MemoryPool
{
public:
                    MemoryPool (int _nUnitSize,
                                int _nGrowSize = 1024,
                                int _nInitSzie = 256);
                    ~MemoryPool();
    void*           Alloc();
    void            Free(void* pFree);
 
private:
    int             nInitSize;          //初始大小
    int             nGrowSize;          //增長大小
    int             nUnitSize;          //分配單元大小
    MemoryBlock*    pBlock;             //內存塊鏈表
};

MemoryPool.cpp

#include "MemoryPool.h"
 
MemoryBlock::MemoryBlock( int nUnitSize,int nUnitAmount )
    :   nSize   (nUnitAmount * nUnitSize),
        nFree   (nUnitAmount - 1),  //構造的時候,就已將第一個單元分配出去了,所以減一
        nFirst  (1),                //同上
        pNext   (NULL)
{
    //初始化數組鏈表,將每個分配單元的下一個分配單元的序號寫在當前單元的前兩個字節中
    char* pData = aData;
    //最后一個位置不用寫入
    for( int i = 1; i < nSize - 1; i++)
    {
        (*(USHORT*)pData) = i;
        pData += nUnitSize;
    }
}
 
void* MemoryBlock::operator new(size_t, int nUnitSize,int nUnitAmount )
{
    return ::operator new( sizeof(MemoryBlock) + nUnitSize * nUnitAmount );
}
 
void MemoryBlock::operator delete( void* pBlock)
{
    ::operator delete(pBlock);
}
 
MemoryPool::MemoryPool( int _nUnitSize, int _nGrowSize /*= 1024*/, int _nInitSzie /*= 256*/ )
{
    nInitSize = _nInitSzie;
    nGrowSize = _nGrowSize;
    pBlock = NULL;
    if(_nUnitSize > 4)
        nUnitSize = (_nUnitSize + (MEMPOOL_ALIGNMENT - 1)) & ~(MEMPOOL_ALIGNMENT - 1);
    else if( _nUnitSize < 2)
        nUnitSize = 2;
    else
        nUnitSize = 4;
}
 
MemoryPool::~MemoryPool()
{
    MemoryBlock* pMyBlock = pBlock;
    while( pMyBlock != NULL)
    {
        pMyBlock = pMyBlock->pNext;
        delete(pMyBlock);
    }
}
 
void* MemoryPool::Alloc()
{
    if( NULL == pBlock)
    {
        //首次生成MemoryBlock,new帶參數,new了一個MemoryBlock類
        pBlock = (MemoryBlock*)new(nUnitSize,nInitSize) MemoryBlock(nUnitSize,nUnitSize);
        return (void*)pBlock->aData;
    }
 
    //找到符合條件的內存塊
    MemoryBlock* pMyBlock = pBlock;
    while( pMyBlock != NULL && 0 == pMyBlock->nFree )
        pMyBlock = pMyBlock->pNext;
 
    if( pMyBlock != NULL)
    {
        //找到了,進行分配
        char* pFree = pMyBlock->aData + pMyBlock->nFirst * nUnitSize;
        pMyBlock->nFirst = *((USHORT*)pFree);
        pMyBlock->nFree--;
 
        return (void*)pFree;
    }
    else
    {
        //沒有找到,說明原來的內存塊都滿了,要再次分配
 
        if( 0 == nGrowSize)
            return NULL;
         
        pMyBlock = (MemoryBlock*)new(nUnitSize,nGrowSize) MemoryBlock(nUnitSize,nGrowSize);
 
        if( NULL == pMyBlock)
            return NULL;
 
        //進行一次插入操作
        pMyBlock->pNext = pBlock;
        pBlock = pMyBlock;
 
        return (void*)pMyBlock->aData;
    }
}
 
void MemoryPool::Free( void* pFree )
{
    //找到p所在的內存塊
    MemoryBlock* pMyBlock = pBlock;
    MemoryBlock* PreBlock = NULL;
    while ( pMyBlock != NULL && ( pBlock->aData > pFree || pMyBlock->aData + pMyBlock->nSize))
    {
        PreBlock = pMyBlock;
        pMyBlock = pMyBlock->pNext;
    }
 
    if( NULL != pMyBlock )      //該內存在本內存池中pMyBlock所指向的內存塊中
    {      
        //Step1 修改數組鏈表
        *((USHORT*)pFree) = pMyBlock->nFirst;
        pMyBlock->nFirst  = (USHORT)((ULONG)pFree - (ULONG)pMyBlock->aData) / nUnitSize;
        pMyBlock->nFree++;
 
        //Step2 判斷是否需要向OS釋放內存
        if( pMyBlock->nSize == pMyBlock->nFree * nUnitSize )
        {
            //在鏈表中刪除該block
             
            delete(pMyBlock);
        }
        else
        {
            //將該block插入到隊首
            PreBlock = pMyBlock->pNext;
            pMyBlock->pNext = pBlock;
            pBlock = pMyBlock;
        }
    }
}

CTest.cpp

#include 
#include "MemoryPool.h"
 
class CTest
{
public:
                CTest(){data1 = data2 = 0;};
                ~CTest(){};
    void*       operator new (size_t);
    void        operator delete(void* pTest);
public:
 
    static      MemoryPool Pool;
    int         data1;
    int         data2;
};
 
void CTest::operator delete( void* pTest )
{  
    Pool.Free(pTest);  
}
 
 
void* CTest::operator new(size_t)
{
    return (CTest*)Pool.Alloc();
}
 
MemoryPool CTest::Pool(sizeof(CTest));
 
int main()
{
    CTest* pTest = new CTest;
    printf("%d",pTest->data2);
}

六、問題

在編寫代碼時,遇到了一些小問題,現與大家分享如下:

重載new操作符時,編譯器要求是第一個參數必須是size_t,返回值必須是void*;free的第一個參數必須是void*.

一般要在類的成員中重載new操作符,而不要重載全局的new操作符。

一個類中要是重載了一個new操作符,一定要有一個相應類型的delete操作符,可以什么都不干,但必須有,否則在構造函數失敗時,找不到對應的delete函數。

例如:

static void*    operator new    (size_t,int nUnitSize,int nUnitAmount);
    static void     operator delete (void* ,int nUnitSize,int nUnitAmount){};

4. 帶參數的new操作符


pBlock = (MemoryBlock*)new(nUnitSize,nInitSize) MemoryBlock(nUnitSize,nUnitSize);

第一個nUnitSize nInitSize是new操作符的參數,該new操作符是new了一個MemoryBlock對象,在new返回的地址上構造MemoryBlock的對象。

5. 如果在類的內部不能進行靜態成員的定義的話,可以只在內部進行聲明,在外部定義:

MemoryPool CTest::Pool(sizeof(CTest));

?

審核編輯:湯梓紅


聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • Linux
    +關注

    關注

    87

    文章

    11296

    瀏覽量

    209359
  • 內存
    +關注

    關注

    8

    文章

    3020

    瀏覽量

    74012
  • C語言
    +關注

    關注

    180

    文章

    7604

    瀏覽量

    136713
收藏 人收藏

    評論

    相關推薦

    C++內存的設計與實現

    內存技術中的一種形式。通常我們在編寫程序的時候回使用 new delete 這些關鍵字來向操作系統申請內存,而這樣造成的后果就是每次
    發表于 09-23 10:22 ?920次閱讀

    內存可以調節內存的大小嗎

    嵌入式–內存直接上代碼,自己體會。嵌入式設備,一般keil提供的堆很小,一般都不使用。使用內存,自己可以調節內存大小。頭文件 mallo
    發表于 12-17 07:00

    內存的概念和實現原理概述

    { //一:內存的概念和實現原理概述//malloc:內存浪費,頻繁分配小塊內存,則浪費更加顯得明顯//“
    發表于 12-17 06:44

    線程是如何實現

    線程的概念是什么?線程是如何實現的?
    發表于 02-28 06:20

    關于RT-Thread內存管理的內存簡析

    這篇文章繼續介紹 RT-Thread 內存管理剩下的部分——內存。為何引入內存內存堆雖然方
    發表于 04-06 17:02

    RT-Thread內存管理之內存實現分析

    了解RT-thread 的內存實現及管理。以RTT最新穩定版本4.1.0的內核為藍本。\\include\\rtdef.h/**Base structure of Memory pool
    發表于 10-17 15:06

    Linux 內存源碼淺析

    內存(Memery Pool)技術是在真正使用內存之前,先申請分配一定數量的、大小相等(一般情況下)的內存塊留作備用。當有
    發表于 04-02 14:32 ?256次閱讀

    基于CXL技術的大內存化方案解析

    如果 FaceBoo k平臺創建的TPP協議是正確的,那么它將有一個不同的內存分頁系統,可以更好地解決由于在服務器主板之外有大量內存而帶來的稍高的延遲。
    發表于 10-20 11:46 ?2097次閱讀

    什么是內存

    1什么是內存 1.1技術 所謂“技術”,就是程序先向系統申請過量的資源,然后自己管理,
    的頭像 發表于 11-08 16:26 ?901次閱讀
    什么是<b class='flag-5'>內存</b><b class='flag-5'>池</b>

    高并發內存項目實現

    本項目實現了一個高并發內存,參考了Google的開源項目tcmalloc實現的簡易版;其功能就是實現高效的多線程
    的頭像 發表于 11-09 11:16 ?715次閱讀
    高并發<b class='flag-5'>內存</b><b class='flag-5'>池</b>項目<b class='flag-5'>實現</b>

    了解連接、線程、內存、異步請求

    技術 技術能夠減少資源對象的創建次數,提?程序的響應性能,特別是在?并發下這種提?更加明顯。使用
    的頭像 發表于 11-09 14:44 ?1313次閱讀
    了解連接<b class='flag-5'>池</b>、線程<b class='flag-5'>池</b>、<b class='flag-5'>內存</b><b class='flag-5'>池</b>、異步請求<b class='flag-5'>池</b>

    如何實現一個高性能內存

    寫在前面 本文的內存代碼是改編自Nginx的內存源碼,思路幾乎一樣。由于Nginx源碼的變量命名我不喜歡,又沒有注釋,看得我很難受。想自己寫一版容易理解的代碼。 應用場景 寫
    的頭像 發表于 11-10 11:11 ?660次閱讀
    如何<b class='flag-5'>實現</b>一個高性能<b class='flag-5'>內存</b><b class='flag-5'>池</b>

    內存的使用場景

    為什么要用內存 為什么要用內存?首先,在7 * 24h的服務器中如果不使用內存,而使用ma
    的頭像 發表于 11-10 17:19 ?704次閱讀
    <b class='flag-5'>內存</b><b class='flag-5'>池</b>的使用場景

    nginx內存源碼設計

    造輪子內存原因引入 作為C/C++程序員, 相較JAVA程序員的一個重大特征是我們可以直接訪問內存, 自己管理內存, 這個可以說是我們的特色, 也是我們的苦楚了. java可以有虛擬
    的頭像 發表于 11-13 11:51 ?689次閱讀
    nginx<b class='flag-5'>內存</b><b class='flag-5'>池</b>源碼設計

    內存主要解決的問題

    內存的定義 1.技術 是在計算機技術中經常使用的一種設計模式,其內涵在于:將程序中需要
    的頭像 發表于 11-13 15:23 ?701次閱讀
    <b class='flag-5'>內存</b><b class='flag-5'>池</b>主要解決的問題
    主站蜘蛛池模板: 精品国产在线亚洲欧美| 国模大胆一区二区三区 | 日韩欧美1区| 十大禁止安装的黄台有风险| 翁止熄痒禁伦短文合集免费视频| 亚洲AV久久婷婷蜜臀无码不卡| 亚洲综合日韩在线2019| 中文字幕午夜乱理片| hdsex老太婆70| 国产精品人妻系列21P| 九九精品视频在线播放| 男女性杂交内射妇女BBWXZ| 日日噜噜夜夜狠狠视频| 亚洲精品无码国产爽快A片百度| 在线观看亚洲 日韩 国产| 啊轻点灬大JI巴又大又粗| 国产色婷婷亚洲99麻豆| 快播电影频道| 色爱区综合激情五月综合激情| 亚洲黄色片免费看| 2021全国精品卡一卡二| 处88XXX| 护士美女照片| 欧美精品华人在线| 亚洲第一成年人网站| 97国产蝌蚪视频在线观看| 国产跪地吃黄金喝圣水合集| 久久国产精品永久免费网站| 热久久免费频精品99热| 亚洲精品高清视频| QVOD在线播放| 黄色小说在线| 日本一在线中文字幕| 一区二区中文字幕在线观看| 处88XXX| 美女被艹网站| 亚洲 欧美 国产 综合不卡| 99精品视频免费观看| 国产自拍视频在线一区| 欧美在线亚洲综合国产人| 亚洲宅男天堂a在线|