溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

STL-空間配置器

發(fā)布時(shí)間:2020-08-06 04:11:27 來(lái)源:網(wǎng)絡(luò) 閱讀:625 作者:追夢(mèng)途中 欄目:編程語(yǔ)言

1、為什么需要空間配置器?

內(nèi)存碎片:

STL-空間配置器

頻繁分配小內(nèi)存導(dǎo)致分配不出來(lái)大內(nèi)存,這就是外碎片;并且頻繁分配小內(nèi)存效率低

比如,系統(tǒng)依次分配了16、8、16、4、8byte,還剩一個(gè)8byte未分配,這時(shí)要分配一個(gè)24byte的空間,系統(tǒng)回收兩個(gè)16byte,總的空間剩余40byte, 但是卻分配不出來(lái)一個(gè)24byte。

二級(jí)空間配置器是為頻繁分配小內(nèi)存而生的一種算法,其實(shí)就是消除一級(jí)空間配置器的外碎片問(wèn)題


2、一級(jí)空間配置器和二級(jí)空間配置器

STL-空間配置器

如果申請(qǐng)的內(nèi)存大小超過(guò)128,那么空間配置器就自動(dòng)調(diào)用一級(jí)空間配置器。反之調(diào)用二級(jí)空間配置器。而且在這里要說(shuō)明的是空間配置器默認(rèn)使用的是一級(jí)空間配置器。

一、一級(jí)空間配置器

     一級(jí)空間配置器是malloc-free的封裝,實(shí)現(xiàn)類(lèi)似C++中new-handler機(jī)制:一旦申請(qǐng)空間不成功,在丟出bad-allloc異常之前,會(huì)先調(diào)用用戶(hù)自己指定的處理例程new-handler()。

    一級(jí)空間配置器的allocate()和reallocate()都是在調(diào)用malloc和realloc不成功時(shí),改調(diào)用oom_malloc和oom_realloc,后兩者都能循環(huán)調(diào)用內(nèi)存不足處理例程,期待在某次調(diào)用之后可以獲得足夠內(nèi)存而達(dá)到目的 ,但是若處理例程未被用戶(hù)設(shè)定,oom_malloc和oom_realloc便會(huì)拋出bad-alloc的異?;蛴胑xit(1)終止程序。

代碼如下:

// 一級(jí)空間配置器(malloc/realloc/free)

template<int inst> //非類(lèi)型模板參數(shù)
class MallocAllocTemplate
{

//1:分配內(nèi)存成功,則直接返回
//2:若分配失敗,則檢查是否設(shè)置處理的句柄handler
//有則調(diào)用以后再分配,不斷重復(fù)這個(gè)過(guò)程,直到分配成功為止.
//沒(méi)有設(shè)置處理的句柄handler,則直接結(jié)束程序

public:
 static void* Allocate(size_t size) //用于分配空間

 {
  void* ret = malloc(size);
  if (0 == ret)
  {
   ret = OomMalloc(size);
  }
  return ret;
 }

 static void Deallocate(void* p) //收回
 {
  free(p);
 }

 static void* Reallocate(void* p, size_t newsize) //用于指定地址重新分配空間
 {
  void* ret = realloc(p, newsize);
  if (ret == 0)
  {
   ret = OomRealloc(p, newsize);
  }
  return ret;
 }
private:
 static void* OomMalloc(size_t size)  //調(diào)用自定義的句柄處理函數(shù)handler釋放并分配內(nèi)存
 {
  ALLOC_FUN handler;
  void* ret;
  while (1)
  {
   handler = MallocAllocHandler;
   if (0 == handler)
   {
    cout << "out of memory" << endl;
    exit(-1);
   }

   handler();
   ret = malloc(size);
   if (ret)
   {
    return(ret);
   }
  }
 }

 static void* OomRealloc(void*p, size_t newsize)
 {
  ALLOC_FUN handler;
  void* ret;
  while (1)
  {
   handler = MallocAllocHandler;
   if (0 == handler)
   {
    cout << "out of memory" << endl;
    exit(-1);
   }

   handler();
   ret = realloc(newsize);
   if (ret)
   {
    return(ret);
   }
  }
 }
 static void(*SetMallocHandler(void(*f)()))(); //設(shè)置操作系統(tǒng)分配內(nèi)存失敗時(shí)的句柄處理函數(shù)
 {
  void(*tmp)() = MallocAllocHandler;
  MallocAllocHandler = f;
  return(tmp);
 }
 
};
template<int inst>
ALLOC_FUN MallocAllocTemplate<inst>::MallocAllocHander = 0; //句柄函數(shù)初始化為0



