STL空間配置器

一、STL爲什麼需要空間配置器?(內存有關)
1、解決內存碎片(外碎片)
外碎片:小塊內存頻繁分配導致小塊空間不連續,分配不出大塊的空間。
內碎片:比如需要3個字節,向上對齊取整new了8個字節,那麼這5個未使用的爲內碎片。
解決外碎片會引入內碎片
比如list、map、set、hashtable等push(new)或pop(delete)操作都有小空間。
2、提高效率:頻繁分配空間,每一次都有系統調用

二、STL空間配置器框架:
一級空間配置器和二級空間配置器

這裏寫圖片描述

1、一級空間空間配置器是對malloc、free和realloc的封裝
(1)allocate調用malloc
(2)deallocate調用free
(3)模擬C++的set_new_hander()
set_new_hander():分配內存失敗處理函數句柄。在內存空間分配失敗的情況下,如果設置了該句柄,則不會直接拋異常,而是執行這個處理函數,因爲__malloc_alloc_oom_handler是全局的,可在外釋放一些空間,那麼有可能下次循環就會分配成功。不斷的嘗試分配,直至分配成功;否則(未設置該句柄),拋出異常。
STL空間配置器使用的是malloc分配空間,所以設計的是set_malloc_hander()。

2、二級空間配置器
(1)維護16個自由串列(free_list)
(2)需求大於128,直接調用一級空間配置器
(3)需求不大於128,檢查對應的free_list,如果free_list有可用的直接用,否則向上取整8、16、24…(允許小小的浪費)。然後調用refill(),爲free_list重新填充空間。
設計:
free_list結構:
這裏寫圖片描述

chunk_alloc():內存池給free_list提供內存塊,由chunk_alloc()完成。
(1)當內存池剩餘字節足夠爲需求分配,直接分配,並更新內存池水位線;
(2)如果內存池已經不夠分配總需求,但是至少足夠分給一個對象,那麼能滿足幾個對象的大小就給幾個;
(3)如果內存池已經不夠一個對象的大小了,先把剩餘的插入到自由鏈表合適的位置;
當內存池爲空,申請兩倍+的空間,一部分給free_list,一部分存在內存池。
如果大量申請導致整個系統的堆空間都不夠了,malloc失敗。那麼chunk_alloc在free_list尋找可用空間塊,若找到則交出,否則調用一級空間配置器,因爲一級空間配置器也許有set_malloc_handler機制,有可能釋放其他空間、分配成功,否則拋出異常。

char* chunkAlloc(size_t size,int& nobjs)
{
    char* result ;
    size_t bytesNeed=size*nobjs;//需求字節數
    size_t bytesLeft=_endFree-_startFree;//內存池剩餘字節數
    if(bytesLeft>=bytesNeed)//剩餘足夠滿足總需求
    {
        result=_startFree;
        _startFree+=bytesNeed;//更新內存池水位線
        return result;
    }
    else if(bytesLeft>=size)//剩餘不夠滿足總需求,但可滿足至少一個對象的大小
    {
        result =_startFree;
        nobjs=bytesLeft/size;//先分配bytesLeft/size個對象
        _startFree+=nobjs*size;//更新內存池水位線
    }
    else//內存池剩餘不足一個對象的需求
    {
        if(bytesLeft>0)//剩餘一些零頭,先給free_list
        {
            size_t index=FREELIST_INDEX(bytesLeft);
            ((Obj*)_startFree)->_freeListLink=_freeList[index];
            _freeList[index]=(Obj*)_startFree;
            _startFree=NULL;
        }
        //從系統堆分配兩倍+
        size_t bytesToGet=2*bytesNeed+ROUND_UP(_heapSize>>4);
        _startFree=(char*)malloc(bytesToGet);

        //系統堆空間不足,分配失敗
        if(NULL==_startFree)
        {
            //先去自由鏈表找
            for(int i=size;i<_MAX_BYTES;i+=_ALIGN)
            {
                Obj* head=_freeList[FREELIST_INDEX(size)];
                if(head)
                {
                    _startFree=(char*)head;
                    head=head->_freeListLink;
                    _endFree=_startFree+i;
                    return chunkAlloc(size,nobjs);
                }
            }
            //自由鏈表無可用塊,調一級空間配置器
            _startFree=(char*)MallocAlloc::Allocate(bytesToGet);
        }
    }
}
static void* Allocate(size_t n)
{
    //大於_MAX_BYTES_,調一級空間配置器的Allocate
    if(n>(size_t)_MAX_BYTES_)
    {
        return(MallocAlloc::Allocate(n));
    }
    //否則在自由鏈表中找適當的
    size_t index=FREELIST_INDEX(n);
    Obj* result=_freeList[index];
    if(NULL==result)
    {
        //自由鏈表無可用塊,調refill()填充
        return refill(ROUND_UP(n));
    }
    //調整自由鏈表
    _freeList[index]=result->_freeListLink;
    return (result);
}
void Dellocate(void* p,size_t n)
{
    //若n大於128,直接還給一級配置器
    if(n>(size_t)_MAX_BYTES_)
    {
        MallocAlloc::Dellocate(p,n);
    }
    //否則掛回到二級空間配置器自由鏈表的適當位置
    else
    {
        size_t index=FREELIST_INDEX(n);
        p->_freeListLink=_freeList[index];
        _freeList[index]=p;
    }
}

缺點:
1、引入內碎片,浪費空間
2、二級配置器分配的空間未還給操作系統,釋放空間是掛在自由鏈表上(進程結束才釋放)
事實上是它無法還給操作系統,因爲內存池從系統堆得到的連續空間被切分成了塊掛在自由鏈表上,而每一個塊的使用和釋放都不是統一的。假如鏈表的某一位置那一串想釋放,但其中有個別還在使用。所以它還是釋放不了。
3、如果一直分配,複用功能達不到
二級空間配置器本質上是想複用內存,自由鏈表從內存池得到一些內存,client用完後掛回去,下次有需要再從鏈表裏拿。那如果一直分配而不釋放,內存空間得不到複用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章