二、二級(jí)空間配置器

      二級(jí)空間配置器是由一個(gè)內(nèi)存池和自由鏈表配合實(shí)現(xiàn)的

   STL-空間配置器

startFree相當(dāng)于水位線,標(biāo)志內(nèi)存池的大小

自由鏈表中其實(shí)是一個(gè)大小為16的指針數(shù)組,間隔為8的倍數(shù)。各自管理大小分別為8,16,24,32,40,48,56,64,72,80,88,96,104, 112,120,128 字節(jié)的小額區(qū)塊。在每個(gè)下標(biāo)下掛著一個(gè)鏈表,把同樣大小的內(nèi)存塊鏈接在一起。類(lèi)似于哈希桶。

代碼如下:

// 二級(jí)空間配置器
template<bool threads,int inst>
class DefaultallocTemplate //二級(jí)空間配置器
{
public:
 enum{ ALIGN = 8 }; //基準(zhǔn)值對(duì)齊
 enum{ MAX_BYTES = 128 }; //最大字節(jié)
 enum{ FREELISTS = MAX_BYTES / ALIGN }; //自由鏈表大小
 union obj
 {
  union obj* listLink; //自由鏈表中指向下一個(gè)內(nèi)存塊的指針
  char clientData[1]; //調(diào)試用
 };
 static size_t FreeListIndex(size_t bytes) //得到所需內(nèi)存塊在自由鏈表中的下標(biāo)
 {
  return ((bytes + ALIGN - 1) / ALIGN - 1);
 }
 static size_t RoundUpNum(size_t bytes) //得到內(nèi)存塊大小的向上對(duì)齊數(shù)(8的倍數(shù))
 {
  return (bytes + ALIGN - 1)&~(ALIGN - 1);
 }
 static obj* FreeList[FREELISTS];  //維護(hù)自由鏈表
 static char* startFree;  //水位線(維護(hù)內(nèi)存池)
 static char* endFree;  //池底
 static size_t heapSize;


 static void* Allocate(size_t size)
 {
  if (size > MAX_BYTES)
  {
   return MallocAllocTemplate<inst>::Allocate(size);
  }
  void* ret = NULL;
  size_t index = FreeListIndex(size);

  if (FreeList[index]) //自由鏈表上有內(nèi)存塊
  {
   obj* ret = FreeList[index]; //取走當(dāng)前下標(biāo)的第一個(gè)給用戶(hù)
   FreeList[index] = ret->listLink;  //FreeList[index]指向下一個(gè)
  }
  else   //若自由鏈表沒(méi)有,則調(diào)用refill從內(nèi)存池填充自由鏈表并返回內(nèi)存池的第一個(gè)內(nèi)存塊
  {
   return Refill(RoundUpNum(size));
  }
  return ret;
 }

 static void* Reallocate(void* p, size_t oldsize, size_t newsize)
 {
  void* ret = NULL;
  if (oldsize > (size_t)MAX_BYTES&&newsize > (size_t)MAX_BYTES)
   return (realloc(p, newsize));
  if (RoundUpNum(oldsize) == RoundUpNum(newsize))
   return p;
  ret = Allocate(newsize);
  size_t copysize = oldsize > newsize ? newsize : oldsize;
  memcopy(ret, p, copysize);
  DeAllocate(p, oldsize);
  return ret;
 }
 static void Deallocate(void* p, size_t size)
 {
  if (size> MAX_BYTES) //如果大于MAX_BYTES直接交還給一級(jí)空間配置器釋放
   return MallocAllocTemplate<inst>::Deallocate(p, size);
  else //放回二級(jí)空間配置器的自由鏈表
  {
   size_t index = FreeListIndex(size);
   obj* tmp = (obj*)p;
   tmp->listLink = FreeList[index];
   Freelist[index] = tmp;
  }
 }

 
 //從內(nèi)存池拿出內(nèi)存填充自由鏈表
 static void* Refill(size_t size)
 {
  int nobjs = 20;//申請(qǐng)20個(gè)size大小的內(nèi)存塊
  char* chunk = ChunkAlloc(size, nobjs);
  if (nobjs == 1) //只分配到一個(gè)內(nèi)存
  {
   return chunk;
  }
 
  size_t index = FreeListIndex(size);
  obj* cur = chunk + size;
  obj* next = NULL;

  //將剩余內(nèi)存塊掛到自由鏈表上
  FreeList[index] = cur;
  for (int i = 1; i < nobjs-1; ++i)
  {
   next=(obj*)((char*)cur +size);
   cur->listLink = next;
   cur = next;
  }
  cur->listLink = NULL;
  return chunk;
 }
 
 //從內(nèi)存池中分配大塊內(nèi)存
 static char* ChunkAlloc(size_t size, int& nobjs)
 {
  char* ret = NULL;
  size_t Leftbytes = endFree - startFree; //剩余的內(nèi)存塊
  size_t Needbytes = size * nobjs; //所總共需要的內(nèi)存塊
  if (Leftbytes >= Needbytes)
  {
   ret = startFree;
   startFree += Needbytes;
  }
  else if (Leftbytes >= size) //不夠分配總size大小,但是夠分配單個(gè)size大小的
  {
   ret = startFree;
   nobjs = Leftbytes / size;
   startFree += nobjs*size;
  }
  else     //一個(gè)內(nèi)存塊都分配不出來(lái)
  {
   if (Leftbytes > 0)
   {
    size_t index = FreeListIndex(Leftbytes);
    ((obj*)startFree)->listLink = FreeList[index];
    FreeList[index] = (obj*)startFree;
    startFree = NULL;
   }
   //向操作系統(tǒng)申請(qǐng)2倍Needbytes加上已分配的heapsize/8的內(nèi)存到內(nèi)存池
   size_t getBytes = 2 * Needbytes + RoundUpNum(heapSize >> 4);
   startFree = (char*)malloc(getBytes);
   if (startFree == NULL) //從系統(tǒng)堆中分配內(nèi)存失敗
   {
    //到后面更大的自由鏈表中去取
    for (int i = size; i < MAX_BYTES; i += ALIGN)
    {
     size_t index = FreeList[FreeListIndex(i)];
     if (FreeList[index])
     {
      startFree = FreeList[index];
      FreeList[index] = FreeList[index]->listLink;
      endFree = startFree + size;
      return ChunkAlloc(size, nobjs);
     }
    }
    //山窮水盡
    //最后的一根救命稻草,找一級(jí)空間配置器分配內(nèi)存
    //(其他進(jìn)程歸還內(nèi)存,調(diào)用自定義的句柄處理函數(shù)釋放內(nèi)存)

    startFree = MallocAllocTemplate<inst>::Allocate(getBytes);
   }
   heapSize += getBytes; //從系統(tǒng)堆分配的總字節(jié)數(shù)(可以用于下次分配時(shí)進(jìn)行調(diào)節(jié))
   endFree = startFree + getBytes;

   return ChunkAlloc(size, nobjs); //遞歸調(diào)用獲取內(nèi)存
  }
  return ret;
 }
};
template<bool threads, int inst>
typename DefaultAllocTemplate<threads, inst>::obj*
DefaultAllocTemplate<threads, inst>::FreeList[FREELISTSIZE] = { 0 };

template<bool threads, int inst>
char* DefaultAllocTemplate<threads, inst>::startFree = 0;

template<bool threads, int inst>
char* DefaultAllocTemplate<threads, inst>::endFree = 0;

template<bool threads, int inst>
size_t DefaultAllocTemplate<threads, inst>::heapSize = 0;


部分代碼解釋?zhuān)?/p>

static size_t FreeListIndex(size_t bytes)//得到所需內(nèi)存塊在自由鏈表中的下標(biāo)
 {
  return ((bytes + ALIGN - 1) / ALIGN - 1);
 }

    此函數(shù)就是找到需要分配的內(nèi)存塊在自由鏈表中的什么地方,((bytes + ALIGN - 1) / ALIGN - 1),把要分配的內(nèi)存大小提升一個(gè)數(shù)量級(jí)(+7,每間隔8為一個(gè)數(shù)量級(jí)),然后除以8,減1,剛好能找到對(duì)應(yīng)的下標(biāo),取出一塊內(nèi)存塊給用戶(hù)。


static size_t RoundUpNum(size_t bytes) //得到內(nèi)存塊大小的向上對(duì)齊數(shù)(8的倍數(shù))
 {
  return (bytes + ALIGN - 1)&~(ALIGN - 1);
 }

     此函數(shù)是得到所需內(nèi)存塊大小的向上對(duì)齊數(shù)。在自由鏈表中,內(nèi)存塊大小總是8的倍數(shù),但是并不是每次所需內(nèi)存大小都是8的倍數(shù)。所以就要取比所需大小大或相等的內(nèi)存塊,這就是向上取整。&~(ALIGN - 1)相當(dāng)于將低8位置0,只取高8位,高8位總是8的倍數(shù),正好符合題意。


很關(guān)鍵的兩個(gè)函數(shù)static void* Refill(size_t size)和static char* ChunkAlloc(size_t size, int& nobjs):

//從內(nèi)存池拿出內(nèi)存填充自由鏈表
 static void* Refill(size_t size)
 {
  int nobjs = 20;//申請(qǐng)20個(gè)size大小的內(nèi)存塊
  char* chunk = ChunkAlloc(size, nobjs);
  if (nobjs == 1)//只分配到一個(gè)內(nèi)存
  {
   return chunk;
  }
 
  size_t index = FreeListIndex(size);
  obj* cur = chunk + size;
  obj* next = NULL;

  //將剩余內(nèi)存塊掛到自由鏈表上
  FreeList[index] = cur;
  for (int i = 1; i < nobjs-1; ++i)
  {
   next=(obj*)((char*)cur +size);
   cur->listLink = next;
   cur = next;
  }
  cur->listLink = NULL;
  return chunk;
 }


   STL-空間配置器

 當(dāng)在自由鏈表的下標(biāo)處沒(méi)有內(nèi)存塊時(shí),我們就必須調(diào)用refill去填充自由鏈表。申請(qǐng)時(shí)一般一次性申請(qǐng)20個(gè)內(nèi)存塊大小的內(nèi)存。通過(guò)移動(dòng)startFree指針將內(nèi)存池內(nèi)的一段內(nèi)存給“切割”出來(lái),然后切成小塊掛在自由鏈表下面。返回第一塊內(nèi)存塊給用戶(hù),其余的都掛在自由鏈表下,方便下次分配,根據(jù)局部性原理,這將極大地提升了分配內(nèi)存空間的效率。



//從內(nèi)存池中分配大塊內(nèi)存
 static char* ChunkAlloc(size_t size, int& nobjs)
 {
  char* ret = NULL;
  size_t Leftbytes = endFree - startFree; //剩余的內(nèi)存塊
  size_t Needbytes = size * nobjs; //所總共需要的內(nèi)存塊
  if (Leftbytes >= Needbytes)
  {
   ret = startFree;
   startFree += Needbytes;
  }
  else if (Leftbytes >= size) //不夠分配總size大小,但是夠分配單個(gè)size大小的
  {
   ret = startFree;
   nobjs = Leftbytes / size;
   startFree += nobjs*size;
  }
  else     //一個(gè)內(nèi)存塊都分配不出來(lái)
  {
   if (Leftbytes > 0)
   {
    size_t index = FreeListIndex(Leftbytes);
    ((obj*)startFree)->listLink = FreeList[index];
    FreeList[index] = (obj*)startFree;
    startFree = NULL;
   }
   //向操作系統(tǒng)申請(qǐng)2倍Needbytes加上已分配的heapsize/8的內(nèi)存到內(nèi)存池
   size_t getBytes = 2 * Needbytes + RoundUpNum(heapSize >> 4);
   startFree = (char*)malloc(getBytes);
   if (startFree == NULL) //從系統(tǒng)堆中分配內(nèi)存失敗
   {
    //到后面更大的自由鏈表中去取
    for (int i = size; i < MAX_BYTES; i += ALIGN)
    {
     size_t index = FreeList[FreeListIndex(i)];
     if (FreeList[index])
     {
      startFree = FreeList[index];
      FreeList[index] = FreeList[index]->listLink;
      endFree = startFree + size;
      return ChunkAlloc(size, nobjs);
     }
    }
    //山窮水盡
    //最后的一根救命稻草,找一級(jí)空間配置器分配內(nèi)存
    //(其他進(jìn)程歸還內(nèi)存,調(diào)用自定義的句柄處理函數(shù)釋放內(nèi)存)

    startFree = MallocAllocTemplate<inst>::Allocate(getBytes);
   }
   heapSize += getBytes; //從系統(tǒng)堆分配的總字節(jié)數(shù)(可以用于下次分配時(shí)進(jìn)行調(diào)節(jié))
   endFree = startFree + getBytes;

   return ChunkAlloc(size, nobjs); //遞歸調(diào)用獲取內(nèi)存
  }
  return ret;
 }

ChunkAlloc要做的就是去找操作系統(tǒng)要內(nèi)存,一次性要20個(gè),但是要考慮很多情況:

(1)內(nèi)存池里有足夠20塊大的內(nèi)存

(2)內(nèi)存池里有小于20塊大于等于1塊的內(nèi)存大小

(3)內(nèi)存池里連1塊內(nèi)存那么大的都沒(méi)有

具體這樣做:

 (1)如果有足夠的內(nèi)存,那么一次性就給20塊,返回第一塊給用戶(hù),其余的掛在自由鏈表上。
 (2)只有一塊或者多塊,返回一塊給用戶(hù)。
 (3) 沒(méi)有內(nèi)存了,找操作系統(tǒng)要。
 (4)操作系統(tǒng)沒(méi)有了,啟用最后一根救命稻草,調(diào)用一級(jí)空間配置器,通過(guò)句柄函數(shù)釋放內(nèi)存,分配內(nèi)存。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI